Skip to content

v6.8.6

Latest

Choose a tag to compare

@wallneradam wallneradam released this 15 Aug 15:26
· 2 commits to main since this release

TradingView compatibility

  • Fills are booked on the symbol's tick grid. TradingView stores a filled price as a tick count scaled back into price units, so the price it records can sit a hair away from the price the fill triggered at. Every booked entry and exit price now goes through round(price / syminfo.mintick) * syminfo.mintick. Measured on BINANCE:BTCUSDT 30m: all 3408 strategy.opentrades.entry_price and all 2840 strategy.closedtrades.exit_price values reproduce, and strategy.closedtrades.profit is exactly size * (exit - entry) on those prices. The algebraically equal ticks * minmove / pricescale form — what math.round_to_mintick uses — misses 438 of the 3408, so the closing operation has to be a multiply by the tick size, not a divide by the price scale.

  • strategy.position_avg_price is the accumulated cost divided by the position size, re-derived on every fill, instead of a re-weighting of the previous average. The two are algebraically identical and round differently: over 22720 in-position bars of a pyramiding run the division matches TradingView on every single bar, while the blend only reproduces the 13440 single-leg ones.

  • math.round(x, precision) rounds the exact binary value of the double scaled by 10 ** precision, ties away from zero, with a 1e-10 absolute tolerance on that scaled value — the same slack TradingView's relational operators carry. All 141985 sampled values reproduce at precision 2, 3 and 5, and no tolerance below 5e-11 or above 2e-10 does. The tolerance is what separates math.round(118152.265, 2) -> 118152.27 from math.round(100019.7405, 3) -> 100019.740: both scale to an exactly representable .5, so neither shortest-decimal rounding nor any plain double model can tell them apart. At precision 8 on single-digit values the tolerance no longer separates the ties; that regime stays unmodelled.

    The three together took the last diverging script of the public conformance corpus from 99.13% plot agreement to an exact match on every bar.

  • A session-qualified time() / time_close() reports session bars, not the plain timeframe grid filtered by a session mask. A daily request reports the session occurrence's own open and close; an intraday request tiles the occurrence starting at the session open and truncates the last bucket at the session close, so an off-grid session shifts the whole series; weekly and monthly requests run from the period's first session open to the next period's and are never na. timeframe_bars_back walks these session bars, continuing into earlier occurrences instead of falling off the current one. The day mask names the weekday the occurrence's last minute falls on, which is what puts an overnight "1700-0200:23456" on Sunday evening through Friday morning. Session endpoints keep their nominal wall clock across daylight-saving changes and the close is taken at the end of the session's last minute, so a run closing at 02:00 on a fall-back date ends at the first 01:59. TradingView's own intraday mask contradicts its daily bounds on a change night when an endpoint lands on the changing hour; the measured daily bounds win there, which costs at most 14 of 28396 bars on the affected session shapes.

  • calc_on_order_fills re-executions stand where the fill happened. A COOF pass now sees the broker emulator at the point of the bar it has walked to rather than at the bar open, and that price both sizes the orders the body places and fills them. The walk is a cursor over the assumed path (open, the extreme nearest it, the other extreme, close): each pass stands on the node its triggering fill happened at and at least one node further along than the previous pass, and reaching the closing node ends the loop — which is the four-executions-per-bar bound TradingView shows. Measured on BINANCE:BTCUSDT 30m with a body whose only way into a position is a stop 0.3% off the close: it marks at the extreme ending the leg the stop filled on in all 1100 entry bars on both sides, and the execution count matches TradingView on every bar. The magnified path additionally bounds the COOF loops per sub-bar, collects every pass's closed trades instead of only the last pass's, and resumes at the triggering fill's sub-bar so orders a re-execution places can no longer fill in sub-bars already behind them.

Fixes

  • A dispatch that outruns the write bridge is no longer killed by a raw timeout. The bridge keeps waiting on the same future for a grace window sized above every plugin's worst-case request bound (a Capital.com order POST can take 50 s), so the plugin's own timeout classifies the disposition; only a write still unresolved after the grace escalates to the controlled manual-intervention halt.

  • FIFO cleanup trusts the consumed-entry evidence, not the label. A netting fold's closing leg is labelled with the reversing intent's id, so the label-derived cleanup tore down the tracking of the live new entry and swallowed the re-entry signals that followed. record_fill's FIFO walk is now authoritative and the label path only runs as a fallback when the walk produced nothing.

  • External-flatten detection was dead for every protected bot. The periodic reconcile's shrink-to-zero branch returned unconditionally on any active intent, and an armed bracket keeps an exit intent alive for the position's whole life. The wait is now a bounded 120 s confirmation clock; on expiry the book is cleared, the flattened entries' slots are retired, and late close fills for them are dropped until a fresh entry fill for the same Pine id re-arms them.

  • A reconnect backfill can no longer move the strategy's clock backwards. A closed bar at or before the last confirmed one — settled history re-served after a reconnect — is dropped with a warning instead of rebuilding the strategy's series from it.

  • The newest leg of a pyramid round stayed permanently unprotected. A same-sync pyramid entry's fill is not yet journaled when the exit-bracket fan runs, so the fan attached the bracket to the older legs only, went active and never re-fanned. run_exit_bracket now defers — a non-halting skip surfaced as position_book_incomplete — while any protected-side entry's cumulative fill is short of its quantity, and the re-run after the fill covers all legs. Working rows and opposite-side entries do not defer the fan.

  • A just-booked fill survives a stale flat snapshot. When the bot's cancel raced a resting entry's fill and lost, the slots were already empty and no exit was armed, so a /positions snapshot predating the fill cleared the freshly booked position instantly and left the venue leg unowned. A fill booked within the grace now starts the same bounded confirmation clock; a venue still flat past the grace clears as before.

  • The @pyne magic-comment pre-check no longer rejects a long module docstring. It read a 1 KB head and required the closed docstring inside it, so a valid script whose docstring ran past the window was refused. It now delegates to the import hook's head detector, which matches a docstring beginning with the magic comment without needing the closing quotes.

  • strategy.opentrades.profit() returns 0.0 outside a running script instead of failing on the internal assertion.

Features

  • adopt_untracked_position_legs() seeds confirmed position rows for untracked venue legs from a symbol-scoped PositionLeg list, so netting venues with handle-addressed closes (a Capital.com or IG-style delete per deal id) share one canonical adoption path instead of hand-rolling account-snapshot parsing. A leg on a foreign symbol raises ValueError, which makes the symbol scoping structural, and the dedup covers live rows, refs and other runs' exposure on the same account.

  • import_hook.source_starts_with_pyne is now public — the head detector that decides whether a source file carries the magic comment.