在 GitHub 上查看

突破柱趋势策略

该策略利用 Parabolic SAR 指标识别趋势反转。在进入交易前,它会等待若干次失败的反转信号,然后顺着新的趋势方向开仓。止损和止盈距离可按点数或按入场价的百分比计算。

参数

  • Reversal Mode – 距离计算方式:点数或百分比。
  • Delta – 两次反转之间的最小价格变动。
  • Negative Signals – 开仓前需要出现的失败反转次数。
  • Stop Loss – 入场价到止损位的距离。
  • Take Profit – 入场价到止盈位的距离。
  • Candle Type – 用于计算的K线类型。

逻辑

  1. 订阅K线并计算 Parabolic SAR。
  2. 当指标方向反转且价格移动至少 Delta 时,记录该价格。
  3. 统计价格逆向移动的失败反转次数。
  4. 当计数达到 Negative Signals 时,按新趋势方向开仓。
  5. 每根K线根据 Reversal Mode 检查止损和止盈。
  6. 当趋势反向或触发风险限制时平仓。

该策略适用于趋势突破交易,可通过调整 delta、止损和止盈距离进行优化。

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>
/// Trend-following breakout strategy based on Parabolic SAR reversals.
/// Opens trades after negative reversals and uses percentage-based SL/TP.
/// </summary>
public class BreakoutBarsTrendStrategy : Strategy
{
	private readonly StrategyParam<int> _negatives;
	private readonly StrategyParam<decimal> _stopLossPct;
	private readonly StrategyParam<decimal> _takeProfitPct;
	private readonly StrategyParam<DataType> _candleType;

	private ParabolicSar _parabolic;
	private int _lastTrend; // -1, 0, 1
	private int _negativeCounter;

	public int Negatives
	{
		get => _negatives.Value;
		set => _negatives.Value = value;
	}

	public decimal StopLossPct
	{
		get => _stopLossPct.Value;
		set => _stopLossPct.Value = value;
	}

	public decimal TakeProfitPct
	{
		get => _takeProfitPct.Value;
		set => _takeProfitPct.Value = value;
	}

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

	public BreakoutBarsTrendStrategy()
	{
		_negatives = Param(nameof(Negatives), 1)
			.SetNotNegative()
			.SetDisplay("Negative Signals", "Negative reversals before entry", "General");

		_stopLossPct = Param(nameof(StopLossPct), 2m)
			.SetGreaterThanZero()
			.SetDisplay("Stop Loss %", "Stop loss percentage", "Risk");

		_takeProfitPct = Param(nameof(TakeProfitPct), 4m)
			.SetGreaterThanZero()
			.SetDisplay("Take Profit %", "Take profit percentage", "Risk");

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

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_parabolic = default;
		_lastTrend = 0;
		_negativeCounter = 0;
	}

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

		_parabolic = new ParabolicSar();

		Indicators.Add(_parabolic);

		var subscription = SubscribeCandles(CandleType);

		subscription
			.Bind(ProcessCandle)
			.Start();

		StartProtection(
			takeProfit: new Unit(TakeProfitPct, UnitTypes.Percent),
			stopLoss: new Unit(StopLossPct, UnitTypes.Percent),
			useMarketOrders: true);

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

	private void ProcessCandle(ICandleMessage candle)
	{
		if (candle.State != CandleStates.Finished)
			return;

		var sarResult = _parabolic.Process(candle);
		if (!sarResult.IsFormed)
			return;

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		var sarValue = sarResult.ToDecimal();
		var trend = sarValue < candle.ClosePrice ? 1 : -1;

		if (_lastTrend != 0 && _lastTrend != trend)
		{
			// Reversal detected
			if (trend == 1 && Position < 0)
				BuyMarket();
			else if (trend == -1 && Position > 0)
				SellMarket();

			_negativeCounter++;

			if (_negativeCounter > Negatives)
			{
				if (trend == 1 && Position <= 0)
				{
					BuyMarket();
				}
				else if (trend == -1 && Position >= 0)
				{
					SellMarket();
				}
				_negativeCounter = 0;
			}
		}

		_lastTrend = trend;
	}
}