在 GitHub 上查看

Trade Channel

概述

Trade Channel 策略源自 MetaTrader 平台的 “TradeChannel” 智能交易系统。策略利用最近若干根已完成 K 线的最高价与最低价构建价格通道,当通道停止扩张而价格再次触及其边界时,反向开仓,期望价格回到通道内部。

核心思想

  • 使用 HighestLowest 指标构建类似 Donchian Channel 的价格带。
  • 只有在通道保持水平(没有新的高点或低点)时才允许入场。
  • 价格触及上轨时做空,触及下轨时做多,属于反向交易。
  • 初始止损设置在突破点外侧一个 Average True Range (ATR) 的距离。
  • 当交易进入盈利区间后,可按需启动移动止损。

参数

名称 说明 默认值 优化区间
Volume 下单手数/合约数量。 1 启用 (0.1 → 2.0,步长 0.1)
ChannelLength 计算通道边界所使用的已完成 K 线数量。 20 启用 (10 → 60,步长 5)
AtrPeriod ATR 指标周期,用于计算初始止损。 4 启用 (2 → 20,步长 2)
TrailingPoints 移动止损偏移量(以合约最小价位为单位),设为 0 表示关闭。 30 启用 (0 → 100,步长 10)
CandleType 参与计算的 K 线类型与周期。 30 分钟 K 线

交易逻辑

  1. 订阅参数指定的 K 线,并驱动 HighestLowestATR 三个指标。
  2. 等待所有指标形成完整数值。首个可用的指标值仅用于初始化通道状态,当根 K 线不进行交易。
  3. 每当产生新的已完成 K 线:
    • 更新通道上轨与下轨,并计算枢轴价 (上轨 + 下轨 + 收盘价) / 3
    • 判断当前上轨/下轨是否与上一根 K 线相同;上轨保持不变时允许做空,下轨保持不变时允许做多。
    • 做空信号: 上轨稳定,且 K 线最高价触及上轨,或收盘价位于枢轴价与上轨之间。
    • 做多信号: 下轨稳定,且 K 线最低价触及下轨,或收盘价位于下轨与枢轴价之间。
    • 策略同一时间仅持有一笔仓位,若已有持仓则忽略新的入场信号。
  4. 进场后:
    • 记录进场价格。
    • 做空的止损设置为 上轨 + ATR,做多的止损设置为 下轨 − ATR
  5. 持仓管理:
    • 多头离场条件:
      • 上轨保持水平且价格触及上轨;
      • K 线最低价跌破当前(初始或移动)止损价位。
    • 空头离场条件:
      • 下轨保持水平且价格触及下轨;
      • K 线最高价突破当前止损价位。
  6. 移动止损(当 TrailingPoints > 0 时):
    • 利用合约的 Security.Step 将参数转换为真实价格偏移;若该元数据缺失则直接使用参数值。
    • 多头在收盘价高于进场价达到偏移量后,将止损提至 收盘价 − 偏移量
    • 空头在收盘价低于进场价达到偏移量后,将止损下移至 收盘价 + 偏移量
    • 移动止损只会收紧保护价,不会回撤。

说明

  • 策略仅处理已完成的 K 线,以保持与原始 MQL 版本中 High[1]Low[1]Close[1] 的逻辑一致。
  • 比较当前与前一条通道边界时,会依据最小价位设置容差,避免浮点误差造成误判。
  • 若交易所未提供有效的 Security.Step,移动止损将退化为使用原始点值。
  • 原策略中的邮件通知与浮动手数功能依赖 MetaTrader 环境,因此在本转换版本中省略。
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>
/// Trade Channel Breakout strategy - Highest/Lowest channel breakout.
/// Buys when close crosses above channel midpoint.
/// Sells when close crosses below channel midpoint.
/// </summary>
public class TradeChannelBreakoutStrategy : Strategy
{
	private readonly StrategyParam<int> _channelPeriod;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _prevClose;
	private decimal _prevMid;
	private bool _hasPrev;

	public int ChannelPeriod { get => _channelPeriod.Value; set => _channelPeriod.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public TradeChannelBreakoutStrategy()
	{
		_channelPeriod = Param(nameof(ChannelPeriod), 20)
			.SetDisplay("Channel Period", "Highest/Lowest 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(); _prevClose = 0m; _prevMid = 0m; _hasPrev = false; }

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

		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;

		var close = candle.ClosePrice;
		var mid = (highest + lowest) / 2;

		if (!_hasPrev) { _prevClose = close; _prevMid = mid; _hasPrev = true; return; }

		if (_prevClose <= _prevMid && close > mid && Position <= 0)
		{
			if (Position < 0) BuyMarket();
			BuyMarket();
		}
		else if (_prevClose >= _prevMid && close < mid && Position >= 0)
		{
			if (Position > 0) SellMarket();
			SellMarket();
		}

		_prevClose = close; _prevMid = mid;
	}
}