在 GitHub 上查看

CandleStop Trailing 策略

概述

该策略基于 CandleStop 方法实现跟踪止损。它只分析已完成的 K 线,并且仅在对仓位有利的方向上移动止损水平。算法使用不同周期的唐奇安通道分别处理多头和空头仓位,非常适合与手动交易或其他入场策略配合使用。

参数

  • Up Trail Periods – 计算空头仓位跟踪最高价所使用的 K 线数量。
  • Down Trail Periods – 计算多头仓位跟踪最低价所使用的 K 线数量。
  • Candle Type – 用于分析的 K 线时间框架。

策略逻辑

  1. 等待已有仓位,策略本身不会主动开仓。
  2. 多头仓位:
    • 计算最近 Down Trail Periods 根 K 线的最低价。
    • 若该值高于当前止损,则将止损上移。
    • 价格触及或跌破止损时,以市价单平仓。
  3. 空头仓位:
    • 计算最近 Up Trail Periods 根 K 线的最高价。
    • 若该值低于当前止损,则将止损下移。
    • 价格触及或突破止损时,以市价单回补仓位。

使用说明

  • 基于 StockSharp 高级 API,依赖于对蜡烛图的订阅。
  • 适合用于保护手动开仓或其它策略产生的仓位。
  • 图表中可显示蜡烛、通道线以及已执行的交易,便于可视化。
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>
/// Trailing stop management based on CandleStop logic.
/// Uses EMA crossover for entries and Highest/Lowest channel for trailing exits.
/// </summary>
public class CandleStopTrailingStrategy : Strategy
{
	private readonly StrategyParam<int> _trailPeriod;
	private readonly StrategyParam<int> _fastEma;
	private readonly StrategyParam<int> _slowEma;
	private readonly StrategyParam<DataType> _candleType;

	private Highest _highest;
	private Lowest _lowest;
	private decimal _stopPrice;

	public int TrailPeriod { get => _trailPeriod.Value; set => _trailPeriod.Value = value; }
	public int FastEma { get => _fastEma.Value; set => _fastEma.Value = value; }
	public int SlowEma { get => _slowEma.Value; set => _slowEma.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public CandleStopTrailingStrategy()
	{
		_trailPeriod = Param(nameof(TrailPeriod), 5)
			.SetDisplay("Trail Period", "Look-back for channel trailing", "Parameters");

		_fastEma = Param(nameof(FastEma), 10)
			.SetDisplay("Fast EMA", "Fast EMA period", "Parameters");

		_slowEma = Param(nameof(SlowEma), 30)
			.SetDisplay("Slow EMA", "Slow EMA period", "Parameters");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Candle type for analysis", "General");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_highest = default;
		_lowest = default;
		_stopPrice = 0m;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		var fast = new ExponentialMovingAverage { Length = FastEma };
		var slow = new ExponentialMovingAverage { Length = SlowEma };
		_highest = new Highest { Length = TrailPeriod };
		_lowest = new Lowest { Length = TrailPeriod };

		Indicators.Add(_highest);
		Indicators.Add(_lowest);

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(fast, slow, (candle, fastVal, slowVal) =>
			{
				if (candle.State != CandleStates.Finished)
					return;

				var highResult = _highest.Process(candle);
				var lowResult = _lowest.Process(candle);

				if (!highResult.IsFormed || !lowResult.IsFormed)
					return;

				var upper = highResult.ToDecimal();
				var lower = lowResult.ToDecimal();

				// Entry logic based on EMA crossover
				if (Position == 0)
				{
					if (fastVal > slowVal && candle.ClosePrice > slowVal)
					{
						BuyMarket();
						_stopPrice = lower;
					}
					else if (fastVal < slowVal && candle.ClosePrice < slowVal)
					{
						SellMarket();
						_stopPrice = upper;
					}
				}
				else if (Position > 0)
				{
					if (lower > _stopPrice)
						_stopPrice = lower;

					if (candle.LowPrice <= _stopPrice)
					{
						SellMarket();
						_stopPrice = 0m;
					}
				}
				else if (Position < 0)
				{
					if (_stopPrice == 0 || upper < _stopPrice)
						_stopPrice = upper;

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

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, fast);
			DrawIndicator(area, slow);
			DrawOwnTrades(area);
		}
	}
}