Gold Pullback Strategy
Gold Pullback Strategy combines EMA trend direction with MACD and TDI filters. Long trades trigger when price touches the 21‑period EMA during an uptrend and both MACD and TDI are bullish. Short trades occur on pullbacks to the EMA21 in downtrends with bearish MACD and TDI. Each position uses a 1:1 take profit and stop loss based on the signal candle plus an offset.
Details
- Data: Price candles.
- Entry Criteria:
- Long: EMA14 above EMA60, candle touches EMA21, MACD line above signal line, TDI MA above TDI signal and RSI above 50.
- Short: EMA14 below EMA60, candle touches EMA21, MACD line below signal line, TDI MA below TDI signal and RSI below 50.
- Exit Criteria: Stop loss or take profit hit at equal distance from entry with an added offset.
- Stops:
Offset= 0.1 applied to candle low/high. - Default Values:
EmaFastLength= 14EmaSlowLength= 60EmaPullbackLength= 21SlOffset= 0.1
- Filters:
- Category: Trend following
- Direction: Long & Short
- Indicators: EMA, MACD, RSI, SMA
- Complexity: Medium
- Risk level: Medium
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Gold pullback strategy.
/// The trend is up when the EmaFastLength EMA is above the EmaSlowLength EMA. A candle that touches the EmaPullbackLength EMA in an
/// uptrend, with the MACD (12, 26, 9) line above its signal, the TDI fast line (2-period SMA of RSI 13) above its signal line (7-period
/// SMA of the same RSI) and RSI above 50 goes long; the mirrored conditions go short. The stop is the signal candle low (high) minus
/// (plus) SlOffset, and the target lies at the same distance on the other side of the entry.
/// </summary>
public class GoldPullbackStrategy : Strategy
{
private const int _rsiLength = 13;
private const int _tdiFastLength = 2;
private const int _tdiSignalLength = 7;
private readonly StrategyParam<int> _emaFastLength;
private readonly StrategyParam<int> _emaSlowLength;
private readonly StrategyParam<int> _emaPullbackLength;
private readonly StrategyParam<decimal> _slOffset;
private readonly StrategyParam<DataType> _candleType;
private SimpleMovingAverage _tdiFast;
private SimpleMovingAverage _tdiSignal;
private decimal _stopPrice;
private decimal _takePrice;
/// <summary>
/// Fast trend EMA length.
/// </summary>
public int EmaFastLength
{
get => _emaFastLength.Value;
set => _emaFastLength.Value = value;
}
/// <summary>
/// Slow trend EMA length.
/// </summary>
public int EmaSlowLength
{
get => _emaSlowLength.Value;
set => _emaSlowLength.Value = value;
}
/// <summary>
/// Pullback EMA length.
/// </summary>
public int EmaPullbackLength
{
get => _emaPullbackLength.Value;
set => _emaPullbackLength.Value = value;
}
/// <summary>
/// Price offset added beyond the signal candle for the stop.
/// </summary>
public decimal SlOffset
{
get => _slOffset.Value;
set => _slOffset.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public GoldPullbackStrategy()
{
_emaFastLength = Param(nameof(EmaFastLength), 14)
.SetGreaterThanZero()
.SetDisplay("EMA Fast Length", "Fast trend EMA length", "Indicators");
_emaSlowLength = Param(nameof(EmaSlowLength), 60)
.SetGreaterThanZero()
.SetDisplay("EMA Slow Length", "Slow trend EMA length", "Indicators");
_emaPullbackLength = Param(nameof(EmaPullbackLength), 21)
.SetGreaterThanZero()
.SetDisplay("EMA Pullback Length", "Pullback EMA length", "Indicators");
_slOffset = Param(nameof(SlOffset), 0.1m)
.SetNotNegative()
.SetDisplay("SL Offset", "Price offset added beyond the signal candle for the stop", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_stopPrice = 0m;
_takePrice = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_stopPrice = 0m;
_takePrice = 0m;
var emaFast = new ExponentialMovingAverage { Length = EmaFastLength };
var emaSlow = new ExponentialMovingAverage { Length = EmaSlowLength };
var emaPullback = new ExponentialMovingAverage { Length = EmaPullbackLength };
var macd = new MovingAverageConvergenceDivergenceSignal
{
Macd =
{
ShortMa = { Length = 12 },
LongMa = { Length = 26 },
},
SignalMa = { Length = 9 }
};
var rsi = new RelativeStrengthIndex { Length = _rsiLength };
_tdiFast = new SimpleMovingAverage { Length = _tdiFastLength };
_tdiSignal = new SimpleMovingAverage { Length = _tdiSignalLength };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(emaFast, emaSlow, emaPullback, macd, rsi, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, emaFast);
DrawIndicator(area, emaSlow);
DrawIndicator(area, emaPullback);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue fastValue, IIndicatorValue slowValue, IIndicatorValue pullbackValue,
IIndicatorValue macdValue, IIndicatorValue rsiValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!rsiValue.IsFormed)
return;
var rsi = rsiValue.GetValue<decimal>();
var tdiFast = _tdiFast.Process(new DecimalIndicatorValue(_tdiFast, rsi, candle.OpenTime) { IsFinal = true });
var tdiSignal = _tdiSignal.Process(new DecimalIndicatorValue(_tdiSignal, rsi, candle.OpenTime) { IsFinal = true });
if (ManagePosition(candle))
return;
if (!fastValue.IsFormed || !slowValue.IsFormed || !pullbackValue.IsFormed || !_tdiFast.IsFormed || !_tdiSignal.IsFormed)
return;
if (!macdValue.IsFormed || macdValue is not MovingAverageConvergenceDivergenceSignalValue { Macd: decimal macd, Signal: decimal signal })
return;
if (!IsFormedAndOnlineAndAllowTrading() || Position != 0)
return;
var emaFast = fastValue.GetValue<decimal>();
var emaSlow = slowValue.GetValue<decimal>();
var pullback = pullbackValue.GetValue<decimal>();
var tdiMa = tdiFast.GetValue<decimal>();
var tdiSig = tdiSignal.GetValue<decimal>();
var touches = candle.LowPrice <= pullback && candle.HighPrice >= pullback;
var close = candle.ClosePrice;
if (touches && emaFast > emaSlow && macd > signal && tdiMa > tdiSig && rsi > 50m)
{
var stop = candle.LowPrice - SlOffset;
if (stop >= close)
return;
BuyMarket(Volume);
_stopPrice = stop;
_takePrice = close + (close - stop);
}
else if (touches && emaFast < emaSlow && macd < signal && tdiMa < tdiSig && rsi < 50m)
{
var stop = candle.HighPrice + SlOffset;
if (stop <= close)
return;
SellMarket(Volume);
_stopPrice = stop;
_takePrice = close - (stop - close);
}
}
// Returns true when the position was closed on this candle.
private bool ManagePosition(ICandleMessage candle)
{
if (Position > 0 && _stopPrice > 0)
{
if (candle.LowPrice <= _stopPrice || candle.HighPrice >= _takePrice)
{
SellMarket(Position);
return true;
}
}
else if (Position < 0 && _stopPrice > 0)
{
if (candle.HighPrice >= _stopPrice || candle.LowPrice <= _takePrice)
{
BuyMarket(Math.Abs(Position));
return true;
}
}
return false;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage, MovingAverageConvergenceDivergenceSignal, RelativeStrengthIndex, SimpleMovingAverage, DecimalIndicatorValue
from StockSharp.Algo.Strategies import Strategy
class gold_pullback_strategy(Strategy):
"""
Gold pullback strategy.
The trend is up when the EmaFastLength EMA is above the EmaSlowLength EMA. A candle that touches the EmaPullbackLength EMA in an
uptrend, with the MACD (12, 26, 9) line above its signal, the TDI fast line (2-period SMA of RSI 13) above its signal line (7-period
SMA of the same RSI) and RSI above 50 goes long; the mirrored conditions go short. The stop is the signal candle low (high) minus
(plus) SlOffset, and the target lies at the same distance on the other side of the entry.
"""
RSI_LENGTH = 13
TDI_FAST_LENGTH = 2
TDI_SIGNAL_LENGTH = 7
def __init__(self):
super(gold_pullback_strategy, self).__init__()
self._ema_fast_length = self.Param("EmaFastLength", 14).SetGreaterThanZero().SetDisplay("EMA Fast Length", "Fast trend EMA length", "Indicators")
self._ema_slow_length = self.Param("EmaSlowLength", 60).SetGreaterThanZero().SetDisplay("EMA Slow Length", "Slow trend EMA length", "Indicators")
self._ema_pullback_length = self.Param("EmaPullbackLength", 21).SetGreaterThanZero().SetDisplay("EMA Pullback Length", "Pullback EMA length", "Indicators")
self._sl_offset = self.Param("SlOffset", 0.1).SetNotNegative().SetDisplay("SL Offset", "Price offset added beyond the signal candle for the stop", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._tdi_fast = None
self._tdi_signal = None
self._stop_price = Decimal(0)
self._take_price = Decimal(0)
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(gold_pullback_strategy, self).OnReseted()
self._stop_price = Decimal(0)
self._take_price = Decimal(0)
def OnStarted2(self, time):
super(gold_pullback_strategy, self).OnStarted2(time)
self._stop_price = Decimal(0)
self._take_price = Decimal(0)
ema_fast = ExponentialMovingAverage()
ema_fast.Length = self._ema_fast_length.Value
ema_slow = ExponentialMovingAverage()
ema_slow.Length = self._ema_slow_length.Value
ema_pullback = ExponentialMovingAverage()
ema_pullback.Length = self._ema_pullback_length.Value
macd = MovingAverageConvergenceDivergenceSignal()
macd.Macd.ShortMa.Length = 12
macd.Macd.LongMa.Length = 26
macd.SignalMa.Length = 9
rsi = RelativeStrengthIndex()
rsi.Length = self.RSI_LENGTH
self._tdi_fast = SimpleMovingAverage()
self._tdi_fast.Length = self.TDI_FAST_LENGTH
self._tdi_signal = SimpleMovingAverage()
self._tdi_signal.Length = self.TDI_SIGNAL_LENGTH
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(ema_fast, ema_slow, ema_pullback, macd, rsi, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ema_fast)
self.DrawIndicator(area, ema_slow)
self.DrawIndicator(area, ema_pullback)
self.DrawOwnTrades(area)
def _process_value(self, indicator, value, time):
indicator_input = DecimalIndicatorValue(indicator, value, time)
indicator_input.IsFinal = True
return indicator.Process(indicator_input)
def _process_candle(self, candle, fast_value, slow_value, pullback_value, macd_value, rsi_value):
if candle.State != CandleStates.Finished:
return
if not rsi_value.IsFormed:
return
rsi = rsi_value.GetValue[Decimal](None)
tdi_fast = self._process_value(self._tdi_fast, rsi, candle.OpenTime)
tdi_signal = self._process_value(self._tdi_signal, rsi, candle.OpenTime)
if self._manage_position(candle):
return
if not fast_value.IsFormed or not slow_value.IsFormed or not pullback_value.IsFormed or not self._tdi_fast.IsFormed or not self._tdi_signal.IsFormed:
return
if not macd_value.IsFormed or macd_value.Macd is None or macd_value.Signal is None:
return
if not self.IsFormedAndOnlineAndAllowTrading() or self.Position != 0:
return
macd = macd_value.Macd
signal = macd_value.Signal
ema_fast = fast_value.GetValue[Decimal](None)
ema_slow = slow_value.GetValue[Decimal](None)
pullback = pullback_value.GetValue[Decimal](None)
tdi_ma = tdi_fast.GetValue[Decimal](None)
tdi_sig = tdi_signal.GetValue[Decimal](None)
touches = candle.LowPrice <= pullback and candle.HighPrice >= pullback
close = candle.ClosePrice
offset = Decimal(self._sl_offset.Value)
if touches and ema_fast > ema_slow and macd > signal and tdi_ma > tdi_sig and rsi > 50:
stop = candle.LowPrice - offset
if stop >= close:
return
self.BuyMarket(self.Volume)
self._stop_price = stop
self._take_price = close + (close - stop)
elif touches and ema_fast < ema_slow and macd < signal and tdi_ma < tdi_sig and rsi < 50:
stop = candle.HighPrice + offset
if stop <= close:
return
self.SellMarket(self.Volume)
self._stop_price = stop
self._take_price = close - (stop - close)
def _manage_position(self, candle):
if self.Position > 0 and self._stop_price > 0:
if candle.LowPrice <= self._stop_price or candle.HighPrice >= self._take_price:
self.SellMarket(self.Position)
return True
elif self.Position < 0 and self._stop_price > 0:
if candle.HighPrice >= self._stop_price or candle.LowPrice <= self._take_price:
self.BuyMarket(abs(self.Position))
return True
return False
def CreateClone(self):
return gold_pullback_strategy()