Appearance
How a strategy fills
When does a strategy's order fill, and at what price? An order your script places at a bar's close acts from the next bar. A market order fills at the next bar's open. A limit or a stop fills where the bar's assumed path first touches its price, and an exit fills on whichever of its levels that path reaches first. With process_orders_on_close = true, a market order fills at the close that placed it instead.
This engine follows TradingView's broker emulator rule for rule, and where TradingView's own trades settled a rule, this engine matches them trade for trade. The few places where the two still part are listed at the end. The rules below follow one bar in order: the open, the path inside the bar, the close, then what carries to the next bar. A lot is what one entry's fill opened: a position is one or more lots on the same side.
pine
//@version=6
strategy("A bracket on every entry", overlay = true)
if ta.crossover(ta.sma(close, 10), ta.sma(close, 30))
strategy.entry("Long", strategy.long)
// A bar that touches both levels exits at whichever
// its assumed path reaches first.
strategy.exit("Bracket", "Long",
stop = strategy.position_avg_price * 0.97,
limit = strategy.position_avg_price * 1.05)
plot(strategy.position_size, "Position", display = display.data_window)When your script runs
On history, the script runs once per bar, at the bar's close. So when it reads strategy.position_size, it sees every fill up to this close. An order it places now can't fill on the bar that's already over, so a market entry shows in strategy.position_size one bar after the bar that placed it.
On a live bar, a strategy with calc_on_every_tick = false, the default, runs once, when the bar closes. With calc_on_every_tick = true, it runs on each update, and a market order placed on one update fills at the next update's price. A whole run recomputes from history, so those fills move to the next bar's open, as they do on TradingView. Leak-safe AmenoScript refuses calc_on_every_tick and calc_on_order_fills, since history can't replay either.
At the open
The bar starts with the orders the last close left. Every market order fills at the open, moved by any slippage. Then the bar's path begins.
An order sized in money, strategy.percent_of_equity or strategy.cash, took its size at the close that placed it: the money over that close rounded to the tick, the symbol's smallest price step (see prices on the tick). A market order spends that size at the open, whatever the open is. A limit or a stop that rests spends its money at its own fill price, except a stop that the open gaps through, which keeps the size it was placed with.
A new day also starts here, for the rules that count by day. The day's high-water mark for strategy.risk.max_intraday_loss starts from equity at the first open, after what fills there.
The path inside the bar
A backtest has only four prices for a bar, so the emulator assumes how the price moved between them. If the open is nearer the high, the price goes open, high, low, close. Otherwise it goes open, low, high, close, and that includes a tie, where the open sits midway.
Resting orders fill on a touch, in the order the path meets them:
- A limit fills when the path reaches its price, at its price. If the open is already past it, it fills at the open.
- A stop fills when the path reaches its price, at its price plus slippage. If the open gaps through it, it fills at the open plus slippage.
- A stop-limit waits for its stop. Once the path trades the stop, the order becomes a limit, and it fills where the rest of the path reaches the limit. If the price at the stop already satisfies the limit, it fills there.
- An exit with both a stop and a limit fills on whichever of the two the path reaches first.
A bar that touches both levels
When one bar's range holds both your stop and your target, the exit fills at the one the assumed path meets first, not at the better one. On a bar that opens nearer its low, that is the low side.
Two rules keep fills honest. An order fills only on prices that came after it existed, so an order placed at a close can't fill on the bar that's over. And an exit fills only on prices after its position existed. An exit on a market entry can fill anywhere on the entry's bar, since that entry filled at the open. An exit on a limit or stop entry walks only the part of the path after the entry's fill.
Trailing stops
strategy.exit with trail_points or trail_price and trail_offset arms a trail. Once the price has moved trail_points ticks in your favour, or past trail_price, the stop follows the best price at trail_offset ticks behind it.
The trail walks the same path as everything else. Its best price starts from the entry's fill, or from the close that set the trail when that came later, and calling the same exit again on later bars doesn't reset it. The best price follows the bar's own prices, off the tick, and the stop fills with slippage. Each exit id on a lot holds its own trail, and strategy.cancel with that id takes it away.
At the close
Your script runs on the closing price. What it reads of strategy.* includes every fill so far, this bar's included. What it places, moves or cancels takes effect from the next bar.
Then, with process_orders_on_close = true, the market orders placed on this close fill at this close, with slippage. Orders with a price, limits and stops, still wait for the next bar. Last, the emulator marks equity at the close.
A lot that fills at the close counts that close, on the tick, toward its own run-up and drawdown. With slippage, the fill sits a tick or more past the close, and TradingView counts that difference.
Prices on the tick
Every price the emulator walks sits on the symbol's tick, syminfo.mintick. A bar's four prices are rounded to the tick, and so is every fill. A resting level goes on the tick on the side against your order: a buy stop and a sell limit round up, a buy limit and a sell stop round down. So on a tick of 0.01, a buy stop at 0.241 rests at 0.25, a sell stop at 0.189 rests at 0.18, and a bar whose high printed 0.2468 reads 0.25, high enough to fill that buy stop.
Equity marks what's open on the tick too, in strategy.equity and in the equity curve. A trail's best price, the buy and hold figure and your script all read the bar as it is, not rounded.
Slippage
slippage in strategy() is a number of ticks. It moves a market order, a stop and a trailing stop against you, by that many ticks. It never moves a limit, whether the path reached the limit or the open gapped through it. A stop-limit fills as a limit, so it takes no slippage either.
Order sizes
strategy.entry and strategy.order take qty as a number of units. Leave it out, or pass na, and strategy()'s default_qty_type and default_qty_value size the order: a fixed count, a cash amount, or a percent of equity. A qty of 0 places nothing.
A size is floored to the size step. Shares, funds, depositary receipts, futures and forex pairs trade in whole units, as on TradingView, and an explicit qty is floored too. A crypto symbol, or a run with no symbol, steps in millionths. qty_step in strategy() sets the step yourself. A listed future's point value sizes its orders, margin, commission and profit, so 3 contracts of ES are worth 3 × 50 × the price.
A size under 0 or over 1e12 stops the script with an error at the call, before any other rule.
Pyramiding. pyramiding = 0 or 1 allows one entry per direction, and N allows N. The count is judged where the order is placed, against what the market orders queued ahead of it will leave, and again at the fill for a market entry only. So two stop entries placed on one close can both fill. strategy.order takes no pyramiding check at all.
Entries that face a position
An entry in the opposite direction to what you hold reverses the position: it closes what's open and opens its own size the other way. A market entry reverses whatever it finds when it fills. An entry with a price keeps the size it was placed with, so one that finds its position already gone opens both parts.
strategy.order is different. It nets against what's open: a sell order of 5 against a long of 3 closes the 3 and opens a short of 2. When strategy.risk.allow_entry_in forbids a side, an entry for that side still closes what it faces and opens nothing.
Exits and closes
strategy.exit's size belongs to the order, not to each lot. Each exit on an entry takes its size from what the exits before it already reserved, a trail's included.
A second exit with no size never fills
The first strategy.exit with no qty or qty_percent already holds the whole entry, so a second one on the same entry never acts. Give each exit a share to split a position between them.
pine
//@version=6
strategy("Two exits on one entry", overlay = true)
if ta.crossover(close, ta.sma(close, 20)) and strategy.position_size == 0
strategy.entry("Long", strategy.long, qty = 2)
// Half at the target, half on the stop.
// Without qty_percent, the second exit would never fill.
strategy.exit("Target", "Long", qty_percent = 50,
limit = strategy.position_avg_price * 1.04)
strategy.exit("Stop", "Long", qty_percent = 50,
stop = strategy.position_avg_price * 0.98)An exit with no size fires per lot, on each lot's own levels, measured from that lot's entry price. qty_percent is a share of what that lot filled, whatever other exits have closed of it since.
Which lot closes. By default, an exit or a close takes the oldest lot first, whichever entry it names, as TradingView does. close_entries_rule = "ANY" in strategy() closes the lots the order names instead. Once an exit id has filled, it is spent on the lot it named, and what it took from older lots counts against that lot's other exits.
strategy.close and strategy.close_all fill at the next open as market orders, or at this close with immediately = true. A strategy.close with nothing of that id open, and a strategy.close_all when flat, do nothing where they are called, so an entry placed just before them stands. A close's order id defaults to Close entry(s) order <id>.
OCA groups. Entries that share an oca_name form a group. With strategy.oca.cancel, the first fill cancels the rest of the group. With strategy.oca.reduce, each fill reduces the others by its size. A fill marks its group, and the group's other orders drop at the end of that pass through the queue.
Commission
commission_type and commission_value in strategy() set the cost: a percent of each fill's value, a cash amount per contract, or a cash amount per order. A cash amount per order is charged once per order and shared by size among the lots it closes and what it opens. Net profit includes the entry commission of lots still open, and strategy.openprofit doesn't.
Margin
margin_long and margin_short are the percent of a position's value your equity must cover. Pine v6's default is 100, so a long can be as large as your equity and no larger. 0 turns the check off.
The emulator checks margin at each point of the path. When equity no longer covers what a position needs, a margin call liquidates four times the shortfall's size, floored to the size step, at that point and with slippage, under the exit id Margin call. When the shortfall is smaller than one size step, the call sells one unit, or all that's held if that's less. A call that still leaves the account short calls once more at the next point.
An entry that can't be margined at the close that placed it isn't placed when it faces nothing. When it faces a position, it closes that position and opens nothing. A market order past the room at its open is refused whole. Equity here includes the entry commission of the lots open, so a position sized at the whole of equity can take a tiny margin call at its own fill.
Risk rules
The strategy.risk.* functions set limits the emulator enforces. Call them anywhere at the top level of the script, usually once.
allow_entry_inlimits entries to one side. An entry on the other side closes what it faces and opens nothing.max_position_sizecaps the units held in one direction. An entry past the cap is cut to the room left, and refused when none is.max_intraday_filled_ordersstops the day once that many orders have filled, exits included: it cancels every order, closes what's open and refuses entries until the next day.max_intraday_losschecks the day's loss from its high-water mark at every point of the path. The mark starts at the day's first open and rises at each close. When the loss reaches the limit, the emulator closes everything there, asClose Position (Max intraday Loss), cancels every order and stops the day.max_drawdownandmax_cons_loss_daysstop trading for the rest of the run once reached: every order is cancelled and what's open closes at the next open.
A day is a UTC calendar day here. TradingView counts the symbol's trading day, so on a symbol whose session doesn't start at midnight UTC, such as a US share, a day's limits reset at another time.
Money and the report
Money follows TradingView's own arithmetic: each amount is held to ten significant digits, and to the unit from ten billion. That matters only when you compare a figure to the last digit.
The end of the data. With close_on_end_of_data = true, the default, a position still open at the last bar closes at the last close, so the list of trades is complete. That close gets no arrow on the chart. The report itself counts closed trades only, as TradingView's does: what that final close took stays an open position in the report's figures, with its profit shown apart. AmenoScript can set close_on_end_of_data = false to leave it open in the trade list too.
Sharpe and Sortino read calendar months of closed equity, from the month of the first entry through the month of the last exit, with a position still open counted at the last price. risk_free_rate in strategy(), a percent a year, comes off first. Buy and hold starts from the first trade's fill, not the first bar, and buys a whole order: the initial capital over that fill, floored to the size step. Its percent is the price's own change.
Max run-up and max drawdown are TradingView's: how far capital plus net profit, with the open lots at their best so far, rose over its lowest point yet, and how far it fell under its highest, each read up to the point you ask. A trade's own run-up and drawdown run from its entry's fill to its exit's fill along the path.
Where this engine differs from TradingView
- About one in twenty of TradingView's in-bar fills that the margin rule would call on gets no margin call there, on the same days across strategies, and no rule found so far explains which. This engine calls on all of them.
- A few reversals at the open that TradingView refused, and that fit the equity by under 0.0001 here, fill here.
- With
calc_on_every_tickandprocess_orders_on_closetogether, a live update's fill at its own close drops at the next update here, where TradingView keeps it. strategy()'scalc_bars_countandmax_bars_backdo nothing: every bar runs.- The risk rules' day is a UTC calendar day, where TradingView's is the symbol's trading day.