Repository navigation
Description
Hi — I think I have found something worth your attention, and it reproduces entirely inside
PineForge with no external comparison needed.
The observation
Take any strategy that uses priced (limit=) entries plus a strategy.exit bracket, and add
one line that can never execute:
if bar_index < 0
strategy.close("L")
bar_index is never negative, so this call is unreachable. Yet on engine 0.12.2 /
codegen 0.10.1, CME ES continuous 1-minute, 86,124 bars, mintick=0.25 / pointvalue=50
supplied via PINEFORGE_SYMINFO:
without the dead strategy.close |
with it | |
|---|---|---|
| trades | 1,481 | 1,482 |
| wins | 785 | 787 |
| Net PnL | -10,402.00 | -10,584.00 |
qty distribution |
{1.0: 1481} |
{1:1422, 2:53, 3:5, 4:1, 5:1} |
trades with qty > 1 |
0 | 60 |
Adding unreachable code changes 60 position sizes and moves Net PnL by 182.00.
Why it happens
script_has_strategy_close_ is an AST scan, so mere textual presence of
strategy.close / strategy.close_all sets it — reachability is not considered. I confirmed
this with PINEFORGE_TRANSPILE_ONLY=1: the generated constructor contains no
script_has_strategy_close_ assignment for the baseline strategy, and
script_has_strategy_close_ = true; once the dead call is added.
The flag then gates the deferred-flip carry in src/engine_orders.cpp:
tv_deferred_flip = script_has_strategy_close_ && is_priced_entry
&& tv_carry_qty > 0.0 && (carry_was_long ? !is_long : is_long);
qty = tv_deferred_flip ? (tv_carry_qty + base_qty) : base_qty;Conditions (b), (c) and (d) are satisfied by the strategy either way. Only (a) flips, and
with it 60 fills change size.
The question
The comment introducing the rule (around engine_orders.cpp:730-758) says the carry applies
when the original position "is later closed (by strategy.close, close_all, or any
exit) and the priced entry now fires from FLAT". My strategy closes via a strategy.exit
bracket, which reads to me like "any exit" — but condition (a) admits only strategy.close.
So: is the gate meant to be a proxy for "this script can go flat by means other than the
bracket", or should a strategy.exit bracket fill arm the carry on its own? Either way,
gating on textual presence of an unreachable call looks like something you would want to know
about.
Supporting evidence that the carry-armed behaviour is the TradingView-faithful one
This part does depend on an external comparison, so I flag its limits below.
I ran the same strategy on TradingView Desktop, CME_MINI:ES1! 15-minute, 439 closed trades:
430 at size 1, seven at size 2, one at size 3, one at size 4. Two identities hold on all 439
trades, so the sizes are real and not a reporting artifact:
commission == 2.25 * 2 * size(the size-4 trade shows commission 18.00 = 2.25 x 8 legs)net_pnl == price_delta * 50 * size - commission
Eight of the nine oversized trades open at the exact timestamp the previous trade closed via
its own XL/XS bracket, and there is a visible runaway on 2026-06-04: size 2 at 20:45,
size 3 at 21:00, size 4 at 21:15. PyneCore 6.7.0 reproduces the same regime unprompted
(51 trades at size 2, 3 at size 3, out of 1,490).
So TradingView appears to arm the carry after a bracket close, which is the behaviour
PineForge produces only when the flag happens to be set.
Limits of this part: none of the three runs share data. The TradingView run is
CME_MINI:ES1! 15-minute 2025-09 to 2026-07; the PineForge and PyneCore runs are Databento
ES continuous 1-minute 2025 Q1. Different vendor, timeframe, calendar and roll convention. On
my account the TradingView ES 1-minute feed terminates at 2026-07-02, so I cannot run
TradingView on the same bars. I would not read the oversized rates across engines as
comparable; what I do consider solid is that TradingView produces sizes 2, 3 and 4 at all
under these settings.
For balance: on a separate divergence PineForge is the one that looks right
Of the 2,634.50 total P&L gap between PineForge and PyneCore at the correct symbol spec,
1,925.00 (73%) comes from 96 exit-fill price differences where PineForge is correct and
PyneCore is not — PineForge applies the favourable open clamp max(limit, open) on a
gapped take-profit, while PyneCore reclassifies it to a market order and adds a tick of
adverse slippage. I measured TradingView's rule with a controlled slippage 0-vs-1 pair: of
17 trades, the 7 fills that moved were every one a stop-side exit and the 10 that did not
move were every one a limit-side exit, so slippage applies to stops and not limits on
TradingView. PineForge matches. I am raising that separately with PyneCore.
The size difference discussed above is 506.50 (19%) of that same gap. So this is not a
one-sided report.
Reproduction
Baseline and probe strategies plus both JSON reports are available; the only textual
difference between them is the three lines quoted at the top. Command used:
docker run --rm \
-e PINEFORGE_SYMINFO=/in/syminfo.json \
-v strategy.pine:/in/strategy.pine:ro \
-v ohlcv.csv:/in/ohlcv.csv:ro \
-v syminfo.json:/in/syminfo.json:ro \
ghcr.io/pineforge-4pass/pineforge-release:engine0.12.2-codegen0.10.1
syminfo.json is {"syminfo": {"mintick": 0.25, "pointvalue": 50.0, "timezone": "UTC"}}.
Two minor container-interface notes
Both may well be intentional; they cost me time so I mention them in case they are not.
PINEFORGE_SYMINFOis not wired inpineforge-engine:0.10.10—run_json.pyaccepts
--syminfoand callsapply_syminfo, butentrypoint.shnever passes it. It works in
pineforge-release:engine0.12.2. Since thepineforge-engineGHCR package still tags
latestat 0.10.10 and the move topineforge-releaseappears only in release notes, I
spent a while concluding symbol specs were unreachable before finding the newer image.margin_long/margin_shortreach the generated constructor but are not accepted keys
inset_strategy_override, so they cannot be changed viaPINEFORGE_OVERRIDES.
docs/pine_v6_audit_master.mddoes document them as compile-time only.
Happy to share any of the artifacts or run further A/Bs.
Activity
luisleo526 commentedon Aug 1, 2026
Thank you, @h1rot — this was the first issue reported by someone from the PineForge community, and we really appreciate the care you put into it.
The minimal dead-code A/B, transpile evidence, runtime analysis, and balanced TradingView/PyneCore comparison made the root cause unusually clear and gave us a strong regression target.
We fixed this in #143, which is now merged into
main.script_has_strategy_close_is retained only for generated-code compatibility and no longer affects runtime behavior, so an unreachablestrategy.closecan no longer change fills. We also added a metamorphic regression test and validated the change against the full corpus and our downstream strategy suite.Thanks again for taking the time to report this so thoroughly — it genuinely helped improve PineForge.