Back to TradingView PineScript for CFD Traders
Part 4

Building a complete trend-following system

Putting every lesson together: a multi-timeframe, ATR-sized, alert-wired trend system you can run on any CFD instrument.

Advanced15 min readBeginner → AdvancedLesson 17 / 17
Try it in TradingView
The complete system — every lesson in one script

Daily regime filter + hourly entry + ATR sizing + bracket exit + JSON alerts. Walk it line by line: every block is something Parts 1–3 covered.

//@version=5
strategy("Trend-following system", overlay=true,
     initial_capital=10000,
     default_qty_type=strategy.fixed,
     default_qty_value=1,
     commission_type=strategy.commission.cash_per_contract,
     commission_value=0,
     slippage=2)

// ── Inputs ──────────────────────────────────────────────────────────
fastLen   = input.int(20, "Fast EMA", minval=5)
slowLen   = input.int(50, "Slow EMA", minval=10)
atrLen    = input.int(14, "ATR length", minval=5)
atrMultSL = input.float(2.0, "ATR stop x", minval=0.5, step=0.1)
atrMultTP = input.float(4.0, "ATR target x", minval=0.5, step=0.1)
riskPct   = input.float(1.0, "Risk % per trade", minval=0.1, step=0.1)

// ── Higher-timeframe regime filter (Part 4) ─────────────────────────
dailyEMA = request.security(syminfo.tickerid, "D", ta.ema(close, 200),
     lookahead=barmerge.lookahead_off)
longBias = close > dailyEMA

// ── Entry signal ────────────────────────────────────────────────────
fastMA    = ta.ema(close, fastLen)
slowMA    = ta.ema(close, slowLen)
entryLong = ta.crossover(fastMA, slowMA) and longBias

// ── Risk-based sizing (Part 3) ──────────────────────────────────────
atrVal    = ta.atr(atrLen)
stopDist  = math.max(atrVal * atrMultSL, syminfo.mintick * 10)
riskMoney = strategy.equity * riskPct / 100
contracts = riskMoney / (stopDist * syminfo.pointvalue)

// ── Orders (Part 3) ─────────────────────────────────────────────────
if entryLong
    strategy.entry("Long", strategy.long, qty=contracts)
    strategy.exit("Long TP/SL", "Long",
         stop  = close - stopDist,
         limit = close + atrVal * atrMultTP)
strategy.close("Long", when=ta.crossunder(fastMA, slowMA))

// ── Visuals ─────────────────────────────────────────────────────────
plot(fastMA,   "Fast EMA",      color=color.blue,   linewidth=2)
plot(slowMA,   "Slow EMA",      color=color.orange, linewidth=2)
plot(dailyEMA, "Daily EMA 200", color=color.gray,   linewidth=3)
plotshape(entryLong, "Entry", shape.triangleup, location.belowbar,
          color=color.green, size=size.small)

// ── Webhook-ready alerts (Part 4) ───────────────────────────────────
if strategy.position_size > 0 and strategy.position_size[1] == 0
    alert('{"action":"buy","ticker":"' + syminfo.ticker +
          '","qty":"'   + str.tostring(contracts) +
          '","stop":"'  + str.tostring(close - stopDist) + '"}',
          alert.freq_once_per_bar_close)
if strategy.position_size == 0 and strategy.position_size[1] > 0
    alert('{"action":"close","ticker":"' + syminfo.ticker + '"}',
          alert.freq_once_per_bar_close)
To run it
  1. 1. Open any chart in TradingView and click Pine Editor at the bottom.
  2. 2. Select everything in the editor and paste this over it, then click Save.
  3. 3. Click Add to chart. Open the Strategy Tester tab if it is a strategy.

The system

What we are building: long-only on CFD instruments, daily-trend filter, hourly entry, ATR-based stops, risk-per-trade sizing, alerts wired for webhook automation. Every block uses what Parts 1–4 covered.

//@version=5
strategy("Trend-Following v1", overlay=true,
     default_qty_type=strategy.cash, default_qty_value=0,
     initial_capital=10000, commission_type=strategy.commission.cash_per_contract,
     commission_value=0, slippage=2)

// ── Inputs ────────────────────────────────────────────────────────────
fastLen   = input.int(20, "Fast EMA", minval=5)
slowLen   = input.int(50, "Slow EMA", minval=10)
atrLen    = input.int(14, "ATR Length", minval=5)
atrMultSL = input.float(2.0, "ATR Stop Mult", minval=0.5, step=0.1)
atrMultTP = input.float(4.0, "ATR Target Mult", minval=0.5, step=0.1)
riskPct   = input.float(1.0, "Risk % per Trade", minval=0.1)

// ── Higher-timeframe trend filter ─────────────────────────────────────
dailyEMA  = request.security(syminfo.tickerid, "D", ta.ema(close, 200),
     lookahead=barmerge.lookahead_off)
longBias  = close > dailyEMA

// ── Entry signal ──────────────────────────────────────────────────────
fastMA    = ta.ema(close, fastLen)
slowMA    = ta.ema(close, slowLen)
entryLong = ta.crossover(fastMA, slowMA) and longBias

// ── Sizing ────────────────────────────────────────────────────────────
atrVal      = ta.atr(atrLen)
stopDist    = atrVal * atrMultSL
riskMoney   = strategy.equity * riskPct / 100
contracts   = riskMoney / (stopDist * syminfo.pointvalue)
contracts  := math.max(contracts, 0)

// ── Entries and exits ─────────────────────────────────────────────────
if entryLong
    strategy.entry("Long", strategy.long, qty=contracts)
    strategy.exit("X", "Long", stop=close - stopDist, limit=close + stopDist * (atrMultTP / atrMultSL))

// ── Visualisation ─────────────────────────────────────────────────────
plot(fastMA, color=color.new(color.blue, 60))
plot(slowMA, color=color.new(color.orange, 60))
plot(dailyEMA, color=color.new(color.gray, 70), linewidth=2)
plotshape(entryLong, "Entry", shape.triangleup, location.belowbar, color.green, size=size.small)

// ── Webhook-ready alert payload ───────────────────────────────────────
if strategy.position_size > 0 and strategy.position_size[1] == 0
    alert('{"action":"buy","ticker":"' + syminfo.ticker + '","qty":"' + str.tostring(contracts) + '","sl":"' + str.tostring(close - stopDist) + '","tp":"' + str.tostring(close + stopDist * (atrMultTP / atrMultSL)) + '"}', alert.freq_once_per_bar_close)
if strategy.position_size == 0 and strategy.position_size[1] > 0
    alert('{"action":"close","ticker":"' + syminfo.ticker + '"}', alert.freq_once_per_bar_close)

How it works

Long-only. The daily 200-EMA is the regime filter — no longs in a daily downtrend. Entries fire on the fast EMA crossing the slow EMA, but only when the daily trend agrees. Stops are 2x ATR from entry, targets are 4x ATR. Position size is set so that a stop-hit costs exactly 1% of equity. The alert payload is JSON, ready to be routed to a webhook.

What you should change before going live

Add slippage to the strategy declaration if your broker requotes. Add process_orders_on_close=false if you want to execute on the next bar's open instead of the close. Run the strategy through every market regime you can find in the data (2017 crypto mania, 2020 covid crash, 2022 rates shock) before trusting it on a live account.

What you now know

You have the full toolchain. Open the Pine Editor, paste this in, and walk through it line by line. Every line is something Parts 1–4 covered. The next strategies you write will be variations on this template.

What you just did

Lesson 17 of 17 in TradingView PineScript for CFD Traders. When you have run the examples or read the section, tick it off and move to the next lesson.