在 GitHub 上查看
Double MA Breakout Strategy
概述
Double MA Breakout Strategy 是 MetaTrader 智能交易系统 DoubleMA_Breakout 的 StockSharp 版本。策略在每根已完成的 K 线上计算一条快线和一条慢线移动平均。当快线突破慢线时,会在最近收盘价上方按指定的突破距离挂出 Buy Stop;当快线跌破慢线时,则在下方挂出 Sell Stop。一旦信号反转或者交易窗口结束,所有挂单都会被撤销,已有仓位将被平掉。
移植过程保留了原始 EA 的核心逻辑,引入了 StockSharp 的高阶下单与风控能力,并通过 StrategyParam<T> 提供了细致的参数控制。代码中的注释全部改写为英文。
参数
| 参数 |
默认值 |
说明 |
FastMaPeriod |
2 |
快速均线周期。 |
SlowMaPeriod |
5 |
慢速均线周期。 |
FastMaMode |
Simple |
快速均线的计算方式(SMA、EMA、SMMA、LWMA、LSMA)。 |
SlowMaMode |
Simple |
慢速均线的计算方式。 |
FastAppliedPrice |
Close |
快速均线使用的价格类型(收盘、开盘、最高、最低、中值、典型价、加权价)。 |
SlowAppliedPrice |
Close |
慢速均线使用的价格类型。 |
SignalShift |
1 |
评估信号时向前查看的已完成 K 线数量,0 表示当前 K 线。 |
BreakoutDistancePoints |
45 |
以价格步长为单位的突破距离,用于放置挂单。 |
UseTimeWindow |
true |
是否启用交易时段过滤。 |
StartHour |
11 |
允许开仓的起始小时(含)。 |
StopHour |
16 |
停止交易的截止小时(含)。 |
UseFridayCloseAll |
true |
周五到达指定时间后平掉所有仓位并撤销挂单。 |
FridayCloseTime |
21:30 |
周五强制平仓的时间。 |
UseFridayStopTrading |
false |
周五到达指定时间后禁止新的开仓,但保留现有仓位。 |
FridayStopTradingTime |
19:00 |
周五停止开仓的时间(在启用时生效)。 |
CandleType |
1 小时 |
用于计算信号的 K 线类型。 |
交易流程
- 订阅
CandleType 对应的完成 K 线,按照所选模式与价格源计算两条移动平均。
- 维护短期历史队列,用来访问
SignalShift 指定的那根 K 线,避免使用被禁止的 GetValue 调用。
- 看多条件: 快速均线高于慢速均线时,撤销 Sell Stop,平掉空头仓位,并在收盘价上方
BreakoutDistancePoints × PriceStep 的位置挂出 Buy Stop(前提是没有其他挂单或持仓)。
- 看空条件: 快速均线低于慢速均线时,撤销 Buy Stop,平掉多头仓位,并在对称位置下方挂出 Sell Stop。
- 时间控制: 超出交易时段时,所有挂单会被撤销。周五可选地在“停止开仓时间”后禁止新单,并在“收盘时间”强制清仓,避免隔夜风险。
- 任一挂单成交后,另一侧的挂单会自动撤销,防止出现双向持仓。
与原始 EA 的差异
- 原策略中的资金管理选项和多级追踪止损未移植。下单手数由 StockSharp 的
Volume 属性决定,额外的风险控制可通过平台保护模块实现。
- MetaTrader 中的低级接口、错误重试逻辑被高层 API (
BuyStop、SellStop、ClosePosition、CancelOrder) 取代,代码更加简洁稳健。
- 诸如保证金阈值、滑点修正等经纪商特定功能未包含在内,如有需要可自行扩展。
- LSMA 模式使用 StockSharp 的
LinearRegression 指标来模拟 MetaTrader 的最小二乘均线。
使用建议
- 启动前请设置合适的
Volume;若保持默认值,则只交易一个合约/手。 |
- 代码中已调用
StartProtection(),可以在平台层面附加止损或止盈模块。 |
- 通过构造函数中的
.SetCanOptimize 设置,可将目标参数加入优化流程。 |
- 请确认交易标的具有正确的
PriceStep;若返回零,则会退化为默认步长 1,以避免挂单距离为零。 |
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>
/// Double MA breakout strategy.
/// Buys on fast/slow EMA bullish crossover.
/// Sells on bearish crossover.
/// </summary>
public class DoubleMaBreakoutStrategy : Strategy
{
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevFast;
private decimal _prevSlow;
private bool _hasPrev;
public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public DoubleMaBreakoutStrategy()
{
_fastPeriod = Param(nameof(FastPeriod), 10)
.SetDisplay("Fast EMA", "Fast EMA period", "Indicators");
_slowPeriod = Param(nameof(SlowPeriod), 25)
.SetDisplay("Slow EMA", "Slow 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(); _prevFast = 0m; _prevSlow = 0m; _hasPrev = false; }
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_hasPrev = false;
var fast = new ExponentialMovingAverage { Length = FastPeriod };
var slow = new ExponentialMovingAverage { Length = SlowPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(fast, slow, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal fast, decimal slow)
{
if (candle.State != CandleStates.Finished)
return;
if (!_hasPrev)
{
_prevFast = fast;
_prevSlow = slow;
_hasPrev = true;
return;
}
var crossUp = _prevFast <= _prevSlow && fast > slow;
var crossDown = _prevFast >= _prevSlow && fast < slow;
if (crossUp && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
}
else if (crossDown && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
}
_prevFast = fast;
_prevSlow = slow;
}
}
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 double_ma_breakout_strategy(Strategy):
def __init__(self):
super(double_ma_breakout_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 10) \
.SetDisplay("Fast EMA", "Fast EMA period", "Indicators")
self._slow_period = self.Param("SlowPeriod", 25) \
.SetDisplay("Slow EMA", "Slow EMA period", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Candle timeframe", "General")
self._prev_fast = 0.0
self._prev_slow = 0.0
self._has_prev = False
@property
def fast_period(self):
return self._fast_period.Value
@property
def slow_period(self):
return self._slow_period.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(double_ma_breakout_strategy, self).OnReseted()
self._prev_fast = 0.0
self._prev_slow = 0.0
self._has_prev = False
def OnStarted2(self, time):
super(double_ma_breakout_strategy, self).OnStarted2(time)
self._has_prev = False
fast = ExponentialMovingAverage()
fast.Length = self.fast_period
slow = ExponentialMovingAverage()
slow.Length = self.slow_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(fast, slow, self.process_candle).Start()
def process_candle(self, candle, fast, slow):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast)
slow_val = float(slow)
if not self._has_prev:
self._prev_fast = fast_val
self._prev_slow = slow_val
self._has_prev = True
return
cross_up = self._prev_fast <= self._prev_slow and fast_val > slow_val
cross_down = self._prev_fast >= self._prev_slow and fast_val < slow_val
if cross_up and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif cross_down and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return double_ma_breakout_strategy()