在 GitHub 上查看

Eliot Wave 策略(MQL4 "Eliot Wave I" 移植版)

概览

Eliot Wave Strategy 将 MetaTrader 4 专家顾问 "Eliot Wave I" 移植到 StockSharp API。 策略结合了快慢两条线性加权移动平均线(LWMA)、更高周期的动量确认以及 超慢周期的 MACD 过滤器,用来捕捉顺势的爆发行情并在同时控制风险。

核心指标

  • 快速 LWMA(默认 6):使用典型价格 (High + Low + Close) / 3 监控短期趋势。
  • 慢速 LWMA(默认 85):在同一时间框架上衡量大趋势方向。
  • Momentum(默认周期 14):在更高时间框架上计算,并转换为相对于 100 的偏离度, 任何值高于阈值都表示行情动量充足。
  • MACD (12, 26, 9):在非常慢的时间框架(默认约 30 天 K 线)上运行,用于过滤宏观趋势。 只有当 MACD 主线高于信号线时才允许做多,低于信号线时才允许做空。

参数

参数 说明 默认值
Base Candle LWMA 计算使用的基础时间框架。 15 分钟 K 线
Momentum Candle 动量确认使用的更高时间框架。 1 小时 K 线
MACD Candle MACD 趋势过滤使用的时间框架。 30 天 K 线
Fast LWMA 快速 LWMA 的周期。 6
Slow LWMA 慢速 LWMA 的周期。 85
Momentum Period 动量指标的回溯周期。 14
Momentum Buy Threshold 判定多头动量所需的最小偏离值。 0.3
Momentum Sell Threshold 判定空头动量所需的最小偏离值。 0.3
Stop Loss (pts) 以报价点为单位的止损距离。 20
Take Profit (pts) 以报价点为单位的止盈距离。 50
Trade Volume 每次下单的手数。 1 手
Max Position 允许持有的最大净仓位,等同于原 EA 的 Max_Trades 限制。 10 手

所有设置均通过 StrategyParam<T> 实现,因此可以直接在 Designer / Runner 中优化。

交易规则

  1. 趋势与结构过滤
    • 做多时要求快速 LWMA 位于慢速 LWMA 之上;做空时相反。
    • 最近两根已完成的 K 线需要互相重叠:做多时 Low[2] < High[1],做空时 Low[1] < High[2],以复制原 EA 的盘整判定。
  2. 动量确认
    • 将动量值转换为 abs(momentum - 100),如果最近三次计算中任意一次超过阈值, 认为当前存在有效动量。
  3. 宏观趋势过滤
    • 做多必须满足 MACD 主线高于信号线;做空必须低于信号线。
  4. 执行逻辑
    • 当所有条件同时满足时,策略以市价单进场,并在必要时平掉原持仓后再加上 设定的交易量。
    • 支持直接反手,确保行为与原 EA 保持一致。

风险管理

  • StartProtection 会自动应用止损和止盈距离(以点数表示)。
  • 额外的离场逻辑会在趋势或 MACD 条件反转时提前平仓,以贴近原脚本的保护逻辑。
  • Max Position 参数限制总持仓,防止出现超出原 EA 允许的并发交易数量。

与原版 EA 的差异

  • 移除了基于图形对象的趋势线检查和邮件/推送通知,因为这些是 MetaTrader 特有功能。
  • 原始脚本中复杂的保本与拖动止损机制被简化为 StartProtection。如需完全复制, 可以在此基础上进一步扩展。
  • 未实现按账户权益计算的强制清仓逻辑;风险通过固定止损和仓位上限来控制。

使用建议

  1. 确保行情源同时提供三个所需的 K 线序列(基础、动量、MACD)。
  2. 根据标的的波动率调整交易量、止损/止盈距离以及动量阈值。
  3. 如果多空表现明显不对称,可分别优化两个动量阈值。
  4. 启用内置的图表显示(K 线、LWMA、成交标记)有助于调试与回测。

该移植版在保持 StockSharp 高层 API 风格的同时,忠实复现了原 MQL4 专家顾问的 信号生成逻辑。

using System;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

public class EliotWaveStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public EliotWaveStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}