Skip to content

PyneCore v6.8.5 — Same-bar exits and live-feed robustness

Latest

Choose a tag to compare

@wallneradam wallneradam released this 14 Aug 16:56
· 1 commit to main since this release

TradingView compatibility

  • A bracket its own entry activates intrabar now acts on that same bar. strategy.exit(profit=..., loss=..., trail_points=...) states its levels as tick offsets from the entry price, so they cannot exist before the entry fills — and they were only turned into real price levels at the bar open and in the process_orders_on_close pass. An entry filling partway through a bar therefore left its bracket in no price bucket of the order book at all, reachable by no leg of the intrabar walk, and the exit was deferred to a later bar. The offsets are now resolved at the fill, and the leg walk resumes at that price level so the fresh bracket keeps its chronological place among the levels still ahead. A trailing leg starts at the entry fill price and follows only the segments of the assumed path still ahead of it, so an extreme the bar reached before the fill stays out of its water mark. Measured on CAPITALCOM:EURUSD 30m: a buy-limit entry on an open -> high -> low -> close bar, where the high precedes the fill, closed 0 of 2063 trades on its entry bar, while the same entry on an open -> low -> high -> close bar closed 1497 of 1526, every one at exactly high - trail_offset. (#76)

  • Session ranges. Hour 24 is accepted as an end-of-day hour, so "0000-2400" is the all-day session instead of a parse error that left every bar na. A bar ending at or past midnight no longer wraps its time-of-day back to 00:00 and reads as "before the session start", which made "0000-2359" miss every last bar of the day. Both endpoints are normalized through minutes-of-day, and the overnight branch now uses the same overlap contract as the same-day branch.

  • Same-bar entry replacement. Re-placing an entry under an id that already has an unfilled order from the same bar modifies that order and carries the raw quantity only; the reversal flip is not computed again. Measured on TradingView: with a 10-contract long open, one strategy.entry(short, 4) reverses to 4 short, while the same call issued twice on one bar sells only 4 and leaves 6 long.

  • Percentile family. ta.percentile_nearest_rank and ta.percentile_linear_interpolation now get one machine per call site (loop iterations included), an underfull window topped up from the source's bar history, and a na length that leaves the machine untouched instead of forcing a rebuild. An na length is an all-na series in ta.median, ta.mode and both percentile functions, matching TradingView, where every other rolling function raises instead — an unset array.new_int() slot fed to one of them used to halt the script. The rolling state survives a length that varies mid-run, and the length is truncated to an integer before the domain check, so a fractional int-typed value behaves like the integer it is.

  • Rolling history growth is monotonic in ta.change, ta.cog, ta.dev, ta.linreg, ta.percentrank, ta.rci, ta.roc and ta.wma, so a dipping series length no longer discards the history a later increase needs.

  • Sizing and cash rounding. Booked commission cash flows round half-up to 1e-6 account-currency units. Explicit qty arguments are quantized by flooring their shortest round-trip decimal onto the mincontract grid instead of snapping in float space. Default-sized price-based entries freeze their money budget at the (last) placement close and take only the per-unit cost from the fill. percent_of_equity market entries are sized at the placement close pushed by the slippage, and a sub-1e7 market entry drops its last lot on odd-tick sizing prices when the budget no longer covers the inflated floored cost. The accumulated position and the open-trade residual snap back onto the lot grid after close fills, so later percentage-based closes floor correctly.

  • Exit binding. An exit leg binds to the openable part of a pending reversal rather than to the whole order: counting the closing leg reserved half the order instead of half the position, and a sibling leg then found nothing left and froze a shrunk bracket at its first size. Legs also record the bound size they reserved against, so a stale sibling no longer blocks a re-issue. The close_all overshoot trade now requires a real lot, so a reversal's sub-lot float residue no longer opens a ghost trade holding a pyramiding slot the next entry needs.

  • calc_on_order_fills and live intra-bar ticks roll function instances back to their bar-start state instead of dropping them, and the bar's closed trades survive the repeated order passes.

  • request.security: an empty symbol is the chart's own instrument, and a coarser intraday context on the chart symbol is served by resampling the chart feed. timeframe.change("") reads the chart timeframe.

  • timestamp() uses the Julian calendar before 1582-10-15 and folds years outside datetime's range by whole Gregorian cycles, as TradingView does.

Fixes

  • Live feeds that mis-stamp their intra-bar updates. A provider that pushes the bar close and then keeps sending quote updates under that same timestamp — instead of opening the next slot — made the runner re-execute the settled bar and overwrite its OHLC. Every series derived from it was then built from a price belonging to the following period: the close[1] a crossover compares against, and with it any EMA or ATR reading. Measured on a live Capital.com feed, where it swallowed a crossover the same minute a cTrader run on the same instrument acted on. Such an update is now dropped once the live stream has closed that bar; the UI tick hook still receives it, and the first drop warns, so a mis-stamped feed surfaces instead of quietly degrading into close-only execution.

  • A reversed-away exit no longer halts the run. The exit bracket chose the legs to protect from the net book alone, so an exit whose parent entry had been reversed away was amended onto the new opposite-side position with inverted levels. Venues reject that attach, which escalated into a defensive close for a parent that no longer existed. A net side that is not the one the exit protects is now the same non-halting skip as a flat book. The defensive close following such a reject also armed a pending marker even when the close fan dispatched no leg at all, so no fill could ever settle it and the grace window always ended in a BrokerManualInterventionError.

  • Live startup. The initial connect deadline is enforced by racing the connect task instead of asyncio.wait_for, which decides on the cancellation it injected — a connect() that swallowed it was reported as a success past the deadline. An abandoned handshake now closes whatever it still opens from inside the connect task, so a provider that ignores its cancellation cannot leave a live venue connection behind a failed startup. Every wait a provider hook could stretch forever is bounded, including the startup thread join, which no longer follows shutdown_timeout (0 meant wait forever, which froze the startup permanently).

  • Startup gap recovery probes backfill_closed_bars with getattr rather than calling it blind. Providers are duck-typed here, so one without the method raised into the best-effort handler and logged a recovery-failure warning on every start.

  • Live reduction vs reversal. skip_flip is carried through the intent pipeline, so a replacement entry no longer folds into a MARKET stop-and-reverse on the live account where the simulator and TradingView merely reduce. Under calc_on_every_tick the fold may already be working at the broker when the replacement arrives; the engine now records the folded dispatch, resizes it back to the raw quantity and cancels the remainder once the fills cover it. strategy.close and strategy.exit keep the raw quantity in broker mode.

Features

  • linefill.all, chart.point.now() defaulting to close, na-tolerant polyline.delete, and a copied table.all registry.
  • initial_connect_timeout is a provider-tunable bound on the live initial connect. LiveProviderPlugin now documents its executor contract: a blocking call handed to a worker thread must carry its own timeout, because shutdown cancels the task, never the worker.
  • CloseIntent carries engine-owned routing provenance instead of plugins inferring it from the comment.