EMA Pullback Strategy

Author: ChaoZhang, Date: 2023-12-21 11:48:54



The EMA pullback strategy is a quantitative trading strategy based on the EMA indicator. It constructs trading signals using three EMA curves with different periods and sets stop loss and take profit based on price pullbacks to automate trading.

Strategy Principle

The strategy uses three EMA curves:

  • EMA1: for judging price pullback support/resistance level, with a relatively short period, default to 33 periods.
  • EMA2: for filtering some reversal signals, with a period of 5 times of EMA1, default to 165 periods.
  • EMA3: for determining overall trend direction, with a period of 11 times of EMA1, default to 365 periods.

Trading signals are generated according to the following logic:

Long signal: Price crosses above EMA1, pulls back below EMA1 forming higher lows, with pullback not reaching EMA2. Enter long when price crosses back above EMA1.

Short signal: Price crosses below EMA1, pulls back above EMA1 forming lower highs, with pullback not reaching EMA2. Enter short when price crosses back below EMA1.

Stop loss is set at the lowest/highest pullback price for long/short. Take profit is set at 2 times the stop loss.

Strategy Advantages

The strategy has the following advantages:

  1. Trading signals constructed using reliable EMA indicator.
  2. Price pullback avoids being trapped effectively.
  3. Stop loss set at previous high/low controls risk effectively.
  4. Take profit satisfying risk-reward ratio.
  5. EMA parameters adjustable for different cycles.

Strategy Risks

The strategy also has some risks:

  1. EMA has lagging effect, may miss trend reversal points.
  2. Pullback range too large exceeding EMA2 may generate false signals.
  3. Stop loss may be broken in trending markets.
  4. Improper parameter settings lead to over-trading or missing opportunities.

Risks can be mitigated by adjusting EMA periods, pullback limit etc. Other indicators can also be added to filter signals.

Optimization Directions

The strategy can also be optimized in the following aspects:

  1. Add trend indicator to avoid counter-trend trading, e.g. MACD.
  2. Add trading volume indicator to avoid false breakout, e.g. OBV.
  3. Optimize EMA periods or use adaptive EMA.
  4. Dynamically optimize parameters using machine learning models like bag-of-words.
  5. Add model prediction for adaptive stop loss and take profit.


The EMA pullback strategy constructs a trading system using three EMAs and sets stop loss and take profit based on price pullbacks to automate trading. It effectively controls trading risks and can be optimized by adjusting parameters based on market conditions. Overall the strategy has sound logic and can be applied in actual trading. Future improvements can be made in aspects like trend determination, parameter optimization and risk control.

start: 2023-11-20 00:00:00
end: 2023-12-20 00:00:00
period: 1h
basePeriod: 15m
exchanges: [{"eid":"Futures_Binance","currency":"BTC_USDT"}]

// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// created by Space Jellyfish

strategy("EMA pullback strategy", overlay = true, initial_capital=10000, commission_value = 0.075)

target_stop_ratio = input(title="Take Profit Stop Loss ratio", type=input.float, defval=2.06, minval=0.5, maxval=100)
riskLimit_low =  input(title="lowest risk per trade", type=input.float, defval=0.008, minval=0, maxval=100)
riskLimit_high =  input(title="highest risk per trade", type=input.float, defval=0.02, minval=0, maxval=100)
//give up the trade, if the risk is smaller than limit, adjust position size if risk is bigger than limit

ema_pullbackLevel_period = input(title="EMA1 for pullback level Period", type=input.integer, defval=33, minval=1, maxval=10000)
ema_pullbackLimiit_period = input(title="EMA2 for pullback limit Period", type=input.integer, defval=165, minval=1, maxval=10000)
ema_trend_period = input(title="EMA3 for trend Period", type=input.integer, defval=365, minval=1, maxval=10000)

startDate = input(title="Start Date", type=input.integer, defval=1, minval=1, maxval=31)
startMonth = input(title="Start Month", type=input.integer, defval=1, minval=1, maxval=12)
startYear = input(title="Start Year", type=input.integer, defval=2018, minval=2008, maxval=2200)

inDateRange = (time >= timestamp(syminfo.timezone, startYear, startMonth, startDate, 0, 0))

ema_pullbackLevel = ema(close, ema_pullbackLevel_period)
ema_pullbackLimit = ema(close, ema_pullbackLimiit_period)
ema_trendDirection = ema(close, ema_trend_period)

//ema pullback 
float pricePullAboveEMA_maxClose = na
float pricePullAboveEMA_maxHigh = na

float pricePullBelowEMA_minClose = na
float pricePullBelowMA_minLow = na

if(crossover(close, ema_pullbackLevel))
    pricePullAboveEMA_maxClose := close
    pricePullAboveEMA_maxHigh := high
    pricePullAboveEMA_maxClose := pricePullAboveEMA_maxClose[1]
    pricePullAboveEMA_maxHigh := pricePullAboveEMA_maxHigh[1]

if(close > pricePullAboveEMA_maxClose)
    pricePullAboveEMA_maxClose := close
if(high > pricePullAboveEMA_maxHigh)
    pricePullAboveEMA_maxHigh := high

if(crossunder(close, ema_pullbackLevel))
    pricePullBelowEMA_minClose := close
    pricePullBelowMA_minLow := low
    pricePullBelowEMA_minClose :=pricePullBelowEMA_minClose[1]
if(close < pricePullBelowEMA_minClose)
    pricePullBelowEMA_minClose := close
if(low < pricePullBelowMA_minLow)
    pricePullBelowMA_minLow := low

long_strategy = crossover(close, ema_pullbackLevel) and pricePullBelowEMA_minClose < ema_pullbackLimit and ema_pullbackLevel>ema_trendDirection 
short_strategy = crossunder(close, ema_pullbackLevel) and pricePullAboveEMA_maxClose > ema_pullbackLimit and ema_pullbackLevel<ema_trendDirection

var open_long_or_short = 0// long = 10000, short = -10000, no open = 0

//check if position is closed
if(strategy.position_size == 0)
    open_long_or_short := 0
    open_long_or_short := open_long_or_short[1]

float risk_long = na
float risk_short = na
float stopLoss = na
float takeProfit = na
float entry_price = na

float entryContracts = 0

risk_long := risk_long[1]
risk_short := risk_short[1]
//open a position determine the position size
if (strategy.position_size == 0 and long_strategy and inDateRange)
    risk_long := (close - pricePullBelowMA_minLow) / close

    if(risk_long < riskLimit_high)
        entryContracts := strategy.equity / close
        entryContracts := (strategy.equity * riskLimit_high / risk_long)/close
    if(risk_long > riskLimit_low)
        strategy.entry("long", strategy.long, qty = entryContracts, when = long_strategy)

    open_long_or_short := 10000
if (strategy.position_size == 0 and short_strategy and inDateRange)
    risk_short := (pricePullAboveEMA_maxHigh - close) / close
    if(risk_short < riskLimit_high)
        entryContracts := strategy.equity / close
        entryContracts := (strategy.equity * riskLimit_high / risk_short)/close

    if(risk_short > riskLimit_low)
        strategy.entry("short", strategy.short, qty = entryContracts, when = short_strategy)

    open_long_or_short := -10000

//take profit / stop loss
if(open_long_or_short == 10000)

    stopLoss :=   strategy.position_avg_price*(1 - risk_long)
    takeProfit :=  strategy.position_avg_price*(1 + target_stop_ratio * risk_long)
    entry_price := strategy.position_avg_price
    strategy.exit("Long exit","long", limit = takeProfit , stop = stopLoss)
if(open_long_or_short == -10000)
    stopLoss :=  strategy.position_avg_price*(1 + risk_short)
    takeProfit :=  strategy.position_avg_price*(1 - target_stop_ratio * risk_short)
    entry_price := strategy.position_avg_price
    strategy.exit("Short exit","short", limit = takeProfit, stop = stopLoss)

plot(ema_pullbackLevel, color=color.aqua,  title="ema pullback level")
plot(ema_pullbackLimit, color=color.purple,  title="ema pullback limit")
plot(ema_trendDirection, color=color.white,  title="ema trend")

plot(entry_price, color = color.yellow, linewidth = 1, style = plot.style_linebr)
plot(stopLoss, color = color.red, linewidth = 1, style = plot.style_linebr)
plot(takeProfit, color = color.green, linewidth = 1, style = plot.style_linebr)