Gold Pullback 策略
Gold Pullback Strategy 是一种趋势策略。当价格在上升趋势中回调至 EMA21 且 MACD 与 TDI 同时看多时做多;当价格在下降趋势中回调至 EMA21 且 MACD 与 TDI 看空时做空。每笔交易的止盈和止损以信号蜡烛为基准,距离相等并加上一个偏移。
细节
- 数据: 价格K线。
- 入场条件:
- 多头: EMA14 高于 EMA60,K线触及 EMA21,MACD 线高于信号线,TDI MA 高于 TDI 信号且 RSI 高于 50。
- 空头: EMA14 低于 EMA60,K线触及 EMA21,MACD 线低于信号线,TDI MA 低于 TDI 信号且 RSI 低于 50。
- 离场条件: 触发等距的止损或止盈。
- 止损:
Offset= 0.1 加到 K 线的最低/最高价。 - 默认参数:
EmaFastLength= 14EmaSlowLength= 60EmaPullbackLength= 21SlOffset= 0.1
- 过滤器:
- 类型: 趋势跟随
- 方向: 多空皆可
- 指标: EMA, MACD, RSI, SMA
- 复杂度: 中等
- 风险级别: 中等
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()