在 GitHub 上查看
Crossing Moving Average 策略
概览
- 由 MetaTrader 5 专家顾问 “Crossing Moving Average (barabashkakvn's edition)”(文件
MQL/21515)转换而来。
- 采用 StockSharp 高级 API,通过蜡烛订阅和指标绑定实现原始逻辑。
- 适用于依靠均线交叉与动量变化捕捉趋势反转的品种。
- 按照要求,仅提供 C# 版本,暂不包含 Python 版本。
核心思路
策略跟踪两条可配置的移动平均线(快线与慢线),可选向前平移,并结合 Momentum 指标进行信号确认。只有在以下条件同时满足时才会开仓:
- 快线在最近两个完整柱上方穿慢线,并且两者之间的距离至少达到设定的最小点差。
- Momentum 当前值超过(做多)或低于(做空)阈值,并且相对于上一柱进一步朝有利方向发展。
- 指标所用价格源可从开、高、低、收、Median、Typical、Weighted 等模式中选择,以复现 MetaTrader 的 applied price 设置。
风险与持仓管理
- 下单手数 固定,由参数设定;在反向开仓时会自动覆盖原有仓位并建立新仓。
- 止损 / 止盈 使用点数配置,并通过
Security.PriceStep 转换为价格偏移。若报价保留 3 或 5 位小数,系统会将步长乘以 10,以匹配 MetaTrader 的点值定义。
- 跟踪止损 在价格距离开仓价超过
TrailingStop + TrailingStep 点后启动;之后若能至少再改善 TrailingStep 点,止损价会更新为 当前价 - TrailingStop(多头)或 当前价 + TrailingStop(空头)。
- 每根完成的蜡烛都会检查其最高价/最低价是否触及止损或止盈。一旦被触发,将以市价平仓,以模拟原平台中的真实执行。
指标组合
- 快线移动平均:可配置周期、平移和类型(SMA、EMA、SMMA、WMA)。
- 慢线移动平均:与快线相同的参数集合。
- Momentum:周期与价格源与均线保持一致。策略会自动识别指标输出是围绕 0 还是围绕 100,并据此调整过滤逻辑。
交易逻辑
- 等待所有指标形成有效值。策略内部保存最近若干个历史数据,以便在评估含平移的交叉时与原始 EA 对齐。
- 计算快线与慢线在前两柱上的差值。只有当快线真正穿越慢线且距离满足
MinDistancePips 要求时才视为有效信号。
- 读取同一时间段内的 Momentum 数据。做多时要求当前 Momentum 高于阈值且高于上一柱;做空时则要求低于负阈值且继续走弱。
- 若出现新的反向信号,策略会立即平掉现有仓位并按设定手数开立新的方向仓位。
参数说明
| 参数 |
含义 |
默认值 |
OrderVolume |
每次下单的基础手数。 |
1 |
StopLossPips |
止损距离(点),0 表示不启用。 |
50 |
TakeProfitPips |
止盈距离(点),0 表示不启用。 |
50 |
TrailingStopPips |
跟踪止损的基准距离(点)。 |
5 |
TrailingStepPips |
更新跟踪止损所需的最小改善(点)。 |
5 |
MinDistancePips |
快慢线之间的最小有效距离(点)。 |
0 |
MomentumFilter |
动量过滤阈值。 |
0.1 |
FastPeriod / FastShift |
快线周期与平移(柱数)。 |
13 / 1 |
SlowPeriod / SlowShift |
慢线周期与平移(柱数)。 |
34 / 3 |
MaMethod |
均线类型(Simple、Exponential、Smoothed、Weighted)。 |
Exponential |
AppliedPrice |
指标使用的价格类型。 |
Close |
MomentumPeriod |
Momentum 计算周期。 |
14 |
CandleType |
策略订阅的蜡烛数据类型。 |
TimeFrame(1m) |
使用建议
- 请确保交易标的的
Security.PriceStep 已正确设置,否则点数将退化为价格绝对值。
- 当启用
TrailingStopPips 时应同时设置正值的 TrailingStepPips,这与原 EA 的参数校验保持一致。
- 止损/止盈基于蜡烛的最高价与最低价判断,因而较小的时间周期能更好地模拟真实行情波动。
- 代码中保留了关键日志,便于跟踪入场、出场以及跟踪止损的移动情况。
文件结构
API/2938_Crossing_Moving_Average/
├── CS/CrossingMovingAverageStrategy.cs
├── README.md
├── README_zh.md
└── README_ru.md
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>
/// Moving average crossover strategy.
/// Enters long when fast EMA crosses above slow EMA, enters short on the opposite crossover.
/// </summary>
public class CrossingMovingAverageStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private decimal? _prevFast;
private decimal? _prevSlow;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int FastPeriod
{
get => _fastPeriod.Value;
set => _fastPeriod.Value = value;
}
public int SlowPeriod
{
get => _slowPeriod.Value;
set => _slowPeriod.Value = value;
}
public CrossingMovingAverageStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Timeframe", "General");
_fastPeriod = Param(nameof(FastPeriod), 13)
.SetGreaterThanZero()
.SetDisplay("Fast Period", "Fast EMA period", "Indicators");
_slowPeriod = Param(nameof(SlowPeriod), 34)
.SetGreaterThanZero()
.SetDisplay("Slow Period", "Slow EMA period", "Indicators");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevFast = null;
_prevSlow = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevFast = null;
_prevSlow = null;
var fastEma = new ExponentialMovingAverage { Length = FastPeriod };
var slowEma = new ExponentialMovingAverage { Length = SlowPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(fastEma, slowEma, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, fastEma);
DrawIndicator(area, slowEma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal fast, decimal slow)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
{
_prevFast = fast;
_prevSlow = slow;
return;
}
if (_prevFast == null || _prevSlow == null)
{
_prevFast = fast;
_prevSlow = slow;
return;
}
// Crossover detection
var prevAbove = _prevFast.Value > _prevSlow.Value;
var currAbove = fast > slow;
_prevFast = fast;
_prevSlow = slow;
// Golden cross: fast crosses above slow
if (!prevAbove && currAbove)
{
if (Position < 0)
BuyMarket();
if (Position <= 0)
BuyMarket();
}
// Death cross: fast crosses below slow
else if (prevAbove && !currAbove)
{
if (Position > 0)
SellMarket();
if (Position >= 0)
SellMarket();
}
}
}
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 crossing_moving_average_strategy(Strategy):
def __init__(self):
super(crossing_moving_average_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._fast_period = self.Param("FastPeriod", 13) \
.SetDisplay("Fast Period", "Fast EMA period", "Indicators")
self._slow_period = self.Param("SlowPeriod", 34) \
.SetDisplay("Slow Period", "Slow EMA period", "Indicators")
self._prev_fast = None
self._prev_slow = None
@property
def CandleType(self):
return self._candle_type.Value
@property
def FastPeriod(self):
return self._fast_period.Value
@property
def SlowPeriod(self):
return self._slow_period.Value
def OnReseted(self):
super(crossing_moving_average_strategy, self).OnReseted()
self._prev_fast = None
self._prev_slow = None
def OnStarted2(self, time):
super(crossing_moving_average_strategy, self).OnStarted2(time)
self._prev_fast = None
self._prev_slow = None
fast_ema = ExponentialMovingAverage()
fast_ema.Length = self.FastPeriod
slow_ema = ExponentialMovingAverage()
slow_ema.Length = self.SlowPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(fast_ema, slow_ema, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, fast_ema)
self.DrawIndicator(area, slow_ema)
self.DrawOwnTrades(area)
def _on_process(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fv = float(fast_value)
sv = float(slow_value)
if self._prev_fast is None or self._prev_slow is None:
self._prev_fast = fv
self._prev_slow = sv
return
prev_above = self._prev_fast > self._prev_slow
curr_above = fv > sv
self._prev_fast = fv
self._prev_slow = sv
# Golden cross: fast crosses above slow
if not prev_above and curr_above:
if self.Position < 0:
self.BuyMarket()
if self.Position <= 0:
self.BuyMarket()
# Death cross: fast crosses below slow
elif prev_above and not curr_above:
if self.Position > 0:
self.SellMarket()
if self.Position >= 0:
self.SellMarket()
def CreateClone(self):
return crossing_moving_average_strategy()