Skip to content

Strategies ​

How do you turn a trading idea into a backtest? Declare the script with strategy() instead of indicator(), open positions with strategy.entry() and leave them with strategy.exit() or strategy.close(). The engine fills each order on the prices that come after it, as TradingView's broker emulator does, and the Strategy Tester shows the equity curve, the trades and the report.

pine
//@version=6
strategy("SMA cross with a bracket", overlay = true,
     initial_capital = 10000,
     default_qty_type = strategy.percent_of_equity, default_qty_value = 90,
     commission_type = strategy.commission.percent, commission_value = 0.1)
int fastLen = input.int(10, "Fast")
int slowLen = input.int(30, "Slow")
float fast = ta.sma(close, fastLen)
float slow = ta.sma(close, slowLen)
plot(fast, "Fast", color = color.teal)
plot(slow, "Slow", color = color.orange)
if ta.crossover(fast, slow)
    // placed at this close, filled at the next bar's open
    strategy.entry("Long", strategy.long)
    // a stop 3% under, a target 6% over; waits for the entry to fill
    strategy.exit("Bracket", "Long", stop = close * 0.97,
         limit = close * 1.06)
if ta.crossunder(fast, slow)
    strategy.close("Long")

Add the script to a chart and open the Strategy Tester, with the TESTER command, to read it. The rest of this page walks through the example one decision at a time. Each rule gets a sentence here, and How a strategy fills has it in full.

Declare the account ​

strategy() takes what indicator() takes, plus the account the backtest trades. Every argument is optional.

ArgumentDefaultWhat it sets
initial_capital1,000,000The money the account starts with
default_qty_typestrategy.fixedHow an entry with no qty is sized: strategy.fixed (units), strategy.percent_of_equity or strategy.cash
default_qty_value1The size in that unit
commission_type, commission_valuepercent, 0What each fill pays: strategy.commission.percent, strategy.commission.cash_per_order or strategy.commission.cash_per_contract
slippage0Ticks a market order, a stop or a trail fills against you
pyramidingone entry a directionHow many entries in one direction may be open at once
process_orders_on_closefalseFill market orders at the close that placed them
margin_long, margin_short100The percent of a position's value the account must hold
calc_on_every_tickfalseRun on every update of a live bar, not once at its close

The full list is on the declarations page.

Enter ​

strategy.entry(id, direction) asks for a position. With no limit or stop it's a market order. With a limit it waits for a price at or better than that one, and with a stop for a price at or past that one.

The id names the entry, and exits refer to it. An entry in the direction opposite to an open position closes that position and opens the new one in one order, so strategy.entry("Short", strategy.short) while long reverses the trade. See entries that face a position.

Exit ​

strategy.close(id) closes the entry with that id by a market order, and strategy.close_all() closes everything. strategy.exit(id, from_entry, ...) places a resting exit: a limit to take profit, a stop to cut a loss, or both as a bracket, with profit and loss in ticks as another way to say them. A trail_points and trail_offset pair makes the stop follow the price.

An exit placed together with its entry waits for that entry to fill and is live from the fill on. When one bar reaches both the stop and the limit, the exit fills at whichever the bar's assumed path meets first, which isn't always the better one. See exits and closes and the path inside the bar.

When an order fills ​

Your script runs once per bar, at its close, so an order it places there can't fill on that bar: the bar is over. A market order fills at the next bar's open. A limit or a stop rests until a later bar's prices reach it. See at the open.

process_orders_on_close = true fills a market order at the close that placed it instead. Use it when the strategy really trades at the close, such as a daily system that orders in the closing auction. On other strategies it fills at a price that was gone by the time the order existed.

Read the position a bar late ​

strategy.position_size and the other strategy.* figures read every fill up to this close. An order placed on this close isn't one of them yet, so strategy.position_size reads the new position on the bar after the one that placed the order. That's also true with process_orders_on_close, since that fill comes after your script has run on the bar.

So on the bar that places an entry, strategy.position_size still reads 0, and any code below the entry that tests it sees no position yet. Keep your own var record of what you ordered if a later line needs to know. See when your script runs.

Size the orders ​

An entry's qty overrides default_qty_type for that order. strategy.percent_of_equity and strategy.cash take their size at the close that places the order: the money divided by that close's price. A share, a fund or a future trades in whole units, and a coin in small steps. See order sizes.

100% of equity doesn't always fill

An order sized at 100% of equity takes its size at the close that placed it and fills at the next open. If that open is higher, the order costs more than the account holds, and at the default margin of 100 it's refused whole: the Tester's warnings say rejected at fill … needs more than the account has at 100% margin, and that trade never happens. When it does fill, its commission leaves the equity a little under the position's value, so small Margin call exits sell slivers of it. Size below 100, as the example's 90 does, or lower margin_long.

Pay for trading ​

commission_value is charged on every fill, in the unit commission_type names. slippage moves each market order, stop and trail that many ticks against you, and never a limit. See commission and slippage.

Slippage is in ticks, not percent, so its weight depends on the price. Two ticks of 0.01 is nothing on a price of 45,000 and a third of a price of 0.06, which a coin's earliest years have.

Read the Strategy Tester ​

The Tester reads the strategy on the chart it's on. Its views:

ViewShows
EquityThe account's equity bar by bar beside buy and hold, with the drawdown beneath
MetricsThe report: net profit from closed trades, total P&L with what's still open added, the trade counts, profit factor, drawdown, Sharpe, Sortino and the rest
MonthlyEach month's return
TradesEvery closed trade: entry and exit, size, profit, commission, and how far it ran for and against you

The report counts closed trades, as TradingView's does. A position still open when the data ends shows apart, as open profit, and only Total PnL adds it in. Ratios such as Sharpe read calendar months of closed equity, as TradingView reads them. See money and the report.

On a live bar ​

On history the script runs once per bar, at the close. On a live bar, a strategy with calc_on_every_tick = false, the default, also runs once, when the bar closes, so its signals don't change while the bar forms. With calc_on_every_tick = true it runs on every update, and a market order placed on one update fills at the next update's price. When the chart runs the strategy whole again, as it does after a reload, those fills move to where history fills them, at the next bar's open, so the past trades a live chart showed can change.

A leak-safe AmenoScript run refuses calc_on_every_tick and calc_on_order_fills, since history can't replay either. See leak-safe runs.

Traps ​

  • Repainting with calc_on_every_tick. Trades taken on a live bar's updates move once the strategy runs whole. Leave it off to backtest what you can trade.
  • process_orders_on_close on a strategy that doesn't trade the close. Each fill is at a close you couldn't have traded once your order existed, so the backtest is better than the market allows.
  • A stop and a target on one bar. The exit takes the level the bar's assumed path meets first, which can be the stop.
  • ta.* inside the entry's if. Work values out before the branch. A ta.* call inside it runs only on the bars that reach it. See state in branches.
  • An account that goes negative. A percent-of-equity size on an account with no equity left stops the run with Invalid qty value. Large slippage on a cheap price is the usual cause.

AmenoScript additions ​

Two strategy() arguments exist only in AmenoScript, so a Pine script can't use them:

  • qty_step rounds every size down to a multiple of it, in place of the symbol's own step.
  • close_on_end_of_data, true unless you say so, lists what is still open at the last bar's close as a closed trade in Trades. The report leaves it out of the closed trades all the same, as above.

A strategy trades one symbol. To trade a list of names by target weights, use a book instead.

See also ​

AmenoScript is compatible with Pine Script v6. Pine Script is a trademark of TradingView, Inc., which has no part in AmenoScript.