Declaration
strategy("My strategy", overlay=true,
initial_capital = 100_000,
default_qty_type = strategy.cash,
default_qty_value = 10_000,
commission_type = strategy.commission.percent,
commission_value = 0.05,
slippage = 1,
pyramiding = 1,
process_orders_on_close = false)
- Every argument to
strategy()isconst. See Declaration statements for the full parameter list. strategy()scripts can also use everything anindicator()can — plots, shapes, drawings, tables, alerts.
Placing orders
strategy.entry(id, direction, qty?, when?, limit?, stop?, oca_name?, oca_type?, alert_message?)
if ta.crossover(close, ta.sma(close, 20))
strategy.entry("Long", strategy.long)
if ta.crossunder(close, ta.sma(close, 20))
strategy.close("Long")
direction:strategy.long/strategy.short.qtyomitted → the declaration'sdefault_qty_value/typeapply.limit=/stop=turn the entry into a working order that fills intrabar when the price touches the level.oca_name=groups working orders one-cancels-all;oca_type=isstrategy.oca.cancel,strategy.oca.reduce, orstrategy.oca.none.alert_message=is forwarded verbatim when the fill triggers an alert.
strategy.exit(id, from_entry, stop?, limit?, trail_points?, trail_price?, trail_offset?, profit?, loss?, qty?, qty_percent?, alert_message?)
Attach exit orders to an entry. All tick-count parameters (profit, loss, trail_points, trail_offset) are converted to price using the symbol's mintick.
strategy.exit("Long Exit", from_entry="Long",
stop = strategy.position_avg_price - 2 * ta.atr(14),
limit = strategy.position_avg_price + 3 * ta.atr(14),
trail_points = 10, trail_offset = 5)
stop/limitare prices.profit/lossare ticks from entry.trail_points/trail_offsetare ticks. A trailing stop requires BOTHtrail_points/trail_priceandtrail_offset; omitting the offset drops the trail (reference semantics).
strategy.close(id, when?, qty?, qty_percent?, alert_message?)
Market-closes the named entry.
strategy.close_all(alert_message?)
Market-closes every open position.
strategy.order(id, direction, qty?, when?, limit?, stop?, oca_name?, …)
Lower-level: directly places an order that nets FIFO against opposite open positions. Use strategy.order when strategy.entry's reversal + pyramiding semantics are not what you want.
strategy.cancel(id) / strategy.cancel_all()
Removes working orders.
Fill model
By default a strategy calculates once per bar and its market orders fill at the next bar's open; stop / limit / trailing orders are evaluated against each bar's OHLC. Five declaration flags refine this — all of them are enforced by the backtester and the live engine:
| Flag | Effect |
|---|---|
calc_on_every_tick=true |
The forming live bar recalculates on every price update, so orders can be placed and filled intrabar. Historical bars still calculate once per bar — add calc_on_every_history_tick=true (ChartScript extension) to replay history tick-by-tick too, so the backtest matches live behaviour. |
calc_on_order_fills=true |
After a fill, the script runs once more on the same bar. Use it when an exit must be placed from the updated strategy.position_size / strategy.position_avg_price without waiting for the next bar. |
use_bar_magnifier=true |
Fills of working orders are resolved against the lower-timeframe bars inside each chart bar (the actual price path) instead of the assumed open→high→low→close path. The backtester uses the finest timeframe it has data for and falls back to coarser ones; the report's header shows the timeframe(s) used, the number of intrabars, and how many chart bars they covered. Capped at 200,000 intrabars per run. |
fill_orders_on_standard_ohlc=true |
On non-standard charts (Heikin Ashi, Renko, Kagi, Line Break, Point & Figure, Range) orders fill at the underlying real OHLC prices rather than the synthetic bar's. |
margin_long / margin_short |
Percent of a position's value that equity must cover (100 = cash only, 50 = 2× leverage). An entry that cannot be funded is rejected (counted in the report); when equity no longer covers the requirement a margin call liquidates enough of the position to restore it (shown as Margin call exits, with the smallest account size that would have avoided every call). |
slippage (ticks) worsens every market and stop fill; commission_type / commission_value apply the commission model to each fill and to all strategy.* account values.
Order fill timing
Set by process_orders_on_close:
| Setting | Fill |
|---|---|
process_orders_on_close = false (default) |
Market orders queue on the signal bar and fill at the next bar's open (platform default). |
process_orders_on_close = true |
Market orders fill at the signal bar's close. |
strategy.exit orders (stop / limit / trailing) are always evaluated intrabar against each bar's OHLC — or against lower-timeframe bars with use_bar_magnifier. With calc_on_every_tick, market orders on the live bar fill at the current price of the tick that placed them.
strategy.risk.*
Risk limits are enforced per bar by the engine; a breach liquidates and may halt the strategy for the rest of the run or the trading day.
strategy.risk.allow_entry_in(strategy.direction.long)
strategy.risk.max_drawdown(10, strategy.percent_of_equity)
strategy.risk.max_position_size(5)
strategy.risk.max_intraday_loss(1000, strategy.cash)
strategy.risk.max_cons_loss_days(3)
strategy.risk.max_intraday_filled_orders(10)
| Rule | Enforcement |
|---|---|
allow_entry_in(direction) |
Blocks disallowed-direction entries. Reversal closes are still permitted. |
max_position_size(n) |
Clamps entry size on every fill path. |
max_drawdown(v, type) |
type=strategy.percent_of_equity → percent of peak equity; strategy.cash → points. Breach liquidates + halts. |
max_intraday_loss(v, type) |
Day-scoped halt. Resumes next calendar day. |
max_cons_loss_days(n) |
Scored at UTC day rollover; halts after n consecutive losing days. |
max_intraday_filled_orders(n) |
Per-day cap on filled orders. |
Both positional and reference named forms (value=, type=, count=, entry_direction=) are accepted.
Account values
strategy.* series expose the running account state (net of the declared commission model):
| Name | Meaning |
|---|---|
strategy.position_size |
signed; positive = long, negative = short |
strategy.position_avg_price |
average entry price (or na) |
strategy.equity |
marked-to-market balance |
strategy.openprofit |
unrealised P&L on the current position |
strategy.netprofit |
cumulative realised P&L |
strategy.max_drawdown / max_runup |
running peak-to-trough / trough-to-peak |
strategy.wintrades / losstrades / eventrades |
counters |
strategy.closedtrades / opentrades |
counts |
Trade accessors (strategy.closedtrades.* / strategy.opentrades.*): entry_time, exit_time, entry_price, exit_price, profit, size, max_runup, max_drawdown.
Backtest report
Running "Backtest" from the editor or the Strategy Tester runs the strategy over the chart's symbol, timeframe and chart type (Heikin Ashi etc. are honoured) in an isolated worker and shows the report in the Account Manager (bottom panel) as a strategy account tab:
- Overview — equity, drawdown and buy-and-hold curves over the tested range (deep histories are decimated to 6,000 points for drawing; metrics use every bar)
- Performance — net / gross profit, profit factor, max drawdown and run-up, Sharpe and Sortino (from monthly returns), CAGR, buy-and-hold return, commission paid, margin calls and liquidated quantity, entries rejected for insufficient funds, smallest account size without a margin call
- Trades analysis — counts, win rate, averages, largest win / loss, consecutive wins / losses, average bars held
- List of trades — every trade with entry / exit, size, profit, run-up / drawdown and margin; exportable to XLSX with a column chooser
- Settings — the effective declaration + overrides the run used (capital, sizing, slippage, commission, fill-model flags, magnifier tiers)
The Strategy Tester lets you re-run with a different period (default 5,000 bars, or a date window on deep history), initial capital and commission without editing the script; pyramiding and slippage can be overridden from Properties.
Alerts
See Alerts. alert_message= on orders rides the fill through to the alert channel; durable alert rules with trigger = order_fill POST the message to a webhook.
See also
- Declaration statements
- Alerts
- Repainting — strategy fill-timing nuances