This multi-timeframe strategy identifies trend direction using fast and slow exponential moving averages from a higher timeframe while seeking trade entries on a lower timeframe.
1Copy the scriptPress Copy script above, or the copy button on the code below.
2Open the Pine EditorOpen a TradingView chart and choose Pine Editor at the bottom of the screen.
3Paste over the templateCreate a new strategy, select all of the template text and paste. Press Save and give it a name.
4Add it to the chartPress Add to chart. The trades and results appear in the Strategy Tester tab; adjust the settings with the gear icon.
Source Code
339 lines of Pine Script v5 from Trend Surfer.pine, published under MIT. Keep the author's credit and licence lines when you reuse it.
Trend Surfer.pine
//@version=5
strategy("EMA Trend Surfer",
overlay=true,
initial_capital=50000,
default_qty_type=strategy.fixed,
default_qty_value=4,
commission_type=strategy.commission.cash_per_contract,
commission_value=0.34,
slippage=2,
process_orders_on_close=true,
calc_on_every_tick=false)
// ═══════════════════════════════════════════════════════════════════
// INPUTS
// ═══════════════════════════════════════════════════════════════════
// Symbol Selection
symbolType = input.string("AUTO", "Symbol Type", options=["AUTO", "MES", "MNQ"],
tooltip="AUTO will detect based on current symbol")
// EMA Settings
ema1_length = input.int(8, "Fast EMA Length", minval=1)
ema2_length = input.int(21, "Slow EMA Length", minval=1)
// Timeframes
htf_timeframe = input.timeframe("15", "Higher Timeframe (HTF)", tooltip="Used for trend direction")
ltf_timeframe = input.timeframe("1", "Lower Timeframe (LTF)", tooltip="Used for entries (should match chart)")
// Sideways/Chop Filters
min_ema_separation = input.float(0.1, "Min EMA Separation %", minval=0, step=0.05,
tooltip="Minimum % separation between HTF EMAs to take trades (filters sideways markets)")
use_adx_filter = input.bool(true, "Use ADX Filter", tooltip="Only trade when ADX indicates trending market")
adx_threshold = input.int(20, "ADX Threshold", minval=10, maxval=50, tooltip="Minimum ADX value to allow trades")
min_bars_between_trades = input.int(5, "Min Bars Between Trades", minval=1, tooltip="Cooldown period after exits to prevent overtrading")
// Trading Session
useSession = input.bool(true, "Use NY Session Only")
sessionTime = input.session("0930-1600", "Trading Session (ET)", tooltip="Regular NY session 9:30 AM - 4:00 PM ET")
// Position Management
partial_exit_pct = input.float(50, "Partial Exit %", minval=1, maxval=100, step=1) / 100
// Daily loss limit
enable_daily_limit = input.bool(true, "Enable Daily Loss Limit")
max_daily_losses = input.int(3, "Max Daily Losses", minval=0)
// Display Options
showHTFEMAs = input.bool(true, "Show HTF EMAs on Chart")
showLabels = input.bool(true, "Show Entry/Exit Labels")
showStats = input.bool(true, "Show Statistics Table")
// ═══════════════════════════════════════════════════════════════════
// SYMBOL DETECTION AND TICK VALUES
// ═══════════════════════════════════════════════════════════════════
// Auto-detect symbol type
detectedSymbol = symbolType == "AUTO" ?
(str.contains(syminfo.ticker, "MES") ? "MES" :
str.contains(syminfo.ticker, "MNQ") ? "MNQ" : "MES") :
symbolType
// Set tick value based on symbol
tickValue = detectedSymbol == "MES" ? 1.25 : 0.50 // MES = $1.25, MNQ = $0.50 per tick
// ═══════════════════════════════════════════════════════════════════
// SESSION FILTER
// ═══════════════════════════════════════════════════════════════════
// Check if we're in the trading session
inSession = not useSession or time(timeframe.period, sessionTime + ":1234567")
// -- Daily loss tracking
var int daily_losses = 0
var int prev_losstrades = strategy.losstrades
var int prev_day = dayofmonth(time)
// reset daily counter at midnight (new day)
curr_day = dayofmonth(time)
if curr_day != prev_day
daily_losses := 0
prev_day := curr_day
// detect newly closed losing trades and increment daily_losses
if strategy.losstrades > prev_losstrades
lost_inc = strategy.losstrades - prev_losstrades
daily_losses := daily_losses + lost_inc
prev_losstrades := strategy.losstrades
// ═══════════════════════════════════════════════════════════════════
// CALCULATE EMAS
// ═══════════════════════════════════════════════════════════════════
// Current timeframe (LTF) EMAs
ltf_ema1 = ta.ema(close, ema1_length)
ltf_ema2 = ta.ema(close, ema2_length)
// Higher timeframe (HTF) EMAs
htf_ema1 = request.security(syminfo.tickerid, htf_timeframe, ta.ema(close, ema1_length))
htf_ema2 = request.security(syminfo.tickerid, htf_timeframe, ta.ema(close, ema2_length))
htf_close = request.security(syminfo.tickerid, htf_timeframe, close)
// ═══════════════════════════════════════════════════════════════════
// TREND DETECTION
// ═══════════════════════════════════════════════════════════════════
// HTF Trend (15-min)
htf_bullish = htf_ema1 > htf_ema2 and htf_close > htf_ema1
htf_bearish = htf_ema1 < htf_ema2 and htf_close < htf_ema1
// LTF Trend (1-min) - EMAs must be aligned with HTF
ltf_bullish_aligned = ltf_ema1 > ltf_ema2
ltf_bearish_aligned = ltf_ema1 < ltf_ema2
// ═══════════════════════════════════════════════════════════════════
// CHOP/SIDEWAYS FILTERS
// ═══════════════════════════════════════════════════════════════════
// EMA Separation Filter - avoid trading when EMAs are too close (sideways market)
ema_separation_pct = math.abs(htf_ema1 - htf_ema2) / htf_ema2 * 100
ema_spread_ok = ema_separation_pct >= min_ema_separation
// ADX Filter - only trade when market is trending
// ta.dmi returns [diplus, diminus, adx]
[htf_diplus, htf_diminus, htf_adx] = request.security(syminfo.tickerid, htf_timeframe, ta.dmi(14, 14))
adx_ok = not use_adx_filter or htf_adx >= adx_threshold
// Cooldown Filter - prevent rapid re-entries after exits
var int last_trade_bar = 0
cooldown_ok = bar_index - last_trade_bar >= min_bars_between_trades
// ═══════════════════════════════════════════════════════════════════
// ENTRY CONDITIONS
// ═══════════════════════════════════════════════════════════════════
// Long Entry: Price closes below 8 EMA then closes back above it
// Must have: HTF bullish, LTF aligned, in session
dipped_below_ema1 = close[1] < ltf_ema1[1]
closed_above_ema1 = close > ltf_ema1
long_entry = htf_bullish and ltf_bullish_aligned and dipped_below_ema1 and closed_above_ema1 and inSession and strategy.position_size == 0 and (not enable_daily_limit or daily_losses < max_daily_losses) and ema_spread_ok and adx_ok and cooldown_ok
// Short Entry: Price closes above 8 EMA then closes back below it (inverse)
peaked_above_ema1 = close[1] > ltf_ema1[1]
closed_below_ema1 = close < ltf_ema1
short_entry = htf_bearish and ltf_bearish_aligned and peaked_above_ema1 and closed_below_ema1 and inSession and strategy.position_size == 0 and (not enable_daily_limit or daily_losses < max_daily_losses) and ema_spread_ok and adx_ok and cooldown_ok
// ═══════════════════════════════════════════════════════════════════
// EXIT CONDITIONS
// ═══════════════════════════════════════════════════════════════════
// Long Exits
long_partial_exit = strategy.position_size > 0 and close < ltf_ema1
long_full_exit = strategy.position_size > 0 and close < ltf_ema2
// Short Exits
short_partial_exit = strategy.position_size < 0 and close > ltf_ema1
short_full_exit = strategy.position_size < 0 and close > ltf_ema2
// ═══════════════════════════════════════════════════════════════════
// POSITION TRACKING
// ═══════════════════════════════════════════════════════════════════
var float entry_price = 0.0
var bool partial_exit_done = false
var int contracts_remaining = 0
// ═══════════════════════════════════════════════════════════════════
// EXECUTE TRADES
// ═══════════════════════════════════════════════════════════════════
// LONG TRADES
if long_entry
strategy.entry("Long", strategy.long, qty=4, comment="Long Entry")
entry_price := close
partial_exit_done := false
contracts_remaining := 4
last_trade_bar := bar_index
if showLabels
label.new(bar_index, low, "L", color=color.green, style=label.style_label_up, textcolor=color.white, size=size.small)
// Long Partial Exit (50%)
if long_partial_exit and not partial_exit_done and contracts_remaining > 2
exit_qty = math.floor(4 * partial_exit_pct)
strategy.close("Long", qty=exit_qty, comment="Partial Exit 50%")
partial_exit_done := true
contracts_remaining := contracts_remaining - exit_qty
if showLabels
label.new(bar_index, high, "P50%", color=color.orange, style=label.style_label_down, textcolor=color.white, size=size.small)
// Long Full Exit
if long_full_exit
strategy.close("Long", comment="Full Exit")
partial_exit_done := false
contracts_remaining := 0
if showLabels
label.new(bar_index, high, "X", color=color.red, style=label.style_label_down,
textcolor=color.white, size=size.small)
// SHORT TRADES
if short_entry
strategy.entry("Short", strategy.short, qty=4, comment="Short Entry")
entry_price := close
partial_exit_done := false
contracts_remaining := 4
last_trade_bar := bar_index
if showLabels
label.new(bar_index, high, "S", color=color.red, style=label.style_label_down,
textcolor=color.white, size=size.small)
// Short Partial Exit (50%)
if short_partial_exit and not partial_exit_done and contracts_remaining > 2
exit_qty = math.floor(4 * partial_exit_pct)
strategy.close("Short", qty=exit_qty, comment="Partial Exit 50%")
partial_exit_done := true
contracts_remaining := contracts_remaining - exit_qty
if showLabels
label.new(bar_index, low, "P50%", color=color.orange, style=label.style_label_up,
textcolor=color.white, size=size.small)
// Short Full Exit
if short_full_exit
strategy.close("Short", comment="Full Exit")
partial_exit_done := false
contracts_remaining := 0
if showLabels
label.new(bar_index, low, "X", color=color.green, style=label.style_label_up,
textcolor=color.white, size=size.small)
// ═══════════════════════════════════════════════════════════════════
// PLOTTING
// ═══════════════════════════════════════════════════════════════════
// Plot LTF EMAs (always shown as they're on current timeframe)
plot(ltf_ema1, color=color.new(color.teal, 0), linewidth=2, title="LTF 8 EMA")
plot(ltf_ema2, color=color.new(color.red, 0), linewidth=2, title="LTF 21 EMA")
// Plot HTF EMAs (optional)
plot(showHTFEMAs ? htf_ema1 : na, color=color.new(color.teal, 50), linewidth=3,
title="HTF 8 EMA", style=plot.style_line)
plot(showHTFEMAs ? htf_ema2 : na, color=color.new(color.red, 50), linewidth=3,
title="HTF 21 EMA", style=plot.style_line)
// Background color for trend
bg_col = htf_bullish ? color.new(color.green, 95) :
htf_bearish ? color.new(color.red, 95) : na
bgcolor(bg_col, title="HTF Trend Background")
// Session background
bgcolor(inSession ? na : color.new(color.gray, 90), title="Outside Session")
// ═══════════════════════════════════════════════════════════════════
// STATISTICS TABLE
// ═══════════════════════════════════════════════════════════════════
if showStats and barstate.islastconfirmedhistory
var table statsTable = table.new(position.top_right, 2, 12,
bgcolor=color.new(color.black, 80),
frame_color=color.gray, frame_width=1,
border_color=color.gray, border_width=1)
// Header
table.cell(statsTable, 0, 0, "Metric", bgcolor=color.new(color.blue, 50),
text_color=color.white, text_halign=text.align_center)
table.cell(statsTable, 1, 0, "Value", bgcolor=color.new(color.blue, 50),
text_color=color.white, text_halign=text.align_center)
// Symbol Info
table.cell(statsTable, 0, 1, "Symbol", text_color=color.white)
table.cell(statsTable, 1, 1, detectedSymbol + " ($" + str.tostring(tickValue) + "/tick)",
text_color=color.white)
// Current Trend
trend_text = htf_bullish ? "BULLISH ↑" : htf_bearish ? "BEARISH ↓" : "NEUTRAL —"
trend_color = htf_bullish ? color.green : htf_bearish ? color.red : color.gray
table.cell(statsTable, 0, 2, "HTF Trend", text_color=color.white)
table.cell(statsTable, 1, 2, trend_text, text_color=trend_color)
// Position
pos_text = strategy.position_size > 0 ? "LONG " + str.tostring(math.abs(strategy.position_size)) :
strategy.position_size < 0 ? "SHORT " + str.tostring(math.abs(strategy.position_size)) :
"FLAT"
pos_color = strategy.position_size > 0 ? color.green : (strategy.position_size < 0 ? color.red : color.gray)
table.cell(statsTable, 0, 3, "Position", text_color=color.white)
table.cell(statsTable, 1, 3, pos_text, text_color=pos_color)
// Total Trades
table.cell(statsTable, 0, 4, "Total Trades", text_color=color.white)
table.cell(statsTable, 1, 4, str.tostring(strategy.closedtrades), text_color=color.white)
// Win Rate
wins = strategy.wintrades
losses = strategy.losstrades
winrate = strategy.closedtrades > 0 ? (wins / strategy.closedtrades) * 100 : 0
table.cell(statsTable, 0, 5, "Win Rate", text_color=color.white)
table.cell(statsTable, 1, 5, str.tostring(winrate, "##.#") + "%",
text_color=winrate >= 50 ? color.green : color.red)
// Net Profit
table.cell(statsTable, 0, 6, "Net Profit", text_color=color.white)
table.cell(statsTable, 1, 6, "$" + str.tostring(strategy.netprofit, "##,###.##"),
text_color=strategy.netprofit >= 0 ? color.green : color.red)
// Session Time
session_active = inSession ? "✓ ACTIVE" : "✗ CLOSED"
session_color = inSession ? color.green : color.gray
table.cell(statsTable, 0, 7, "Session", text_color=color.white)
table.cell(statsTable, 1, 7, session_active, text_color=session_color)
// Losses Today
table.cell(statsTable, 0, 8, "Losses Today", text_color=color.white)
table.cell(statsTable, 1, 8, str.tostring(daily_losses), text_color=daily_losses >= max_daily_losses ? color.red : color.white)
// EMA Spread Filter Status
ema_spread_text = ema_spread_ok ? "OK (" + str.tostring(ema_separation_pct, "#.##") + "%)" : "BLOCKED (" + str.tostring(ema_separation_pct, "#.##") + "%)"
table.cell(statsTable, 0, 9, "EMA Spread", text_color=color.white)
table.cell(statsTable, 1, 9, ema_spread_text, text_color=ema_spread_ok ? color.green : color.orange)
// ADX Filter Status
adx_text = adx_ok ? "OK (" + str.tostring(htf_adx, "#.#") + ")" : "BLOCKED (" + str.tostring(htf_adx, "#.#") + ")"
table.cell(statsTable, 0, 10, "ADX Filter", text_color=color.white)
table.cell(statsTable, 1, 10, adx_text, text_color=adx_ok ? color.green : color.orange)
// Cooldown Status
bars_since_trade = bar_index - last_trade_bar
cooldown_text = cooldown_ok ? "OK" : "WAIT (" + str.tostring(min_bars_between_trades - bars_since_trade) + " bars)"
table.cell(statsTable, 0, 11, "Cooldown", text_color=color.white)
table.cell(statsTable, 1, 11, cooldown_text, text_color=cooldown_ok ? color.green : color.orange)
// ═══════════════════════════════════════════════════════════════════
// ALERTS
// ═══════════════════════════════════════════════════════════════════
alertcondition(long_entry, title="Long Entry", message="Long Entry Signal: {{ticker}}")
alertcondition(short_entry, title="Short Entry", message="Short Entry Signal: {{ticker}}")
alertcondition(long_partial_exit, title="Long Partial Exit", message="Long 50% Exit: {{ticker}}")
alertcondition(short_partial_exit, title="Short Partial Exit", message="Short 50% Exit: {{ticker}}")
alertcondition(long_full_exit, title="Long Full Exit", message="Long Full Exit: {{ticker}}")
alertcondition(short_full_exit, title="Short Full Exit", message="Short Full Exit: {{ticker}}")
Overview
Trend signals are filtered using minimum EMA percentage separation, an optional ADX filter, and specified session hours such as the New York session. The script includes a configurable trade cooldown, partial profit-taking, and a maximum daily loss limit. It plots higher-timeframe EMAs, execution labels, and a statistics table on the chart. By default, it operates without a predefined stop loss.
The inputs you can change from the gear icon on the chart. Defaults are the author's.
Setting
Type
Default
Variable
Symbol Type
string
AUTO
symbolType
Fast EMA Length
int
8
ema1_length
Slow EMA Length
int
21
ema2_length
Higher Timeframe (HTF)
timeframe
15
htf_timeframe
Lower Timeframe (LTF)
timeframe
1
ltf_timeframe
Min EMA Separation %
float
0.1
min_ema_separation
Use ADX Filter
bool
true
use_adx_filter
ADX Threshold
int
20
adx_threshold
Min Bars Between Trades
int
5
min_bars_between_trades
Use NY Session Only
bool
true
useSession
Trading Session (ET)
session
0930-1600
sessionTime
Partial Exit %
float
50
partial_exit_pct
Show all 17 settings
Setting
Type
Default
Variable
Enable Daily Loss Limit
bool
true
enable_daily_limit
Max Daily Losses
int
3
max_daily_losses
Show HTF EMAs on Chart
bool
true
showHTFEMAs
Show Entry/Exit Labels
bool
true
showLabels
Show Statistics Table
bool
true
showStats
Third-party code. ForexR did not write, test or endorse this script, and the summary above was generated automatically from its code and README. Indicators describe the past, and a strategy's backtest can look better than live trading, especially when a script repaints. Check it in the Strategy Tester and on a demo account before you rely on it.
Donald - Test Logic Futures is an overlay strategy designed for intraday futures trading. It combines kernel regression functions with exponential moving averages to evaluate price retracements and entry timing. Trades are filtered by session execution windows
Andean Scalping is a Pine Script v5 strategy template designed for backtesting trading signals. It features session and date-range filtering, directional trade toggles for long and short entries, and optional trend filtering tools including higher-timeframe EM
This strategy trades Nikkei 225 futures during a configurable overnight session, defaulting to 19:00–03:00 ET. It builds long and short trend conditions from an 8/21 EMA pair and a 200-period SMA, with RSI, momentum, an ATR-scaled breakout range, and a volume
This strategy plots a 90-period simple moving average from the daily and four-hour charts plus a daily session VWAP on the price chart. It enters long when VWAP is above the daily average and the four-hour VWAP crosses above its average, and short in the oppos
This strategy trades exponential moving average crossovers designed for NQ and MNQ futures contracts on intraday timeframes. It calculates three EMAs (8, 55, and 165 periods) and generates entry signals during a user-defined trading session when previous day v
This strategy trades directional momentum breakouts for crude oil on the price chart. It enters long or short positions when a fast exponential moving average crosses a slow exponential moving average, provided that current Average True Range exceeds its movin