Auf GitHub ansehen

Adaptive Trend Flow Strategy

The Adaptive Trend Flow strategy builds a volatility-based channel from fast and slow EMAs of the typical price. When price crosses the channel boundaries the internal trend flips. Long positions are opened when the trend turns upward and optional SMA and MACD filters agree. Positions are closed when the trend reverses downward.

Details

  • Entry Criteria:
    • Trend changes from down to up and filters confirm.
  • Long/Short: Long only.
  • Exit Criteria:
    • Trend changes from up to down.
  • Stops: None.
  • Default Values:
    • Length = 2
    • SmoothLength = 2
    • Sensitivity = 2.0
    • UseSmaFilter = true
    • SmaLength = 4
    • UseMacdFilter = true
    • MacdFastLength = 2
    • MacdSlowLength = 7
    • MacdSignalLength = 2
  • Filters:
    • Category: Trend following
    • Direction: Long
    • Indicators: EMA, SMA, MACD, Standard Deviation
    • Stops: No
    • Complexity: Medium
    • Timeframe: Any
    • 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>
/// Adaptive Trend Flow Strategy.
/// Uses EMA crossover with volatility channel breakout for entries.
/// </summary>
public class AdaptiveTrendFlowStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastLength;
	private readonly StrategyParam<int> _slowLength;
	private readonly StrategyParam<int> _atrLength;
	private readonly StrategyParam<decimal> _sensitivity;
	private readonly StrategyParam<int> _cooldownBars;

	private decimal _prevFast;
	private decimal _prevSlow;
	private int _cooldownRemaining;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int FastLength { get => _fastLength.Value; set => _fastLength.Value = value; }
	public int SlowLength { get => _slowLength.Value; set => _slowLength.Value = value; }
	public int AtrLength { get => _atrLength.Value; set => _atrLength.Value = value; }
	public decimal Sensitivity { get => _sensitivity.Value; set => _sensitivity.Value = value; }
	public int CooldownBars { get => _cooldownBars.Value; set => _cooldownBars.Value = value; }

	public AdaptiveTrendFlowStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles to use", "General");

		_fastLength = Param(nameof(FastLength), 10)
			.SetGreaterThanZero()
			.SetDisplay("Fast EMA Length", "Fast EMA period", "Trend");

		_slowLength = Param(nameof(SlowLength), 30)
			.SetGreaterThanZero()
			.SetDisplay("Slow EMA Length", "Slow EMA period", "Trend");

		_atrLength = Param(nameof(AtrLength), 14)
			.SetGreaterThanZero()
			.SetDisplay("ATR Length", "ATR period for volatility", "Trend");

		_sensitivity = Param(nameof(Sensitivity), 1.5m)
			.SetGreaterThanZero()
			.SetDisplay("Sensitivity", "ATR multiplier for channel", "Trend");

		_cooldownBars = Param(nameof(CooldownBars), 10)
			.SetDisplay("Cooldown Bars", "Bars between trades", "Risk");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevFast = 0;
		_prevSlow = 0;
		_cooldownRemaining = 0;
	}

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

		var fastEma = new ExponentialMovingAverage { Length = FastLength };
		var slowEma = new ExponentialMovingAverage { Length = SlowLength };
		var atr = new AverageTrueRange { Length = AtrLength };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(fastEma, slowEma, atr, ProcessCandle)
			.Start();

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

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

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		if (_prevFast == 0)
		{
			_prevFast = fastVal;
			_prevSlow = slowVal;
			return;
		}

		if (_cooldownRemaining > 0)
		{
			_cooldownRemaining--;
			_prevFast = fastVal;
			_prevSlow = slowVal;
			return;
		}

		var channel = atrVal * Sensitivity;
		var crossedAbove = _prevFast <= _prevSlow + channel && fastVal > slowVal + channel;
		var crossedBelow = _prevFast >= _prevSlow - channel && fastVal < slowVal - channel;

		if (crossedAbove && Position <= 0)
		{
			if (Position < 0)
				BuyMarket(Math.Abs(Position));
			BuyMarket(Volume);
			_cooldownRemaining = CooldownBars;
		}
		else if (crossedBelow && Position >= 0)
		{
			if (Position > 0)
				SellMarket(Math.Abs(Position));
			SellMarket(Volume);
			_cooldownRemaining = CooldownBars;
		}

		_prevFast = fastVal;
		_prevSlow = slowVal;
	}
}