在 GitHub 上查看

Trade Channel 策略

Trade Channel 策略 在 Donchian 价格通道周围交易突破与回调。当上轨保持不变且价格触及上轨或收于其下方但高于枢轴点时开多;相反条件下开空。止损设置在相反通道边界之外并加上 ATR 值,可选的移动止损用于跟随盈利。

参数

  • ChannelPeriod — Donchian 通道的长度。
  • AtrPeriod — 计算止损的 ATR 周期。
  • Trailing — 移动止损距离,价格单位(0 表示关闭)。
  • CandleType — 用于计算的蜡烛类型。
using System;
using System.Collections.Generic;

using Ecng.Common;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Trade Channel breakout strategy.
/// Uses Highest/Lowest channel and ATR for stop management.
/// </summary>
public class TradeChannelStrategy : Strategy
{
	private readonly StrategyParam<int> _channelPeriod;
	private readonly StrategyParam<int> _atrPeriod;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _prevUpper;
	private decimal _prevLower;
	private decimal _stopPrice;
	private bool _hasPrev;

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

	public TradeChannelStrategy()
	{
		_channelPeriod = Param(nameof(ChannelPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("Channel Period", "Donchian channel period", "Indicators");
		_atrPeriod = Param(nameof(AtrPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("ATR Period", "ATR length for stop calculation", "Indicators");
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles", "General");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
		=> [(Security, CandleType)];

	protected override void OnReseted()
	{
		base.OnReseted();
		_prevUpper = 0;
		_prevLower = 0;
		_stopPrice = 0;
		_hasPrev = false;
	}

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

		var highest = new Highest { Length = ChannelPeriod };
		var lowest = new Lowest { Length = ChannelPeriod };
		var atr = new StandardDeviation { Length = AtrPeriod };

		SubscribeCandles(CandleType)
			.Bind(highest, lowest, atr, ProcessCandle)
			.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal upper, decimal lower, decimal atrVal)
	{
		if (candle.State != CandleStates.Finished) return;

		if (!_hasPrev)
		{
			_prevUpper = upper;
			_prevLower = lower;
			_hasPrev = true;
			return;
		}

		if (atrVal <= 0)
		{
			_prevUpper = upper;
			_prevLower = lower;
			return;
		}

		var close = candle.ClosePrice;

		// Breakout above channel => long
		if (close >= _prevUpper && Position <= 0)
		{
			if (Position < 0) BuyMarket();
			BuyMarket();
			_stopPrice = lower - atrVal;
		}
		// Breakout below channel => short
		else if (close <= _prevLower && Position >= 0)
		{
			if (Position > 0) SellMarket();
			SellMarket();
			_stopPrice = upper + atrVal;
		}
		// Manage long
		else if (Position > 0)
		{
			// Trailing stop
			var newStop = close - atrVal * 2;
			if (newStop > _stopPrice) _stopPrice = newStop;

			if (candle.LowPrice <= _stopPrice)
			{
				SellMarket();
				_stopPrice = 0;
			}
		}
		// Manage short
		else if (Position < 0)
		{
			var newStop = close + atrVal * 2;
			if (newStop < _stopPrice) _stopPrice = newStop;

			if (candle.HighPrice >= _stopPrice)
			{
				BuyMarket();
				_stopPrice = 0;
			}
		}

		_prevUpper = upper;
		_prevLower = lower;
	}
}