Skip to content

Strategy command arguments lowered to fixed values: strategy.order() without qty, a switch qty from an int input, allow_entry_in() from an input #292

Closed

Description

@letiep97

Summary

Three valid v6 scripts translate and build, but an argument of a strategy command reaches the engine as a fixed value that differs from the Pine value. Each case has a control that differs in one place and trades as documented.

# Pine form Emitted argument Result
1 strategy.order('Long', strategy.long) with no qty qty = 0 no orders (0 trades instead of 68)
2 q = switch mode with 'Fixed' => fixedQty (an input.int) and => na, then qty = q int q, q = na<int>() no orders (0 trades instead of 71)
3 strategy.risk.allow_entry_in(dir == 'Long' ? strategy.direction.long : strategy.direction.short) with dir from input.string direction 0 (both) shorts are opened (71 trades instead of 35 long)

Versions

pineforge-codegen 1.9.1, pineforge-engine v1.9.1 (fc0a2df), Apple clang 17.0.0, macOS 15.8 arm64. Each script was built as is and run on BTCUSDT 1h bars from 2024-01-01 to 2024-03-01 (1,441 bars), initial capital 10000 through strategy_set_override, no commission or slippage. Codegen main at c1cc532 (after #290), run from source with the same engine, emits the same arguments and gives the same results.

Reproduction

All scripts share this header:

//@version=6
strategy('repro', default_qty_type = strategy.percent_of_equity, default_qty_value = 10)
fast = ta.ema(close, 9)
slow = ta.ema(close, 21)

1. strategy.order() without qty

if ta.crossover(fast, slow)
    strategy.order('Long', strategy.long)
if ta.crossunder(fast, slow)
    strategy.order('Short', strategy.short)

Generated:

strategy_order(std::string("Long"), true, 0, na<double>(), na<double>(), "", 0);
strategy_order(std::string("Short"), false, 0, na<double>(), na<double>(), "", 0);

0 trades. Control, the same calls with qty = na: strategy_order(std::string("Long"), true, na<double>(), ...), 68 trades, first order 0.02288 (10% of equity).

2. switch returning an int input or na, used as qty

mode = input.string('Default', 'Size', options = ['Default', 'Fixed'])
fixedQty = input.int(1, 'Fixed qty')
q = switch mode
    'Fixed' => fixedQty
    => na
if ta.crossover(fast, slow)
    strategy.entry('Long', strategy.long, qty = q)
if ta.crossunder(fast, slow)
    strategy.entry('Short', strategy.short, qty = q)

Generated:

int q = 0;
// ...
q = fixedQty;      // 'Fixed' branch
q = na<int>();     // default branch
strategy_entry(std::string("Long"), true, na<double>(), na<double>(), q, "", "", 0, -1);

0 trades with the default 'Default' mode. Control, q = mode == 'Fixed' ? fixedQty : na: double q, q = (((mode == std::string("Fixed"))) ? (fixedQty) : (na<double>()));, 71 trades, first order 0.02288. With input.float instead of input.int the switch form also gives 71 trades.

3. strategy.risk.allow_entry_in() with a direction chosen from an input

dir = input.string('Long', 'Direction', options = ['Long', 'Short'])
strategy.risk.allow_entry_in(dir == 'Long' ? strategy.direction.long : strategy.direction.short)
if ta.crossover(fast, slow)
    strategy.entry('Long', strategy.long)
if ta.crossunder(fast, slow)
    strategy.entry('Short', strategy.short)

Generated: set_pine_risk_direction(0);, the value for "both directions", although dir is read with get_input_string("Direction", std::string("Long")) just before.

71 trades, 35 long and 36 short. Control, strategy.risk.allow_entry_in(strategy.direction.long): set_pine_risk_direction(1);, 35 trades, all long.

Expected

  1. 68 trades as in the control. A strategy.order() call without a qty argument uses the strategy's default order size from default_qty_type and default_qty_value (default_qty_type and default_qty_value, position sizing); the qty parameter defaults to na (strategy.order()). Checked on TradingView (BINANCE:BTCUSDT 1h): the script and its control give the same trade list; the first order, on bar 60, is 10% of equity.
  2. 71 trades as in the control. A qty of na also means the default order size (position sizing). An int na should stay na when it is passed to the qty parameter. Checked on TradingView (BINANCE:BTCUSDT 1h): the switch form and its control give the same trade list; the first order is 10% of equity.
  3. 35 long trades and no short, as in the control, with the default input. The argument is an "input" value, which a parameter that takes "simple" values accepts (qualifiers, strategy.risk.allow_entry_in()). If only literal directions can be supported, a translation error would be safer than allowing both directions. Checked on TradingView (BINANCE:BTCUSDT 1h): the script and its control give the same trade list, all long: 35 trades from bar 0 to bar 1440, none short.

Workaround

  1. Pass qty = na explicitly, or a computed quantity.
  2. Use the ternary form of the control, or input.float for the fixed quantity.
  3. Pass a literal direction. We have no tested workaround that keeps the choice as an input.

Activity

  1. luisleo526 commented on Nov 1, 2026

    @luisleo526
    Contributor

    Thank you for the detailed report. We are checking each case against TradingView's behaviour and our source, and will post a result for each case in this issue by the end of 2026-11-03 (UTC). If a case needs longer, we will say so here with the reason.

  2. luisleo526 commented on Nov 5, 2026

    @luisleo526
    Contributor

    The ignored direction restriction is tracked in this known issue. All released versions are affected: a non-literal positional argument can lose an intended long-only or short-only restriction. Pass the literal direction constant positionally as a workaround; value = is a separate hard error on 1.10.0. Fixed in the next release.

  3. luisleo526 commented on Nov 6, 2026

    @luisleo526
    Contributor

    Status: all three cases are fixed on main: the argument of each strategy command now reaches the engine as the Pine value, not as a fixed one. The fixes ship in the next release. Read the note under the table before you compare trade counts.

    Checked with the code of main at 5b72a0c and the engine at d398279, on Linux aarch64: each script of the issue and its control were translated, the generated C++ was built against the engine and run on BTCUSDT 1h bars from 2024-01-01 to 2024-03-01 (1,441 bars), with initial capital 10000 and no commission or slippage, as in the report. "Before" is what the report measured on 1.9.1. The TradingView values are the ones the report gives.

    # Case TradingView Before main at 5b72a0c Matches TradingView?
    1 strategy.order() without qty same trade list as the control; the first order is on bar 60 and is 10% of equity 0 trades the script and its control give the same trade list; with trading from bar 21 on, 68 trades, and the first order is on bar 60 with quantity 0.02288 (10% of equity) Yes, see the note
    2 switch returning an int input or na, used as qty same trade list as the control; the first order is on bar 60 and is 10% of equity 0 trades the script and its control give the same trade list; with trading from bar 21 on, 71 trades, and the first order is on bar 60 with quantity 0.02288 Yes, see the note
    3 strategy.risk.allow_entry_in() with a direction chosen from an input 35 long trades, none short 71 trades, 35 long and 36 short the script and its control give the same trade list; with trading from bar 21 on, 35 long trades and none short (PineForge prints its diagnostic PF-W1503, partial runtime support for this call) Yes, see the note

    Note on the first bars: PineForge returns a value for ta.ema earlier than TradingView does (the diagnostic PF-W1505 that these scripts print), so a script that trades from its first bar makes trades in the first 21 bars that TradingView cannot make. The numbers above start trading at bar 21 (the engine's strategy_set_trade_start_time, set to the open of bar 21), the first bar on which TradingView can produce a crossover of the two EMAs (its 21 bar EMA has its first value on bar 20). The same scripts trading from bar 0 give 71, 74 and 37 trades for cases 1, 2 and 3; 3, 3 and 2 of them are filled before bar 21, and the rest are the 68, 71 and 35 above. The script and its control give the same trade list in both ways.

    Not run: x86 and macOS.

  4. luisleo526 commented on Nov 8, 2026

    @luisleo526
    Contributor

    Update: today's check confirms all three argument-handling fixes on main, with the separate moving-average start-up difference described below.

    Checked on 2026-11-08 on codegen main at 7d7681dc with engine main at 755e6658, on the 1,441-bar BTCUSDT 1h sample. Each script and its control give identical trade lists, both from bar 0 and when trading starts at bar 21.

    # Case Result on main today Change Matches the value the issue prints?
    1 strategy.order() without qty With trading from bar 21: 68 trades, identical to the control; first fill on bar 60, quantity 0.02288 (10% of initial equity) #327 (08261a24) Yes for control equality, first order and size; start-up qualification below
    2 switch returning an int input or na, used as qty With trading from bar 21: 71 trades, identical to the control; first fill on bar 60, quantity 0.02288 #327 (08261a24) Yes for control equality, first order and size; start-up qualification below
    3 strategy.risk.allow_entry_in() with a direction chosen from an input With trading from bar 21: 35 long trades and none short, identical to the control #327 (08261a24) Yes: the issue prints 35 long trades and none short; start-up qualification below

    The moving-average values agree from the first bar where TradingView has a value; before that PineForge returns a number where TradingView has na. This is a separate known start-up difference that we are working on. Starting trading at bar 21 excludes that window. Trading from bar 0 gives 71, 74 and 37 trades for cases 1, 2 and 3, including 3, 3 and 2 fills before bar 21. The 68 and 71 counts above are control counts, not TradingView totals printed by the issue. Case 3 also reports PF-W1503 for partial runtime support.

    This is fixed on main and ships in 1.11.0, whose publish target is 2026-11-10. This issue will be closed when that release is published.

  5. luisleo526 commented on Nov 9, 2026

    @luisleo526
    Contributor

    Fixed in v1.11.0: all three cases (the strategy command arguments that were lowered to fixed values) are in the v1.11.0 release. Closing.

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