在 GitHub 上查看

Russian20 动量均线策略

概览

Russian20 动量均线策略 直接改编自 MetaTrader 5 专家顾问 Russian20-hp1.mq5。原始脚本由 Gordago Software Corp. 发布,使用 2 小时图、20 周期简单移动平均线(SMA)和 5 周期动量指标来捕捉短期趋势延续。StockSharp 版本保留了相同的分析核心,同时将下单与风险控制迁移到高阶策略 API。

交易逻辑

  • 数据频率: 默认使用 2 小时K线(与 MQL5 的 PERIOD_H2 相同),策略仅在每根K线收盘后运行。
  • 指标:
    • 可配置周期的简单移动平均线(默认 20)。
    • 可配置周期的动量指标(默认 5),其中 100 为动量中性值。
  • 多头开仓条件(全部满足):
    1. 收盘价位于 SMA 之上。
    2. 动量值大于 100,表示向上的加速度。
    3. 当前收盘价高于上一根K线的收盘价,确认价格动能继续向上。
  • 空头开仓条件(全部满足):
    1. 收盘价位于 SMA 之下。
    2. 动量值小于 100,表示向下的加速度。
    3. 当前收盘价低于上一根K线的收盘价。
  • 多头平仓: 当动量跌破 100 或价格触及设定的止损/止盈距离时,立即平掉多单。
  • 空头平仓: 当动量上破 100 或价格触及设定的止损/止盈距离时,立即平掉空单。

风险管理

原始 MQL5 程序将止损与止盈设置为“点(pips)”,并针对 4 位和 5 位报价做了修正。C# 版本按以下方式复现:

  • 根据证券的 PriceStep 计算调整后的点值;若价格保留三位或五位小数,则点值为 PriceStep * 10,否则等于 PriceStep
  • 将用户输入的止损/止盈点数转换为绝对价格距离。
  • 在每根收盘K线上检测价格是否突破这些阈值,如发生则立即平仓。

参数

参数 默认值 说明
CandleType 2 小时K线 用于生成信号的数据类型。
MovingAverageLength 20 SMA 滤波的周期。
MomentumPeriod 5 动量指标的周期。
StopLossBuyPips 50 多头止损点数,为 0 表示禁用。
TakeProfitBuyPips 50 多头止盈点数,为 0 表示禁用。
StopLossSellPips 50 空头止损点数,为 0 表示禁用。
TakeProfitSellPips 50 空头止盈点数,为 0 表示禁用。

所有数值参数都通过 StrategyParam<T> 暴露,并在适用时标记为可优化,方便在 StockSharp 中进行回测与参数搜索。

实现细节

  • 策略使用高阶的 SubscribeCandles().Bind(...) 接口,同时获取K线、SMA 与动量值,无需手动维护指标缓冲区。
  • 动量阈值完全沿用 MQL5 脚本(100 为分界),当价格超出换算后的止损/止盈距离时,会以市价单离场,行为与原策略一致。
  • 通过缓存上一根K线的收盘价来判断价格动能,避免创建额外的历史集合,符合项目的性能要求。
  • 当运行环境支持图表时,可使用 DrawCandlesDrawIndicatorDrawOwnTrades 快速可视化信号与交易。

使用建议

  • 默认参数即为作者的原始配置,如需在不同市场使用,可调整 CandleType 与指标周期。
  • 若交易的品种拥有非常规的最小报价单位,请确认换算出的点值是否合理,再设定止损/止盈距离。
  • 策略设计为同一时间仅持有一笔仓位。外部手动交易或同品种的其他策略可能会干扰其平仓逻辑。
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>
/// Russian20 Momentum MA strategy. Combines SMA filter with momentum confirmation.
/// </summary>
public class Russian20MomentumMaStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _maPeriod;
	private readonly StrategyParam<int> _momPeriod;

	private decimal? _prevMom;

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public int MaPeriod
	{
		get => _maPeriod.Value;
		set => _maPeriod.Value = value;
	}

	public int MomPeriod
	{
		get => _momPeriod.Value;
		set => _momPeriod.Value = value;
	}

	public Russian20MomentumMaStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe", "General");

		_maPeriod = Param(nameof(MaPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("MA Period", "SMA period for trend filter", "Indicators");

		_momPeriod = Param(nameof(MomPeriod), 10)
			.SetGreaterThanZero()
			.SetDisplay("Momentum Period", "Momentum lookback", "Indicators");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevMom = null;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_prevMom = null;

		var sma = new SimpleMovingAverage { Length = MaPeriod };
		var mom = new Momentum { Length = MomPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(sma, mom, ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, sma);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle, decimal maVal, decimal momVal)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!IsFormedAndOnlineAndAllowTrading())
		{
			_prevMom = momVal;
			return;
		}

		if (_prevMom == null)
		{
			_prevMom = momVal;
			return;
		}

		var close = candle.ClosePrice;

		// Price above MA + momentum crosses above zero → buy
		if (close > maVal && _prevMom.Value <= 100m && momVal > 100m && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();
			BuyMarket();
		}
		// Price below MA + momentum crosses below zero → sell
		else if (close < maVal && _prevMom.Value >= 100m && momVal < 100m && Position >= 0)
		{
			if (Position > 0)
				SellMarket();
			SellMarket();
		}

		_prevMom = momVal;
	}
}