首页
/
策略示例
在 GitHub 上查看
Donchain Counter-Channel System
概述
Donchain Counter-Channel System 复刻了 Michal Rutka 在 2005 年《Currency Trader》杂志上发表的 MetaTrader 4 智能交易程序。策略在所选周期(默认日线)上计算 20 根长度的 Donchian 通道。当下轨在上一根 K 线上抬升时,系统判定空头力量减弱,于下一根 K 线的开盘附近以市价买入;当上轨在上一根 K 线上下压时,则视为多头动能衰退,于下一根 K 线市价卖出。保护性止损始终放在相反方向的 Donchian 边界上,与原始脚本的止损移动方法完全一致。
策略遵循“每天最多一笔交易”的限制,即在 24 小时冷却期内禁止新的入场。该移植版本利用 StockSharp 的高层 API,通过 BindEx 让 Donchian 指标的数值与每根已完成的蜡烛一同送达。
交易逻辑
订阅参数 CandleType 指定的蜡烛序列(默认日线),并用 ChannelPeriod 设置 DonchianChannels 指标长度。
每当一根蜡烛收盘:
若持有多头,当前下轨抬升时将止损同步到新的下轨;若蜡烛最低价触及止损,立即平仓。
若持有空头,当前上轨下移时将止损同步到新的上轨;若蜡烛最高价触及止损,立即平仓。
若无持仓,若距离上一次进场不足 TradeCooldown,则跳过信号。
若上一根蜡烛的 Donchian 下轨高于前一根蜡烛的下轨,判定下轨拐头向上,市价买入并把初始止损放在当前下轨。
若上一根蜡烛的 Donchian 上轨低于前一根蜡烛的上轨,判定上轨拐头向下,市价卖出并把初始止损放在当前上轨。
持仓期间持续跟踪通道,直到价格穿越止损水平为止,仓位随即关闭。
参数
名称
默认值
说明
Volume
1
多、空方向的下单手数。
ChannelPeriod
20
计算 Donchian 通道所用的蜡烛数量。
TradeCooldown
1 天
连续两次开仓之间必须等待的时间。
CandleType
日线
用于计算 Donchian 通道的蜡烛类型。
指标与数据
Donchian 通道 —— 提供上下轨,用于识别通道拐点并充当移动止损。
默认日线蜡烛 —— 提供 24 小时冷却所需的收盘时间,并驱动指标计算。
实现要点
通过 BindEx 获取 DonchianChannelsValue,一次性获得上下轨值,避免重复调用指标。
通过比较蜡烛最高价/最低价与存储的通道值来模拟止损触发,与原版 EA 在每根新 K 线上修改止损的行为保持一致。
冷却计时器只在新开仓时更新,确保一天内不会重复进场。
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>
/// Donchian counter-channel system.
/// Counter-trend: buys when lower channel turns up (reversal from support).
/// Sells when upper channel turns down (reversal from resistance).
/// </summary>
public class DonchainCounterChannelSystemStrategy : Strategy
{
private readonly StrategyParam<int> _channelPeriod;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevHigh;
private decimal _prevLow;
private decimal _prevPrevHigh;
private decimal _prevPrevLow;
private int _barCount;
public int ChannelPeriod { get => _channelPeriod.Value; set => _channelPeriod.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public DonchainCounterChannelSystemStrategy()
{
_channelPeriod = Param(nameof(ChannelPeriod), 20)
.SetDisplay("Channel Period", "Donchian channel lookback", "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(); _prevHigh = 0m; _prevLow = 0m; _prevPrevHigh = 0m; _prevPrevLow = 0m; _barCount = 0; }
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_barCount = 0;
var highest = new Highest { Length = ChannelPeriod };
var lowest = new Lowest { Length = ChannelPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(highest, lowest, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal highest, decimal lowest)
{
if (candle.State != CandleStates.Finished)
return;
_barCount++;
if (_barCount < 3)
{
_prevPrevHigh = _prevHigh;
_prevPrevLow = _prevLow;
_prevHigh = highest;
_prevLow = lowest;
return;
}
// Lower band turning up = support holding = buy signal
var lowerTurningUp = lowest > _prevLow && _prevLow <= _prevPrevLow;
// Upper band turning down = resistance holding = sell signal
var upperTurningDown = highest < _prevHigh && _prevHigh >= _prevPrevHigh;
if (lowerTurningUp && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
}
else if (upperTurningDown && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
}
_prevPrevHigh = _prevHigh;
_prevPrevLow = _prevLow;
_prevHigh = highest;
_prevLow = lowest;
}
}
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 Highest, Lowest
from StockSharp.Algo.Strategies import Strategy
class donchain_counter_channel_system_strategy(Strategy):
def __init__(self):
super(donchain_counter_channel_system_strategy, self).__init__()
self._channel_period = self.Param("ChannelPeriod", 20) \
.SetDisplay("Channel Period", "Donchian channel lookback", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Candle timeframe", "General")
self._prev_high = 0.0
self._prev_low = 0.0
self._prev_prev_high = 0.0
self._prev_prev_low = 0.0
self._bar_count = 0
@property
def channel_period(self):
return self._channel_period.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(donchain_counter_channel_system_strategy, self).OnReseted()
self._prev_high = 0.0
self._prev_low = 0.0
self._prev_prev_high = 0.0
self._prev_prev_low = 0.0
self._bar_count = 0
def OnStarted2(self, time):
super(donchain_counter_channel_system_strategy, self).OnStarted2(time)
self._bar_count = 0
highest = Highest()
highest.Length = self.channel_period
lowest = Lowest()
lowest.Length = self.channel_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(highest, lowest, self.process_candle).Start()
def process_candle(self, candle, highest, lowest):
if candle.State != CandleStates.Finished:
return
high_val = float(highest)
low_val = float(lowest)
self._bar_count += 1
if self._bar_count < 3:
self._prev_prev_high = self._prev_high
self._prev_prev_low = self._prev_low
self._prev_high = high_val
self._prev_low = low_val
return
lower_turning_up = low_val > self._prev_low and self._prev_low <= self._prev_prev_low
upper_turning_down = high_val < self._prev_high and self._prev_high >= self._prev_prev_high
if lower_turning_up and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif upper_turning_down and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._prev_prev_high = self._prev_high
self._prev_prev_low = self._prev_low
self._prev_high = high_val
self._prev_low = low_val
def CreateClone(self):
return donchain_counter_channel_system_strategy()