在 GitHub 上查看

Three EMA 策略

概述

该策略复刻 MetaTrader "ThreeEMA" 专家顾问,通过三条指数移动平均线(EMA)的排列来判断趋势方向。策略在同一周期内计算快、中、慢三条 EMA,当出现 快线 > 中线 > 慢线 的多头结构时,建立或保持多头仓位;当出现 快线 < 中线 < 慢线 的空头结构时,建立或保持空头仓位。止损与止盈距离沿用了原始 MQL 参数,并以合约价格步长对应的点数表示。

MQL 原版逻辑

原始 EA 在每根已完成的蜡烛上读取三条 EMA 指标,并执行如下判断:

  • 开多 / 平空FastEMA > MediumEMA > SlowEMA
  • 开空 / 平多FastEMA < MediumEMA < SlowEMA
  • 止损与止盈以固定点差加减到开仓价。

所有订单均以市价方式提交,资金管理模块使用固定手数,拖曳止损功能被关闭。

StockSharp 实现细节

  • 采用高级蜡烛订阅 API,将三条 ExponentialMovingAverage 指标绑定到同一订阅,保证每根完成蜡烛同时提供所有 EMA 数值。
  • 仅在 CandleStates.Finished 的蜡烛上评估交易条件,避免未完成蜡烛带来的噪声。
  • 当检测到新的多/空结构时,策略会取消当前挂单、关闭反向持仓,并按需求方向发送市价单。
  • 通过 StartProtection 根据 PriceStep 将点数形式的止损和止盈转换为绝对价格偏移,保持与原版 EA 相同的保护行为。
  • 若可用图表区域,策略会绘制蜡烛及三条 EMA,便于人工核对信号。

参数

名称 默认值 说明
CandleType 1 分钟 计算 EMA 所使用的蜡烛周期。
FastPeriod 5 快速 EMA 周期,必须小于 MediumPeriod
MediumPeriod 12 中速 EMA 周期,必须介于快慢两条之间。
SlowPeriod 24 慢速 EMA 周期,必须为最大值。
StopLossPoints 400 止损距离(点)。若为 0 则不启用自动止损。
TakeProfitPoints 900 止盈距离(点)。若为 0 则不启用自动止盈。

使用建议

  1. 启动前请设置 Volume,以匹配希望交易的固定手数。
  2. 确认三条 EMA 的周期严格递增;若不满足条件,OnStarted 会抛出异常,与 MQL 原版验证一致。
  3. 该策略在多空结构切换时会立即反向开仓,因此在趋势频繁变化时会持续保持市场敞口。
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 ThreeEmaStrategy : 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 ThreeEmaStrategy()
	{
		_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;
	}
}