v6.4.6 — Futures PnL, process_orders_on_close & security robustness
What's new
A correctness-focused release for futures strategies, currency-aware data handling, and request.security() robustness. No behavior changes for scripts that were already correct under TradingView semantics — every fix closes a real divergence, deadlock, or rounding error reported from live use.
Special thanks to Zombajo for the precise, well-reproduced bug reports that drove most of this release. Detailed GitHub issues with real TradingView reference data are the single most valuable input we receive — keep them coming.
Strategy
Futures PnL now respects syminfo.pointvalue
Every PnL computation in strategy/__init__.py is now multiplied by syminfo.pointvalue — closed-trade P&L, high/low PnL feeding drawdown and runup, percent-commission base, entry_value / trade_value percent denominators, position-level and per-trade openprofit, and the close_all overshoot branch. The synthetic end-of-backtest "Open" exit emitted by script_runner.py is multiplied too, so the last row of the trades CSV matches closed trades for futures contracts.
Regression: test_050_futures_pnl.py runs an SMA(10/30) cross on NYMEX_MINI:QM1! 60m OHLCV captured from TradingView and asserts each closed trade's USD profit against TradingView's Net P&L USD, with a 200-trade coverage guard. Closes #50.
minmove != 1 symbols now round to the correct tick
lib.math.round_to_mintick (and the internal _price_round) used to assume minmove=1. It now reconstructs the tick as int(n / mintick + 0.5) * minmove / pricescale, which is bit-identical for minmove=1 (every existing test untouched) and correct for symbols like NYMEX_MINI:QM1! where pricescale=1000, minmove=25, mintick=0.025.
process_orders_on_close=true is now honoured
A new Position.process_orders_at_close() runs after main() in both the regular and the bar-magnified script-runner paths. It fills current-bar market, limit and stop orders at self.c — limit fills are not slipped (Pine "limit price or better"), stop fills are slipped, and the trigger kind is tracked so filled_by_type selects the correct exit comment. Same-bar strategy.exit(profit=…, loss=…) materializes its tick offsets against the matching open trade's entry price before the trigger check and re-indexes the order in PriceOrderBook.
Fills run in two phases: Phase 1 handles entries plus already-priced exits; Phase 2 re-scans current-bar exits after Phase-1 entries opened new trades. drawdown_summ / runup_summ contributions from the close pass are discarded — the bar's H/L was already booked by _finalize_bar_pnl against the open trades. _settle_close_pass_trades only updates cum_profit / entry_equity for trades closed during this pass.
Pine TV semantics: calc_on_order_fills is silently disabled when process_orders_on_close=true. var_snapshot is now instantiated only when COOF is on AND POOC is off, so every COOF re-run is automatically a no-op in the combined case.
cash_per_contract commission now charges both legs
Pine v6 charges commission.cash_per_contract on entry AND exit (2 * qty * commission_value per round-trip). PyneCore lumped cash_per_contract into the deferred-realization path together with cash_per_order, but only cash_per_order had a realization branch in the order-delete block, so the exit leg of cash_per_contract was silently dropped.
cash_per_contract is size-proportional and needs no deferral; only cash_per_order does (a flat fee split across partial fills). It is now realized inline, the same way the percent branch already was. Regression test test_044_cash_per_contract_both_legs.py uses a real CAPITALCOM:EURUSD 60m TradingView slice with 304 round-trips. Closes #51.
PriceOrderBook.add_order is now idempotent per (order, price)
Mixed explicit + tick exits (e.g. strategy.exit(limit=…, loss=…)) used to double-index the explicit side, which remove_order only deleted once — duplicates survived cancellation and could re-fill on the next bar. Adding the same (order, price) twice is now a no-op.
Core
request.security() no longer deadlocks on a crashed child
The main loop now polls security child-process liveness while waiting for security events, so a child that exits before signalling raises in the parent instead of hanging indefinitely. The live security-process map is threaded through protocol setup, and cleanup iteration is aligned with the new mapping.
barstate.* runtime states are now mutable variables
barstate.isfirst, islast, isnew, isconfirmed, ishistory, isrealtime, and islastconfirmedhistory switched from module properties to plain variables, so the runner can reset and update them consistently during script execution. The transformer's module_properties.json was updated accordingly.
Data
Symbol-prefix-aware base currency detection
detect_base_currency() now strips provider prefixes (e.g. BINANCE:, CAPITALCOM:) and TradingView ticker suffixes (!, .P, perp markers) before matching, so crypto and forex symbols like BINANCE:BTCUSDT.P or OANDA:EURUSD resolve to the bare asset code. This keeps crypto quantity rounding correct — basecurrency must remain a bare asset code (BTC, not BINANCE:BTCUSDT) for lot-size logic. Regression tests cover prefixed and suffixed symbols across crypto and forex.
Documentation
- New compatibility note in
docs/overview/compatibility.mdexplaining expected IEEE-754 numerical drift versus TradingView and that exact equality comparisons can diverge near sub-tick boundaries. docs/reference/lib/ta.mddocuments thatta.crossover/ta.crossunderintentionally use Pine's strict comparison semantics without epsilon tolerance.docs/overview/configuration.mdanddocs/reference/lib/syminfo.mddocument the bare-asset-code requirement forbasecurrency.- The GitHub bug-report template now has a PyneCore branch field.