在 GitHub 上查看

通道策略

本策略为 MetaTrader 4 平台上 Gordago "Channels" 专家顾问的直接移植版。策略通过一个极短周期的指数移动平均线(EMA)与三组基于 EMA 的通道(Envelope)组合来捕捉价格突破压缩区间的时刻。与原版一样,开仓后仅持有一笔仓位,并使用止损/止盈单以及可选的跟踪止损管理风险。

交易逻辑

  • 默认订阅 1 小时 K 线并计算:
    • 使用收盘价的快速 EMA(周期 2)。
    • 使用开盘价的第二条快速 EMA(周期 2),用于满足空头入场条件。
    • 使用收盘价的慢速 EMA(周期 220),并在此基础上构建 ±1.0%、±0.7%、±0.3% 三组通道。
  • 当基于收盘价的快速 EMA 满足以下任一条件时开多单:
    1. 向上穿越 1% 下轨。
    2. 向上穿越 0.7% 下轨。
    3. 连续两根 K 线位于 0.3% 下轨之下(超卖判定)。
    4. 向上穿越慢速 EMA。
    5. 向上穿越 0.3% 上轨。
    6. 向上穿越 0.7% 上轨。
  • 当基于开盘价的快速 EMA 满足以下任一条件时开空单:
    1. 向下穿越 1% 上轨。
    2. 向下穿越 0.7% 上轨。
    3. 向下穿越 0.3% 上轨。
    4. 向下穿越慢速 EMA。
    5. 向下穿越 0.3% 下轨。
    6. 向下穿越 0.7% 下轨。
  • 同一时间只允许持有一笔仓位,持仓期间忽略新的入场信号,这与原始 EA 的行为一致。

风险管理

  • 多空分别可以设置独立的止损与止盈距离(单位:点)。当数值为 0 时跳过对应保护单,保持与原始源码默认关闭的行为一致。
  • 可选的跟踪止损会在价格按设置的点数向有利方向移动后,自动上调(或下调)止损价位。
  • 平仓或停止策略时会自动撤销所有保护性订单。

参数说明

参数 说明
Candle Type 用于分析的 K 线周期(默认 1 小时)。
Volume 每次下单的手数。
Fast EMA / Slow EMA 快速与慢速 EMA 的周期。
Envelope 1%, Envelope 0.7%, Envelope 0.3% 三组通道的百分比宽度。
Buy Stop-Loss, Sell Stop-Loss 多空初始止损距离(点)。
Buy Take-Profit, Sell Take-Profit 多空固定止盈距离(点)。
Buy Trailing, Sell Trailing 多空跟踪止损距离(点)。
Use Trading Hours 是否启用交易时间过滤。
From Hour, To Hour 允许开仓的起止小时(包含端点)。若起始小时大于结束小时,则时间窗口跨越午夜。

使用提示

  1. 止损、止盈和跟踪止损的点数会乘以品种的 PriceStep 转换成价格差,请确保交易品种的最小变动价与设置匹配。
  2. 快速 EMA 的周期刻意保持在 2,以忠实还原原版逻辑,修改该值会显著改变信号频率。
  3. 原始 EA 还包含账户过滤和声音提示等平台相关功能,这些与交易逻辑无关,故在移植中省略。
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>
/// Channels strategy - EMA envelope breakout.
/// Buys when price breaks above fast EMA + offset, sells when it breaks below.
/// Uses slow EMA as trend filter.
/// </summary>
public class ChannelsStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<DataType> _candleType;

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

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public ChannelsStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 10)
			.SetDisplay("Fast EMA", "Fast EMA period", "Indicators");

		_slowPeriod = Param(nameof(SlowPeriod), 30)
			.SetDisplay("Slow EMA", "Slow EMA period", "Indicators");

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

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];
	protected override void OnReseted() { base.OnReseted(); _prevFast = 0m; _prevSlow = 0m; _hasPrev = false; }

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

		_hasPrev = false;

		var fast = new ExponentialMovingAverage { Length = FastPeriod };
		var slow = new ExponentialMovingAverage { Length = SlowPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(fast, slow, ProcessCandle)
			.Start();
	}

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

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

		var close = candle.ClosePrice;

		// Buy: price crosses above fast EMA, fast EMA above slow EMA (uptrend)
		if (close > fast && fast > slow && _prevFast <= _prevSlow && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();
			BuyMarket();
		}
		// Sell: price crosses below fast EMA, fast EMA below slow EMA (downtrend)
		else if (close < fast && fast < slow && _prevFast >= _prevSlow && Position >= 0)
		{
			if (Position > 0)
				SellMarket();
			SellMarket();
		}
		// Exit long when fast crosses below slow
		else if (Position > 0 && fast < slow && _prevFast >= _prevSlow)
		{
			SellMarket();
		}
		// Exit short when fast crosses above slow
		else if (Position < 0 && fast > slow && _prevFast <= _prevSlow)
		{
			BuyMarket();
		}

		_prevFast = fast;
		_prevSlow = slow;
	}
}