在 GitHub 上查看
EMA Cross Contest Hedged 策略
该策略在 StockSharp 中重现 MetaTrader 智能交易系统 EMA_CROSS_CONTEST_HEDGED。机器人监控快慢指数移动平均线(EMA)的交叉,并可选使用 MACD 直方图过滤趋势。当出现信号时,策略立即以市价开仓,并放置一组分批的止损挂单,当价格继续沿趋势运行时逐步加仓以对冲风险。
交易逻辑
- 在指定的K线数据上计算快 EMA 和慢 EMA。信号可以基于上一根完成的K线(默认)或当前K线收盘后生成。
- 当快 EMA 上穿慢 EMA 时认为出现多头交叉,下穿时认为出现空头交叉。
- 若启用 MACD 过滤,则做多需要 MACD 线上方为正,做空需要 MACD 线为负。
- 多头信号触发时,以市价买入,设置止损和止盈,并在价格上方按固定间距排列四个买入止损挂单。
- 空头信号触发时,以市价卖出,并在价格下方设置四个卖出止损挂单。
- 如果挂单在到期时间之前未被触发,将自动取消。
- 浮动盈利扩大时启动追踪止损;若参数
Use Close 启用,则相反方向的交叉会提前平仓。
参数
- Candle Type – 所用K线类型/周期。
- Order Volume – 初始头寸及每个对冲挂单的交易量。
- Take Profit (pips) – 止盈距离(点)。
- Stop Loss (pips) – 止损距离(点)。
- Trailing Stop (pips) – 追踪止损距离(点,0 表示关闭)。
- Hedge Level (pips) – 对冲挂单之间的间隔(点)。
- Use Close – 当出现反向交叉时是否平仓。
- Use MACD – 是否需要 MACD 确认。
- Expiration (s) – 挂单有效时间(秒)。
- Short EMA – 快速 EMA 的周期。
- Long EMA – 慢速 EMA 的周期(必须大于快速 EMA)。
- Signal Bar – 取信号的K线:0=当前K线,1=上一根K线。
说明
- 按照要求,代码中的注释全部使用英文。
- 对冲挂单的排列方式与原始 MQL 策略一致,共包含四个等距级别。
- 点数转换为价格时会考虑交易品种的
PriceStep 与 Decimals,以贴近 MetaTrader 的计算方式。
using System;
using System.Collections.Generic;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// EMA Cross Contest strategy - dual EMA crossover.
/// Buys when short EMA crosses above long EMA.
/// Sells when short EMA crosses below long EMA.
/// </summary>
public class EmaCrossContestHedgedLadderStrategy : Strategy
{
private readonly StrategyParam<int> _shortPeriod;
private readonly StrategyParam<int> _longPeriod;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevShort;
private decimal _prevLong;
private bool _hasPrev;
public int ShortPeriod { get => _shortPeriod.Value; set => _shortPeriod.Value = value; }
public int LongPeriod { get => _longPeriod.Value; set => _longPeriod.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public EmaCrossContestHedgedLadderStrategy()
{
_shortPeriod = Param(nameof(ShortPeriod), 9)
.SetDisplay("Short EMA", "Short EMA period", "Indicators");
_longPeriod = Param(nameof(LongPeriod), 21)
.SetDisplay("Long EMA", "Long EMA period", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];
protected override void OnReseted() { base.OnReseted(); _prevShort = 0m; _prevLong = 0m; _hasPrev = false; }
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_hasPrev = false;
var shortEma = new ExponentialMovingAverage { Length = ShortPeriod };
var longEma = new ExponentialMovingAverage { Length = LongPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(shortEma, longEma, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal shortEma, decimal longEma)
{
if (candle.State != CandleStates.Finished)
return;
if (!_hasPrev)
{
_prevShort = shortEma;
_prevLong = longEma;
_hasPrev = true;
return;
}
if (_prevShort <= _prevLong && shortEma > longEma && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
}
else if (_prevShort >= _prevLong && shortEma < longEma && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
}
_prevShort = shortEma;
_prevLong = longEma;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class ema_cross_contest_hedged_ladder_strategy(Strategy):
def __init__(self):
super(ema_cross_contest_hedged_ladder_strategy, self).__init__()
self._short_period = self.Param("ShortPeriod", 9).SetDisplay("Short EMA", "Short EMA period", "Indicators")
self._long_period = self.Param("LongPeriod", 21).SetDisplay("Long EMA", "Long EMA period", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))).SetDisplay("Candle Type", "Candle timeframe", "General")
self._prev_short = 0.0
self._prev_long = 0.0
self._has_prev = False
@property
def short_period(self): return self._short_period.Value
@property
def long_period(self): return self._long_period.Value
@property
def candle_type(self): return self._candle_type.Value
def OnReseted(self):
super(ema_cross_contest_hedged_ladder_strategy, self).OnReseted()
self._prev_short = 0.0
self._prev_long = 0.0
self._has_prev = False
def OnStarted2(self, time):
super(ema_cross_contest_hedged_ladder_strategy, self).OnStarted2(time)
self._has_prev = False
short_ema = ExponentialMovingAverage()
short_ema.Length = self.short_period
long_ema = ExponentialMovingAverage()
long_ema.Length = self.long_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(short_ema, long_ema, self.process_candle).Start()
def process_candle(self, candle, short_ema, long_ema):
if candle.State != CandleStates.Finished:
return
short_val = float(short_ema)
long_val = float(long_ema)
if not self._has_prev:
self._prev_short = short_val
self._prev_long = long_val
self._has_prev = True
return
if self._prev_short <= self._prev_long and short_val > long_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif self._prev_short >= self._prev_long and short_val < long_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._prev_short = short_val
self._prev_long = long_val
def CreateClone(self):
return ema_cross_contest_hedged_ladder_strategy()