Skip to content

strategy.risk.max_drawdown(value, strategy.percent_of_equity): no halt in a strategy that only reverses, and an early halt in a strategy that enters from flat #804

Closed

Description

@letiep97

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

  1. A halt when the limit is reached in a strategy that only reverses, as on TradingView.
  2. 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

  1. luisleo526 commented on Nov 10, 2026

    @luisleo526
    Collaborator

    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.

  2. luisleo526 commented on Nov 10, 2026

    @luisleo526
    Collaborator

    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.

  3. luisleo526 commented on Nov 11, 2026

    @luisleo526
    Collaborator

    Fixed on the engine main branch in 360c4ab (#825). It will be in the next engine release.

  4. letiep97 commented on Nov 13, 2026

    @letiep97
    Author

    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).

    • Reversal-only script, 2% limit: 3 trades, net -209.10, as TradingView (1.11.0: 52 trades). With a 99% limit: 52 trades, unchanged.
    • Script entering from flat, 5% limit: 26 trades, net 679.40, as TradingView (1.11.0: 5 trades).
    • A 100%-of-equity variant of the reversal script, 5% limit: 8 trades, net -561.78, the exact TradingView figures (1.11.0: 88 trades).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions