Repository navigation
Description
Summary
Two scripts with the positional percentage form of strategy.risk.max_drawdown(), each compared with the same script at a limit of 99 and with TradingView:
| Script | TradingView | PineForge |
|---|---|---|
1. Reversals only (strategy.entry long / short), limit 2 |
halts after the second losing trade: 3 trades | no halt: 52 trades, the same as with limit 99 |
2. Entries from flat (strategy.entry / strategy.close), limit 5 |
no halt: 26 trades, the same as with limit 99 | halts after the fifth trade: 5 trades |
In both runs TradingView acts as if the limit were compared with the equity of closed trades. In script 2 PineForge halts where closed-trade equity is 0.76% below its peak; that point is 5.0% below the highest equity including the open trade's profit.
Codegen reports PF-W1503 strategy.risk.max_drawdown(...) has partial PineForge runtime support for both scripts, and pineforge-4pass/pineforge-codegen-oss#325 says the percentage-limit implementation has not been compared with TradingView. These are two TradingView measurements for that comparison. That issue also notes that a reversal-only trace gave the same trades for every max_drawdown form and did not demonstrate a halt; script 1 is a reversal-only strategy where TradingView does halt.
Versions
pineforge-engine v1.11.0 (a4472324) with pineforge-codegen 1.11.0, and v1.10.0 with Codegen 1.10.0: the same trade lists on both. Apple clang 17.0.0, macOS 15.8 arm64. BTCUSDT 1h bars from 2024-01-01 to 2024-03-01 (1,441 bars), initial capital 10000, no commission or slippage, mintick 0.01, qty_step and mincontract 0.00001, input timeframe = script timeframe = 1h.
TradingView: BINANCE:BTCUSDT, 1h, Deep Backtesting over the same dates, measured on 2026-11-10.
Reproduction
1. Reversals only
//@version=6
strategy("max drawdown rule, reversals", overlay = true, initial_capital = 10000, default_qty_type = strategy.percent_of_equity, default_qty_value = 50)
strategy.risk.max_drawdown(2, strategy.percent_of_equity)
if ta.crossover(ta.ema(close, 10), ta.ema(close, 30))
strategy.entry("Long", strategy.long)
if ta.crossunder(ta.ema(close, 10), ta.ema(close, 30))
strategy.entry("Short", strategy.short)
TradingView, 3 trades, net −209.10:
| Trade | Entry | Exit | Profit | Closed-trade equity |
|---|---|---|---|---|
| Short 0.11685 | 2024-01-03 13:00 at 42789.39 | 2024-01-04 17:00 at 43915.70 | −131.61 | 9868.39 |
| Long 0.11235 | 2024-01-04 17:00 at 43915.70 | 2024-01-05 17:00 at 43225.99 | −77.49 | 9790.90 (2.09% down) |
| Short 0.11325 | 2024-01-05 17:00 at 43225.99 | 2024-01-05 17:00 at 43225.99 | 0 | 9790.90 |
The reversing short opens and is closed in the same fill, and no order is placed afterwards. With max_drawdown(99, …) TradingView gives 52 trades (51 closed, one open at the end of the range).
PineForge: 52 trades for limit 2, identical to its 52 trades for limit 99. Its first trades are TradingView's limit-99 trades (Short 0.11685, Long 0.11235, Short 0.11325 closed on 2024-01-05 22:00 at 43946.26, …).
2. Entries from flat
//@version=6
strategy("max drawdown rule", overlay = true, initial_capital = 10000, default_qty_type = strategy.percent_of_equity, default_qty_value = 100)
strategy.risk.max_drawdown(5, strategy.percent_of_equity)
if ta.crossover(ta.ema(close, 10), ta.ema(close, 30))
strategy.entry("Long", strategy.long)
if ta.crossunder(ta.ema(close, 10), ta.ema(close, 30))
strategy.close("Long")
TradingView: 26 trades (25 closed, one open at the end of the range), identical to the 26 trades with max_drawdown(99, …). Over the run its closed-trade equity is never more than 4.34% below its peak.
PineForge: 5 trades, equal to TradingView's first five, then no entry for the rest of the run. With limit 99 it gives 26 trades.
| Trade | Entry | Exit | Profit | Closed-trade equity |
|---|---|---|---|---|
| Long 0.22770 | 2024-01-04 17:00 at 43915.70 | 2024-01-05 17:00 at 43225.99 | −157.05 | 9842.95 |
| Long 0.22397 | 2024-01-05 22:00 at 43946.26 | 2024-01-06 08:00 at 43653.20 | −65.64 | 9777.32 |
| Long 0.22285 | 2024-01-06 17:00 at 43872.35 | 2024-01-08 01:00 at 43632.00 | −53.56 | 9723.75 |
| Long 0.21784 | 2024-01-08 12:00 at 44636.00 | 2024-01-10 03:00 at 45971.10 | +290.84 | 10014.59 |
| Long 0.21541 | 2024-01-10 20:00 at 46489.19 | 2024-01-12 02:00 at 46135.48 | −76.19 | 9938.40 |
| Long 0.23116 (TradingView only) | 2024-01-15 20:00 at 42992.67 | 2024-01-16 00:00 at 42511.10 | −111.32 | 9827.08 |
During the fifth trade the highest hourly close is 48571.22, which puts equity including open profit at 10463.08; the exit at 46135.48 leaves 9938.40, 5.0% below that (5.8% below the value at the highest high, 48969.48). The fifth trade itself closes at the same time and price on both sides; only the later entries are missing on PineForge.
Expected
- A halt when the limit is reached in a strategy that only reverses, as on TradingView.
- No halt in script 2 on this data, as on TradingView.
These may be two separate causes; they are filed together because both concern when the rule fires. Only these two scripts were measured, so what TradingView compares the limit with is an inference from them.
Workaround
None known.
Activity
luisleo526 commentedon Nov 10, 2026
Thank you for the report and the script. It is received and in triage: we are reproducing it on your script and comparing against TradingView. I will post the result here, confirmed or not and with the planned fix version, by 2026-11-11 12:00 (Taipei time). If it is confirmed as a wrong value that ships in a released version, the release notes will say so with a workaround.
luisleo526 commentedon Nov 10, 2026
Result (2026-11-10): confirmed.
Trigger: the percentage form of strategy.risk.max_drawdown(). Affected versions: reproduced on 1.11.0; the reporter sees the same trade lists on 1.10.0. Effect: two differences from TradingView measured by the reporter. (1) In a strategy that only reverses with strategy.entry long and short, the halt does not stop trading: with a 2 percent limit PineForge gives 52 trades, the same as with 99, where TradingView gives 3; PineForge sets its halt but a reversing entry is not blocked and the open position is not closed. (2) In a strategy that enters from flat, PineForge halts early: with a 5 percent limit it gives 5 trades where TradingView gives 26, because PineForge measures the fall from the highest equity including the open trade's profit, while in both measured scripts TradingView behaves as if it measured the equity of closed trades. Workaround (checked by running it on the reporter's two scripts): a guard in the script on the equity of closed trades, that is initial_capital + strategy.netprofit, its running peak, and strategy.close_all() with no new entries once the fall reaches the limit. The entries-from-flat script then gives the same 26 trades as TradingView. The reversal script stops after the second losing trade with the same first two trades as TradingView; the position opened by the next reversal is closed one bar later (a loss of 43.43) where TradingView closes it at the same fill with zero profit, so this guard is close to TradingView's behaviour but not identical to it. Plan: this stays an open difference; changing it needs TradingView measurements of the halt, which are being prepared.
Pin the layout sizes and offsets that the drawdown rule's new field m…
Merge the engine 1.11.1 train into the percent default quantity cap
Merge the engine 1.11.1 train into the reversal exit sizing change
luisleo526 commentedon Nov 11, 2026
Fixed on the engine main branch in 360c4ab (#825). It will be in the next engine release.
letiep97 commentedon Nov 13, 2026
Confirmed fixed in 1.12.0, thank you.
Checked on the 1.12.0 release (engine v1.12.0
6e27d0f2, pineforge-codegen 1.12.0), built from source on macOS 15.8 arm64, with the scripts from this issue (BTCUSDT 1h, 2024-01-01 to 2024-03-01, capital 10000, no commission).