millionchart
فارسی

Samples: strategies

Sample strategies to copy, put on the chart and test in the Strategy Lab. Crossovers, reversion, breakouts, stops and targets, sessions and risk rules.

Each program on this page is a complete strategy: copy it into a new script, press Save and Add to chart to see its simulated trades on the chart, or press Test to run it in the Strategy Lab. Every program is run on real bars each time this guide is built.

Moving average crossover#

Buy when a fast average crosses above a slow one, and close the trade when it crosses back.

Cuprel
// A crossover strategy: buy when the fast average crosses above the slow one, sell the position when it crosses back.
// The tester fills the orders on the synthetic data; costs are set in the setup block.
strategy "Moving average crossover"
    edition = 1
    display = price

setup:
    capital = 10000
    size = sizing.lots(0.1)

input fast_length: int = 10, name = "Fast length", min = 1
input slow_length: int = 30, name = "Slow length", min = 2
let fast = tech.ema(close, fast_length)
let slow = tech.ema(close, slow_length)
if tech.crosses_above(fast, slow)
    orders.enter("Long", side.long)
if tech.crosses_below(fast, slow)
    orders.close("Long")
plot(fast, name = "Fast", color = color.green)
plot(slow, name = "Slow", color = color.red)

What it teaches: setup: with a capital and a size, orders.enter and orders.close with the same key.

RSI reversion#

Buy when the RSI falls below the oversold level, and leave when it has recovered above a middle level.

Cuprel
// Mean reversion: enter long when the RSI falls below the oversold level, leave when it recovers above the middle.
strategy "RSI reversion"
    edition = 1
    display = price

setup:
    capital = 10000
    size = sizing.lots(0.1)

input length: int = 14, name = "RSI length", min = 2
input oversold: float = 30, name = "Oversold"
input exit_level: float = 55, name = "Exit level"
let value = tech.rsi(close, length)
if tech.crosses_below(value, oversold)
    orders.enter("Long", side.long)
if value > exit_level
    orders.close("Long")
plot(close, name = "Close", color = color.gray)

What it teaches: an entry and an exit on two different conditions of one value.

Donchian breakout#

A stop order just above the highest high of the last bars, renewed on every closed bar, and an exit under the lowest low of a shorter window.

Cuprel
// A channel breakout: a stop order one tick above the highest high of the last `length` bars, an exit under the lowest low of a shorter window.
strategy "Donchian breakout"
    edition = 1
    display = price

setup:
    capital = 10000
    size = sizing.lots(0.1)

input entry_length: int = 20, name = "Entry length", min = 2
input exit_length: int = 10, name = "Exit length", min = 2
let entry_level = tech.highest(high, entry_length)[1]
let exit_level = tech.lowest(low, exit_length)[1]
if bar.is_closed
    orders.enter("Breakout", side.long, stop = entry_level, expires = 1)
    orders.exit("Leave", "Breakout", stop = exit_level)
plot(entry_level, name = "Entry level", color = color.green)
plot(exit_level, name = "Exit level", color = color.red)

What it teaches: a pending stop entry with expires = 1, orders.exit with a stop, and bar.is_closed.

ATR stop and target#

An entry in an up trend, with a stop and a target measured in multiples of the average true range at the time of entry.

Cuprel
// A position with a protective stop and a target measured in multiples of the Average True Range at the time of entry.
strategy "ATR stop and target"
    edition = 1
    display = price

setup:
    capital = 10000
    size = sizing.lots(0.1)

input length: int = 14, name = "ATR length", min = 2
input stop_mult: float = 2.0, name = "Stop multiple", min = 0.5, step = 0.5
input target_mult: float = 3.0, name = "Target multiple", min = 0.5, step = 0.5
let range = tech.atr(length)
let trend_up = tech.ema(close, 20) > tech.ema(close, 50)
if tech.crosses_above(close, tech.ema(close, 20)) and trend_up and trades.open_count == 0
    orders.enter("Long", side.long)
    orders.exit(
        "Long exit",
        "Long",
        stop = close - stop_mult * range,
        limit = close + target_mult * range,
    )
plot(tech.ema(close, 20), name = "EMA 20", color = color.orange)

What it teaches: orders.exit with a stop and a limit placed together with the entry, and trades.open_count to stay in one trade at a time.

Risk guards#

A simple crossover of the price and its average, with two rules that stop new entries: at most two entries a day, and none after three losing days in a row.

Cuprel
// Rules that stop trading when a limit is reached: no more than two entries a day, no new entries after three losing days in a row.
strategy "Risk guards"
    edition = 1
    display = price

setup:
    capital = 10000
    size = sizing.lots(0.1)

input length: int = 20, name = "Length", min = 2
risk.max_daily_orders(2)
risk.max_losing_days(3)
let basis = tech.sma(close, length)
if tech.crosses_above(close, basis)
    orders.enter("Long", side.long)
if tech.crosses_below(close, basis)
    orders.close("Long")
plot(basis, name = "Basis", color = color.orange)

What it teaches: risk.max_daily_orders and risk.max_losing_days. See the risk reference for the other rules.

Opening range breakout#

The high and low of the first hour of the London session, a trade in the direction of the first close outside that range, and every trade closed when the session ends.

Cuprel
// Buy a close above the first hour's high of the London session, sell a close below its low: one trade a day at most,
// and every trade is closed when the session ends.
strategy "Opening range breakout"
    edition = 1
    display = price

setup:
    capital = 10000
    size = sizing.lots(0.1)

session opening = 08:00 .. 09:00 @ "Europe/London"
session london = 08:00 .. 16:30 @ "Europe/London"
state top: float = na
state bottom: float = na
state traded = false
if opening.start
    top = high
    bottom = low
    traded = false
else if opening.active
    top = math.max(top, high)
    bottom = math.min(bottom, low)
let crossed_up = tech.crosses_above(close, top)
let crossed_down = tech.crosses_below(close, bottom)
let window = london.active and not opening.active
if window and not traded and crossed_up
    orders.enter("Long", side.long)
    traded = true
if window and not traded and crossed_down
    orders.enter("Short", side.short)
    traded = true
if london.end
    orders.close_all()
plot(if window then top else na, name = "Range high", color = color.green, style = step_break)
plot(if window then bottom else na, name = "Range low", color = color.red, style = step_break)

What it teaches: two named sessions, state values that remember the range and whether the day has traded, long and short entries, and orders.close_all at the end of the session. The crossings are worked out on lines of their own before they are combined with other conditions.

Inside bar breakout#

After an inside bar, a stop order waits above the high of the bar before it for three bars. The exit is a target and a stop in ticks from the entry price.

Cuprel
// After an inside bar, a stop order waits above the high of the bar before it for three bars.
// The exit is written in ticks from the entry price: a target and a stop.
strategy "Inside bar breakout"
    edition = 1
    display = price

setup:
    capital = 10000
    size = sizing.lots(0.1)

input target_ticks: int = 300, name = "Target (ticks)", min = 10
input stop_ticks: int = 150, name = "Stop (ticks)", min = 10
let inside = high < high[1] and low > low[1]
if inside and position.size == 0
    orders.enter("Break", side.long, stop = high[1], expires = 3)
orders.exit("Exit", "Break", profit_ticks = target_ticks, loss_ticks = stop_ticks)
mark.char(inside, name = "Inside bar", char = "i", place = above, color = color.gray)

What it teaches: expires on a pending order, and orders.exit with profit_ticks and loss_ticks instead of prices.

Trend pullback#

Buy a short dip inside an up trend, protect the trade with a stop two ATRs under the entry, and close a trade that lasts too long.

Cuprel
// Buy a short dip inside an up trend: the close is above the long average and a fast RSI is low.
// A stop two ATRs under the entry protects the trade, and a trade that lasts too long is closed.
strategy "Trend pullback"
    edition = 1
    display = price

setup:
    capital = 10000
    size = sizing.lots(0.1)
    fees = fee.per_lot(3.5)

execution:
    spread = 20 ticks
    slippage = 2 ticks

input trend_length: int = 200, name = "Trend length", min = 20
input rsi_length: int = 5, name = "RSI length", min = 2
input dip: float = 20, name = "Dip level"
input max_bars: int = 48, name = "Most bars in a trade", min = 1
let trend = tech.ema(close, trend_length)
let rsi = tech.rsi(close, rsi_length)
let atr = tech.atr(14)
state entry_bar = 0
if close > trend and rsi < dip and position.size == 0
    orders.enter("Dip", side.long)
    entry_bar = bar.index
if position.size > 0
    orders.exit("Protect", "Dip", stop = position.avg_price - 2 * atr)
    if bar.index - entry_bar >= max_bars
        orders.close("Dip")
plot(trend, name = "Trend", color = color.gray)

What it teaches: costs in setup: and execution:, a protective stop that follows the entry price, and a time exit counted in bars with a state value.

Band reversion, both sides#

Buy when the close falls under the lower band, sell short when it rises over the upper band, and close each side at the middle line.

Cuprel
// Trade both sides of a band: buy when the close falls under the lower band, sell short when it rises over the upper one,
// and close each side when the close comes back to the middle line.
strategy "Band reversion, both sides"
    edition = 1
    display = price

setup:
    capital = 10000
    size = sizing.lots(0.1)

input length: int = 20, name = "Length", min = 2
input mult: float = 2.0, name = "Deviations", min = 0.5, step = 0.1
let basis = tech.sma(close, length)
let dev = mult * tech.stdev(close, length)
let upper = basis + dev
let lower = basis - dev
let below = tech.crosses_below(close, lower)
let above = tech.crosses_above(close, upper)
let back_up = tech.crosses_above(close, basis)
let back_down = tech.crosses_below(close, basis)
if below
    orders.enter("Long", side.long)
if above
    orders.enter("Short", side.short)
if back_up
    orders.close("Long")
if back_down
    orders.close("Short")
plot(upper, name = "Upper", color = color.blue)
plot(lower, name = "Lower", color = color.blue)
plot(basis, name = "Basis", color = color.orange)

What it teaches: side.short, two entries with their own keys, and closing each by its key.

See also: Strategies and the header, Lesson 14: Your first strategy.