在 GitHub 上查看

Starter V6 Mod E

Starter V6 Mod E 是将 MetaTrader 4 专家顾问 Starter_v6mod_e_www_forex-instruments_info.mq4 移植到 StockSharp 高阶 API 的版本。该策略保留了原始系统的 Laguerre 极值判定、双 EMA 动量过滤、CCI 过滤以及 EMA 角度门限逻辑,同时按照 StockSharp 事件驱动模型实现交易执行。

交易逻辑

  • 趋势闸门:通过 34 周期 EMA 在可配置的起始/结束位移之间计算斜率,并将结果转换为点值。斜率为正时仅允许做多,斜率为负时仅允许做空,斜率近零则禁止新仓。
  • Laguerre 极值:根据原始递推公式实现的 Laguerre RSI(默认 γ = 0.7),输出范围 0–1。做多要求当前值与上一根 K 线的值均低于 Laguerre Oversold,做空要求两者均高于 Laguerre Overbought
  • EMA 动量过滤:基于 PRICE_MEDIAN 的 120 与 40 周期 EMA 必须同时上升方可做多,同时下降方可做空。
  • CCI 确认:14 周期 CCI 需低于 -CCI Threshold 才能做多,高于 +CCI Threshold 才能做空,对应 MQL 脚本中的 Alpha 过滤器。
  • 周五保护:超过 Friday Block Hour 后禁止开新仓,达到 Friday Exit Hour 时强制平掉所有持仓,以规避周末风险。

风险管理

  • 可配置的止损、止盈及移动止损距离(单位:点),复刻原策略的资金管理行为。
  • 移动止损会跟踪持仓以来的最大有利价格,一旦回撤超过设定值即平仓。
  • 所有平仓操作均通过高阶 API 的 SellMarket/BuyMarket 完成,符合平台要求。

参数

参数 说明
Volume 每次市价开仓的交易量。
StopLossPips 止损距离(点)。
TakeProfitPips 止盈距离(点)。
TrailingStopPips 移动止损距离(点,0 表示关闭)。
SlowEmaPeriod 慢速 EMA 的周期(使用 PRICE_MEDIAN)。
FastEmaPeriod 快速 EMA 的周期(使用 PRICE_MEDIAN)。
AngleEmaPeriod 用于角度检测的 EMA 周期。
AngleStartShift / AngleEndShift 计算 EMA 斜率时使用的起止位移。
AngleThreshold 允许交易所需的最小斜率(点值)。
CciPeriod / CciThreshold CCI 周期及绝对阈值。
LaguerreGamma Laguerre 振荡器的 γ 参数。
LaguerreOversold / LaguerreOverbought Laguerre 0–1 区间的超卖/超买阈值。
CandleType 使用的 K 线类型(默认 1 分钟)。
FridayBlockHour / FridayExitHour 控制周五风险限制的小时数(本地时间)。

转换说明

  • Laguerre 振荡器直接采用原始递推公式实现,保持 0–1 输出范围与 γ 平滑特性。
  • EMA 斜率通过历史 EMA 值的点值差异代替 MQL 中的角度计算函数,实现同样的趋势门限。
  • 原始脚本中的资金曲线/网格控制在该版本中未启用,保持与提供的 MT4 设置一致,同时符合 StockSharp 对显式仓位管理的推荐。
  • 通过 OnNewMyTrade 追踪成交价,用于计算移动止损与持仓最高/最低价。
namespace StockSharp.Samples.Strategies;

using System;
using System.Collections.Generic;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

/// <summary>
/// Starter V6 Mod E strategy using dual EMA crossover with Laguerre RSI filter.
/// Buy when fast EMA crosses above slow EMA and Laguerre is oversold.
/// Sell when fast EMA crosses below slow EMA and Laguerre is overbought.
/// </summary>
public class StarterV6ModEStrategy : Strategy
{
	private readonly StrategyParam<int> _slowEmaPeriod;
	private readonly StrategyParam<int> _fastEmaPeriod;
	private readonly StrategyParam<decimal> _laguerreGamma;
	private readonly StrategyParam<decimal> _laguerreOversold;
	private readonly StrategyParam<decimal> _laguerreOverbought;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _lagL0;
	private decimal _lagL1;
	private decimal _lagL2;
	private decimal _lagL3;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _prevLaguerre;
	private bool _hasPrev;

	public int SlowEmaPeriod
	{
		get => _slowEmaPeriod.Value;
		set => _slowEmaPeriod.Value = value;
	}

	public int FastEmaPeriod
	{
		get => _fastEmaPeriod.Value;
		set => _fastEmaPeriod.Value = value;
	}

	public decimal LaguerreGamma
	{
		get => _laguerreGamma.Value;
		set => _laguerreGamma.Value = value;
	}

	public decimal LaguerreOversold
	{
		get => _laguerreOversold.Value;
		set => _laguerreOversold.Value = value;
	}

	public decimal LaguerreOverbought
	{
		get => _laguerreOverbought.Value;
		set => _laguerreOverbought.Value = value;
	}

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

	public StarterV6ModEStrategy()
	{
		_slowEmaPeriod = Param(nameof(SlowEmaPeriod), 26)
			.SetDisplay("Slow EMA", "Slow EMA period", "Indicators");

		_fastEmaPeriod = Param(nameof(FastEmaPeriod), 12)
			.SetDisplay("Fast EMA", "Fast EMA period", "Indicators");

		_laguerreGamma = Param(nameof(LaguerreGamma), 0.7m)
			.SetDisplay("Laguerre Gamma", "Smoothing factor for Laguerre RSI", "Indicators");

		_laguerreOversold = Param(nameof(LaguerreOversold), 0.5m)
			.SetDisplay("Laguerre Oversold", "Oversold level (0-1)", "Indicators");

		_laguerreOverbought = Param(nameof(LaguerreOverbought), 0.5m)
			.SetDisplay("Laguerre Overbought", "Overbought level (0-1)", "Indicators");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_lagL0 = 0m;
		_lagL1 = 0m;
		_lagL2 = 0m;
		_lagL3 = 0m;
		_prevFast = 0m;
		_prevSlow = 0m;
		_prevLaguerre = 0m;
		_hasPrev = false;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_hasPrev = false;
		_lagL0 = _lagL1 = _lagL2 = _lagL3 = 0m;

		var fastEma = new ExponentialMovingAverage { Length = FastEmaPeriod };
		var slowEma = new ExponentialMovingAverage { Length = SlowEmaPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(fastEma, slowEma, ProcessCandle)
			.Start();
	}

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

		var laguerre = CalculateLaguerre(candle.ClosePrice);

		if (!_hasPrev)
		{
			_prevFast = fast;
			_prevSlow = slow;
			_prevLaguerre = laguerre;
			_hasPrev = true;
			return;
		}

		// EMA crossover signals
		var bullishCross = _prevFast <= _prevSlow && fast > slow;
		var bearishCross = _prevFast >= _prevSlow && fast < slow;

		// Long: fast EMA crosses above slow + Laguerre was oversold
		if (Position <= 0 && bullishCross && _prevLaguerre <= LaguerreOversold)
		{
			if (Position < 0)
				BuyMarket();
			BuyMarket();
		}
		// Short: fast EMA crosses below slow + Laguerre was overbought
		else if (Position >= 0 && bearishCross && _prevLaguerre >= LaguerreOverbought)
		{
			if (Position > 0)
				SellMarket();
			SellMarket();
		}

		_prevFast = fast;
		_prevSlow = slow;
		_prevLaguerre = laguerre;
	}

	private decimal CalculateLaguerre(decimal price)
	{
		var gamma = LaguerreGamma;

		var l0Prev = _lagL0;
		var l1Prev = _lagL1;
		var l2Prev = _lagL2;
		var l3Prev = _lagL3;

		_lagL0 = (1m - gamma) * price + gamma * l0Prev;
		_lagL1 = -gamma * _lagL0 + l0Prev + gamma * l1Prev;
		_lagL2 = -gamma * _lagL1 + l1Prev + gamma * l2Prev;
		_lagL3 = -gamma * _lagL2 + l2Prev + gamma * l3Prev;

		decimal cu = 0m;
		decimal cd = 0m;

		if (_lagL0 >= _lagL1) cu = _lagL0 - _lagL1; else cd = _lagL1 - _lagL0;
		if (_lagL1 >= _lagL2) cu += _lagL1 - _lagL2; else cd += _lagL2 - _lagL1;
		if (_lagL2 >= _lagL3) cu += _lagL2 - _lagL3; else cd += _lagL3 - _lagL2;

		var denom = cu + cd;
		return denom == 0m ? 0m : cu / denom;
	}
}