strategy(), entries and exits
Lesson 16 · about 11 min
A strategy is an indicator that submits orders to TradingView's broker emulator. The emulator fills them against the chart's bars, tracks a position and equity, and produces the Strategy Tester report. The orders are simulated; nothing reaches a real broker unless you wire alerts to one. This lesson covers the declaration, the order functions and the fill rules; the next two cover stops, sizing and costs.
The declaration
//@version=6
strategy("EMA cross", shorttitle="EMAx", overlay=true,
initial_capital=10000,
default_qty_type=strategy.percent_of_equity, default_qty_value=10,
pyramiding=0)
initial_capital: starting equity in the account currency.default_qty_typeanddefault_qty_value: how much eachstrategy.entrytrades when noqtyis given.strategy.fixed(units),strategy.cash(currency amount), orstrategy.percent_of_equity(percent of current equity; 10 means 10%).pyramiding: the maximum number of entries in the same direction. Zero or one means a new long is ignored while already long.currency,margin_long,margin_short,commission_*,slippage,process_orders_on_close,calc_on_every_tick,use_bar_magnifierare covered in later lessons.
Entries
fastLen = input.int(9, "Fast EMA", minval=1)
slowLen = input.int(21, "Slow EMA", minval=1)
fast = ta.ema(close, fastLen)
slow = ta.ema(close, slowLen)
longCond = ta.crossover(fast, slow)
shortCond = ta.crossunder(fast, slow)
if longCond
strategy.entry("Long", strategy.long)
if shortCond
strategy.entry("Short", strategy.short)
plot(fast, "Fast", color=color.orange)
plot(slow, "Slow", color=color.blue)
strategy.entry(id, direction) submits a market order. The id is a label you use later to close or attach exits to; strategy.long and strategy.short are the direction. Optional arguments: qty to override the default size, limit and stop to make it a limit or stop entry instead of market, comment for the trade list, and alert_message for order-fill alerts.
Entries in the opposite direction reverse the position: if you are long 100 and call strategy.entry("Short", strategy.short) with a default size of 100, the emulator sells 200, closing the long and opening a short. That is often the intended behaviour for an always-in-the-market system and a surprise for everyone else. To only close, use strategy.close.
Exits without stops
strategy.close("Long") flattens the position with that entry ID at market. strategy.close_all() flattens everything. Both accept comment. For a long-only version of the cross:
if longCond
strategy.entry("Long", strategy.long)
if shortCond
strategy.close("Long", comment="Cross down")
When orders fill
Market orders submitted during bar N fill at the open of bar N+1. This is the emulator's default and the honest one: the condition used bar N's close, which was not known until the bar ended. The trade list shows the entry on the next bar and the entry price is that bar's open, which is usually not the price where the arrow appears.
Two settings change this. process_orders_on_close=true fills at bar N's close instead, which is reasonable on daily bars if you actually trade the closing auction and misleading otherwise. calc_on_every_tick=true evaluates the script on every live tick and can submit mid-bar, which the backtest cannot reproduce.
Limit and stop entries (strategy.entry("Long", strategy.long, limit=price)) rest in the emulator until a later bar's range touches the price, then fill at that price; if the fill price is inside a gap, the emulator fills at the open of the gapping bar.
Position state
Inside the script you can read the emulator's state on every bar:
strategy.position_size: units held, positive long, negative short, zero flat.strategy.position_avg_price: average entry price.strategy.opentradesandstrategy.closedtrades: counts.strategy.equity,strategy.netprofit,strategy.openprofit.
Guarding entries with strategy.position_size == 0 is the usual way to prevent re-entry while in a trade, independent of pyramiding.
Restricting the date range
startTime = input.time(timestamp("2020-01-01T00:00:00"), "Start")
endTime = input.time(timestamp("2030-01-01T00:00:00"), "End")
inWindow = time >= startTime and time <= endTime
if longCond and inWindow
strategy.entry("Long", strategy.long)
This is essential for module 7's out-of-sample testing and for comparing runs across the same period.
Key idea:
strategy.entrysubmits a market order that fills at the next bar's open; opposite-direction entries reverse,strategy.closeonly flattens, andstrategy.position_sizetells you where you stand on every bar.
Try it: Put the long-only EMA cross on a daily chart, open the Strategy Tester and read the first three trades in the "List of Trades" tab. Confirm that each entry date is one bar after its crossover. Then set
process_orders_on_close=trueand watch the entry prices change.
Recap
strategy()sets initial capital, default quantity type and value, and pyramiding; other cost settings come later.strategy.entry(id, direction)reverses an opposite position;strategy.close(id)only flattens.- Market orders fill at the next bar's open by default;
process_orders_on_closeandcalc_on_every_tickchange that and need justification. strategy.position_sizeand friends expose the emulator's state on every bar.- Use
input.timeand a window check to control the test period.
See it drawn
Original diagrams for the ideas on this page. Illustrative, not real market data.