在 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 指标的数值与每根已完成的蜡烛一同送达。

交易逻辑

  1. 订阅参数 CandleType 指定的蜡烛序列(默认日线),并用 ChannelPeriod 设置 DonchianChannels 指标长度。
  2. 每当一根蜡烛收盘:
    • 若持有多头,当前下轨抬升时将止损同步到新的下轨;若蜡烛最低价触及止损,立即平仓。
    • 若持有空头,当前上轨下移时将止损同步到新的上轨;若蜡烛最高价触及止损,立即平仓。
    • 若无持仓,若距离上一次进场不足 TradeCooldown,则跳过信号。
    • 若上一根蜡烛的 Donchian 下轨高于前一根蜡烛的下轨,判定下轨拐头向上,市价买入并把初始止损放在当前下轨。
    • 若上一根蜡烛的 Donchian 上轨低于前一根蜡烛的上轨,判定上轨拐头向下,市价卖出并把初始止损放在当前上轨。
  3. 持仓期间持续跟踪通道,直到价格穿越止损水平为止,仓位随即关闭。

参数

名称 默认值 说明
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;
	}
}