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= 2SmoothLength= 2Sensitivity= 2.0UseSmaFilter= trueSmaLength= 4UseMacdFilter= trueMacdFastLength= 2MacdSlowLength= 7MacdSignalLength= 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;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class adaptive_trend_flow_strategy(Strategy):
def __init__(self):
super(adaptive_trend_flow_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._fast_length = self.Param("FastLength", 10) \
.SetGreaterThanZero() \
.SetDisplay("Fast EMA Length", "Fast EMA period", "Trend")
self._slow_length = self.Param("SlowLength", 30) \
.SetGreaterThanZero() \
.SetDisplay("Slow EMA Length", "Slow EMA period", "Trend")
self._atr_length = self.Param("AtrLength", 14) \
.SetGreaterThanZero() \
.SetDisplay("ATR Length", "ATR period for volatility", "Trend")
self._sensitivity = self.Param("Sensitivity", 1.5) \
.SetGreaterThanZero() \
.SetDisplay("Sensitivity", "ATR multiplier for channel", "Trend")
self._cooldown_bars = self.Param("CooldownBars", 10) \
.SetDisplay("Cooldown Bars", "Bars between trades", "Risk")
self._prev_fast = 0.0
self._prev_slow = 0.0
self._cooldown_remaining = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(adaptive_trend_flow_strategy, self).OnReseted()
self._prev_fast = 0.0
self._prev_slow = 0.0
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(adaptive_trend_flow_strategy, self).OnStarted2(time)
fast_ema = ExponentialMovingAverage()
fast_ema.Length = int(self._fast_length.Value)
slow_ema = ExponentialMovingAverage()
slow_ema.Length = int(self._slow_length.Value)
atr = AverageTrueRange()
atr.Length = int(self._atr_length.Value)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(fast_ema, slow_ema, atr, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, fast_ema)
self.DrawIndicator(area, slow_ema)
self.DrawOwnTrades(area)
def _on_process(self, candle, fast_val, slow_val, atr_val):
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
fast_v = float(fast_val)
slow_v = float(slow_val)
atr_v = float(atr_val)
if self._prev_fast == 0:
self._prev_fast = fast_v
self._prev_slow = slow_v
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
self._prev_fast = fast_v
self._prev_slow = slow_v
return
sens = float(self._sensitivity.Value)
cooldown = int(self._cooldown_bars.Value)
channel = atr_v * sens
crossed_above = self._prev_fast <= self._prev_slow + channel and fast_v > slow_v + channel
crossed_below = self._prev_fast >= self._prev_slow - channel and fast_v < slow_v - channel
if crossed_above and self.Position <= 0:
if self.Position < 0:
self.BuyMarket(Math.Abs(self.Position))
self.BuyMarket(self.Volume)
self._cooldown_remaining = cooldown
elif crossed_below and self.Position >= 0:
if self.Position > 0:
self.SellMarket(Math.Abs(self.Position))
self.SellMarket(self.Volume)
self._cooldown_remaining = cooldown
self._prev_fast = fast_v
self._prev_slow = slow_v
def CreateClone(self):
return adaptive_trend_flow_strategy()