GitHub で見る

Trend Catcher Strategy

The Trend Catcher strategy combines Parabolic SAR with multiple simple moving averages to capture directional moves. It waits for price to cross the Parabolic SAR in the direction of the prevailing fast averages and then manages the position using dynamic stop-loss and trailing rules.

A trade is opened when the latest candle closes on the opposite side of the Parabolic SAR compared to the previous candle while fast averages confirm the move. The initial stop-loss is calculated from the distance to the SAR point and is bounded by minimum and maximum limits. Profit targets are defined as a multiple of the stop distance. After price advances by a specified amount, the stop moves to breakeven with a small offset and later trails the price.

Details

  • Entry Criteria:
    • Long: Close[0] > SAR && Close[1] < SAR_prev && FastMA > SlowMA && Close > FastMA2.
    • Short: Close[0] < SAR && Close[1] > SAR_prev && FastMA < SlowMA && Close < FastMA2.
  • Exit Criteria:
    • Stop-loss or take-profit levels are hit.
    • Trailing stop activated after profit threshold.
    • Opposite signal closes existing position.
  • Stops: Dynamic stop-loss from SAR with optional breakeven and trailing adjustments.
  • Default Values:
    • SlowMaPeriod = 200
    • FastMaPeriod = 50
    • FastMa2Period = 25
    • SarStep = 0.004
    • SarMax = 0.2
    • SlMultiplier = 1
    • TpMultiplier = 1
    • MinStopLoss = 10
    • MaxStopLoss = 200
    • ProfitLevel = 500
    • BreakevenOffset = 1
    • TrailingThreshold = 500
    • TrailingDistance = 10
  • Filters:
    • Category: Trend following
    • Direction: Both
    • Indicators: Parabolic SAR, SMA
    • Stops: Yes
    • Complexity: Medium
    • Timeframe: Short-term
    • Seasonality: No
    • Neural networks: No
    • Divergence: No
    • Risk level: Medium
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>
/// Parabolic SAR trend catching strategy.
/// Uses Parabolic SAR flip with MA trend filter for entries.
/// </summary>
public class TrendCatcherStrategy : Strategy
{
	private readonly StrategyParam<int> _slowMaPeriod;
	private readonly StrategyParam<int> _fastMaPeriod;
	private readonly StrategyParam<decimal> _sarStep;
	private readonly StrategyParam<decimal> _sarMax;
	private readonly StrategyParam<DataType> _candleType;

	private ExponentialMovingAverage _slowMa;
	private bool _isInitialized;
	private bool _isPriceAboveSarPrev;

	public int SlowMaPeriod { get => _slowMaPeriod.Value; set => _slowMaPeriod.Value = value; }
	public int FastMaPeriod { get => _fastMaPeriod.Value; set => _fastMaPeriod.Value = value; }
	public decimal SarStep { get => _sarStep.Value; set => _sarStep.Value = value; }
	public decimal SarMax { get => _sarMax.Value; set => _sarMax.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public TrendCatcherStrategy()
	{
		_slowMaPeriod = Param(nameof(SlowMaPeriod), 200)
			.SetGreaterThanZero()
			.SetDisplay("Slow MA Period", "Period of the slow moving average", "Moving Averages");

		_fastMaPeriod = Param(nameof(FastMaPeriod), 50)
			.SetGreaterThanZero()
			.SetDisplay("Fast MA Period", "Period of the fast moving average", "Moving Averages");

		_sarStep = Param(nameof(SarStep), 0.004m)
			.SetDisplay("SAR Step", "Parabolic SAR acceleration step", "Parabolic SAR");

		_sarMax = Param(nameof(SarMax), 0.2m)
			.SetDisplay("SAR Max", "Parabolic SAR maximum acceleration", "Parabolic SAR");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).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();
		_slowMa = default;
		_isInitialized = default;
		_isPriceAboveSarPrev = default;
	}

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

		_isInitialized = false;

		var sar = new ParabolicSar
		{
			Acceleration = SarStep,
			AccelerationStep = SarStep,
			AccelerationMax = SarMax
		};
		var fastMa = new ExponentialMovingAverage { Length = FastMaPeriod };
		_slowMa = new ExponentialMovingAverage { Length = SlowMaPeriod };

		Indicators.Add(_slowMa);

		var subscription = SubscribeCandles(CandleType);
		subscription
			.BindEx(sar, fastMa, ProcessCandle)
			.Start();

		StartProtection(
			takeProfit: new Unit(3, UnitTypes.Percent),
			stopLoss: new Unit(2, UnitTypes.Percent),
			isStopTrailing: true
		);
	}

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

		if (!sarValue.IsFormed || !fastMaValue.IsFormed)
			return;

		var sar = sarValue.ToDecimal();
		var fastValue = fastMaValue.ToDecimal();

		var slowResult = _slowMa.Process(candle.ClosePrice, candle.OpenTime, true);
		if (!slowResult.IsFormed)
			return;

		var slowValue = slowResult.ToDecimal();

		var isPriceAboveSar = candle.ClosePrice > sar;

		if (!_isInitialized)
		{
			_isPriceAboveSarPrev = isPriceAboveSar;
			_isInitialized = true;
			return;
		}

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		// Buy: SAR flips below price + fast MA above slow MA
		var buySignal = isPriceAboveSar && !_isPriceAboveSarPrev && fastValue > slowValue;
		// Sell: SAR flips above price + fast MA below slow MA
		var sellSignal = !isPriceAboveSar && _isPriceAboveSarPrev && fastValue < slowValue;

		if (buySignal && Position <= 0)
		{
			if (Position < 0) BuyMarket();
			BuyMarket();
		}
		else if (sellSignal && Position >= 0)
		{
			if (Position > 0) SellMarket();
			SellMarket();
		}

		_isPriceAboveSarPrev = isPriceAboveSar;
	}
}