Appearance
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.
| Argument | Default | What it sets |
|---|---|---|
initial_capital | 1,000,000 | The money the account starts with |
default_qty_type | strategy.fixed | How an entry with no qty is sized: strategy.fixed (units), strategy.percent_of_equity or strategy.cash |
default_qty_value | 1 | The size in that unit |
commission_type, commission_value | percent, 0 | What each fill pays: strategy.commission.percent, strategy.commission.cash_per_order or strategy.commission.cash_per_contract |
slippage | 0 | Ticks a market order, a stop or a trail fills against you |
pyramiding | one entry a direction | How many entries in one direction may be open at once |
process_orders_on_close | false | Fill market orders at the close that placed them |
margin_long, margin_short | 100 | The percent of a position's value the account must hold |
calc_on_every_tick | false | Run 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:
| View | Shows |
|---|---|
| Equity | The account's equity bar by bar beside buy and hold, with the drawdown beneath |
| Metrics | The 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 |
| Monthly | Each month's return |
| Trades | Every 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_closeon 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'sif. Work values out before the branch. Ata.*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_steprounds 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
- How a strategy fills, every rule of the emulator
- strategy.entry, strategy.exit, strategy.close
- Inputs, to tune lengths and levels from the settings
- Alerts, to hear when the strategy acts