Skip to content
GetProfitable
Search

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_type and default_qty_value: how much each strategy.entry trades when no qty is given. strategy.fixed (units), strategy.cash (currency amount), or strategy.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_magnifier are 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.opentrades and strategy.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.entry submits a market order that fills at the next bar's open; opposite-direction entries reverse, strategy.close only flattens, and strategy.position_size tells 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=true and 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_close and calc_on_every_tick change that and need justification.
  • strategy.position_size and friends expose the emulator's state on every bar.
  • Use input.time and a window check to control the test period.

See it drawn

Original diagrams for the ideas on this page. Illustrative, not real market data.

How a position size is worked outAccount size, risk per trade and stop distance feed into one box giving the number of shares.ACCOUNT SIZE$25,000your capitalRISK PER TRADE1%of the accountSTOP DISTANCE$0.50entry to stopPOSITION SIZE500 sharesrisk budget: $25,000 × 1% = $250position size: $250 ÷ $0.50 = 500 shares
Working out a position size. Three numbers decide how big a trade is: the account, the share of it put at risk, and the distance from entry to stop. One percent of $25,000 is a $250 budget, and a $0.50 stop divides into that 500 times.
Slippage on a market orderA buy order clears four price levels, so the average price paid is worse than the price first quoted.Buy 1,000 shares at marketpricesell orders resting (bar length = size)20.04300 shares20.03200 shares20.01200 shares20.00300 sharesnothing resting at 20.02order sweeps up the bookaverage fill 20.02SLIPPAGE0.02 a share$20.00 in totalintended 20.00Each level fills at its own price; the average is what you really paid.
Slippage on a market order. You click at 20.00, but only 300 shares are resting there, so the rest of the order fills at 20.01, 20.03 and 20.04. The average price paid is 20.02, and that two-cent gap is slippage.
Margin and leverageA small deposit controlling a much larger position, and the point at which losses trigger a margin call.Position you controlnotional value $100,000your margin deposit: $5,000$100,000 / $5,000 = 20:1 leverageYour deposit absorbs every dollar of loss$5,000$2,500$0Equity leftMARGIN CALLequity has fallen to $2,5000%1%2%2.5%3%4%5%How far the price moves against you
Margin and leverage. A $5,000 deposit can control a $100,000 position, which is 20:1 leverage. Because the loss is measured on the full $100,000, a 2.5% move against you halves the deposit and brings a margin call, and a 5% move uses all of it.