在 GitHub 上查看
通道策略
本策略为 MetaTrader 4 平台上 Gordago "Channels" 专家顾问的直接移植版。策略通过一个极短周期的指数移动平均线(EMA)与三组基于 EMA 的通道(Envelope)组合来捕捉价格突破压缩区间的时刻。与原版一样,开仓后仅持有一笔仓位,并使用止损/止盈单以及可选的跟踪止损管理风险。
交易逻辑
- 默认订阅 1 小时 K 线并计算:
- 使用收盘价的快速 EMA(周期 2)。
- 使用开盘价的第二条快速 EMA(周期 2),用于满足空头入场条件。
- 使用收盘价的慢速 EMA(周期 220),并在此基础上构建 ±1.0%、±0.7%、±0.3% 三组通道。
- 当基于收盘价的快速 EMA 满足以下任一条件时开多单:
- 向上穿越 1% 下轨。
- 向上穿越 0.7% 下轨。
- 连续两根 K 线位于 0.3% 下轨之下(超卖判定)。
- 向上穿越慢速 EMA。
- 向上穿越 0.3% 上轨。
- 向上穿越 0.7% 上轨。
- 当基于开盘价的快速 EMA 满足以下任一条件时开空单:
- 向下穿越 1% 上轨。
- 向下穿越 0.7% 上轨。
- 向下穿越 0.3% 上轨。
- 向下穿越慢速 EMA。
- 向下穿越 0.3% 下轨。
- 向下穿越 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 |
允许开仓的起止小时(包含端点)。若起始小时大于结束小时,则时间窗口跨越午夜。 |
使用提示
- 止损、止盈和跟踪止损的点数会乘以品种的
PriceStep 转换成价格差,请确保交易品种的最小变动价与设置匹配。
- 快速 EMA 的周期刻意保持在 2,以忠实还原原版逻辑,修改该值会显著改变信号频率。
- 原始 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;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class channels_strategy(Strategy):
def __init__(self):
super(channels_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 10) \
.SetDisplay("Fast EMA", "Fast EMA period", "Indicators")
self._slow_period = self.Param("SlowPeriod", 30) \
.SetDisplay("Slow EMA", "Slow EMA period", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Candle timeframe", "General")
self._prev_fast = 0.0
self._prev_slow = 0.0
self._has_prev = False
@property
def fast_period(self):
return self._fast_period.Value
@property
def slow_period(self):
return self._slow_period.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(channels_strategy, self).OnReseted()
self._prev_fast = 0.0
self._prev_slow = 0.0
self._has_prev = False
def OnStarted2(self, time):
super(channels_strategy, self).OnStarted2(time)
self._has_prev = False
fast = ExponentialMovingAverage()
fast.Length = self.fast_period
slow = ExponentialMovingAverage()
slow.Length = self.slow_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(fast, slow, self.process_candle).Start()
def process_candle(self, candle, fast, slow):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast)
slow_val = float(slow)
if not self._has_prev:
self._prev_fast = fast_val
self._prev_slow = slow_val
self._has_prev = True
return
close = float(candle.ClosePrice)
if close > fast_val and fast_val > slow_val and self._prev_fast <= self._prev_slow and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif close < fast_val and fast_val < slow_val and self._prev_fast >= self._prev_slow and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
elif self.Position > 0 and fast_val < slow_val and self._prev_fast >= self._prev_slow:
self.SellMarket()
elif self.Position < 0 and fast_val > slow_val and self._prev_fast <= self._prev_slow:
self.BuyMarket()
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return channels_strategy()