Trendless AG Histogram Strategy
The strategy trades reversals detected by the Trendless AG Histogram indicator. The indicator measures the distance between price and a smoothed moving average and then smooths the result again, forming a histogram around zero. Local minima indicate potential upward reversals while local maxima suggest downward reversals.
Positions are opened when the histogram changes direction. If the indicator rises after being below previous values, a long position is opened. If it falls after being above previous values, a short position is opened. Optional stop-loss and take-profit levels manage risk.
Details
- Entry Criteria:
- Long: Histogram value is rising while the previous value was lower than its predecessor.
- Short: Histogram value is falling while the previous value was higher than its predecessor.
- Long/Short: Both directions.
- Exit Criteria:
- Opposite signal or stop-loss/take-profit levels.
- Stops: Fixed stop-loss and take-profit in price units.
- Default Values:
Fast Length= 7.Slow Length= 5.Stop Loss= 1000.Take Profit= 2000.Candle Type= 12-hour candles.
- Filters:
- Category: Trend following.
- Direction: Both.
- Indicators: Custom indicator built from moving averages.
- Stops: Yes.
- Complexity: Moderate.
- Timeframe: Medium-term.
- Seasonality: No.
- Neural networks: No.
- Divergence: Yes.
- Risk level: Medium.
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy based on the Trendless AG Histogram indicator.
/// Opens long when the histogram forms a trough and starts rising.
/// Opens short when it forms a peak and starts falling.
/// Includes optional stop-loss and take-profit levels.
/// </summary>
public class TrendlessAgHistStrategy : Strategy
{
private readonly StrategyParam<int> _fastLength;
private readonly StrategyParam<int> _slowLength;
private readonly StrategyParam<decimal> _stopLoss;
private readonly StrategyParam<decimal> _takeProfit;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<DataType> _candleType;
private TrendlessAgHist _indicator;
private decimal _prev1;
private decimal _prev2;
private bool _initialized;
private decimal _entryPrice;
private bool _isLong;
private int _barsSinceTrade;
/// <summary>
/// Fast smoothing period.
/// </summary>
public int FastLength { get => _fastLength.Value; set => _fastLength.Value = value; }
/// <summary>
/// Slow smoothing period.
/// </summary>
public int SlowLength { get => _slowLength.Value; set => _slowLength.Value = value; }
/// <summary>
/// Stop loss in price units.
/// </summary>
public decimal StopLoss { get => _stopLoss.Value; set => _stopLoss.Value = value; }
/// <summary>
/// Take profit in price units.
/// </summary>
public decimal TakeProfit { get => _takeProfit.Value; set => _takeProfit.Value = value; }
/// <summary>
/// Bars to wait after a completed trade.
/// </summary>
public int CooldownBars { get => _cooldownBars.Value; set => _cooldownBars.Value = value; }
/// <summary>
/// Candle type for calculations.
/// </summary>
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
/// <summary>
/// Initializes a new instance of <see cref="TrendlessAgHistStrategy"/>.
/// </summary>
public TrendlessAgHistStrategy()
{
_fastLength = Param(nameof(FastLength), 7)
.SetGreaterThanZero()
.SetDisplay("Fast Length", "Period of the first smoothing", "Parameters");
_slowLength = Param(nameof(SlowLength), 5)
.SetGreaterThanZero()
.SetDisplay("Slow Length", "Period of the second smoothing", "Parameters");
_stopLoss = Param(nameof(StopLoss), 1000m)
.SetGreaterThanZero()
.SetDisplay("Stop Loss", "Loss limit in price units", "Risk Management");
_takeProfit = Param(nameof(TakeProfit), 2000m)
.SetGreaterThanZero()
.SetDisplay("Take Profit", "Profit target in price units", "Risk Management");
_cooldownBars = Param(nameof(CooldownBars), 1)
.SetDisplay("Cooldown Bars", "Bars to wait after a completed trade", "Risk Management");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(12).TimeFrame())
.SetDisplay("Candle Type", "Working candle timeframe", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_indicator?.Reset();
_prev1 = 0m;
_prev2 = 0m;
_initialized = false;
_entryPrice = 0m;
_isLong = false;
_barsSinceTrade = CooldownBars;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_indicator = new TrendlessAgHist
{
FastLength = FastLength,
SlowLength = SlowLength
};
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_indicator, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _indicator);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal value)
{
if (candle.State != CandleStates.Finished)
return;
if (!_initialized)
{
_prev2 = _prev1;
_prev1 = value;
_initialized = true;
return;
}
if (_barsSinceTrade < CooldownBars)
_barsSinceTrade++;
if (_barsSinceTrade >= CooldownBars && _prev1 < _prev2 && value > _prev1 && _prev1 < 0m)
{
if (Position <= 0)
{
_entryPrice = candle.ClosePrice;
_isLong = true;
BuyMarket(Volume + Math.Abs(Position));
_barsSinceTrade = 0;
}
}
else if (_barsSinceTrade >= CooldownBars && _prev1 > _prev2 && value < _prev1 && _prev1 > 0m)
{
if (Position >= 0)
{
_entryPrice = candle.ClosePrice;
_isLong = false;
SellMarket(Volume + Math.Abs(Position));
_barsSinceTrade = 0;
}
}
_prev2 = _prev1;
_prev1 = value;
if (Position != 0 && _entryPrice != 0m)
CheckRisk(candle.ClosePrice);
}
private void CheckRisk(decimal price)
{
if (_isLong && Position > 0)
{
if (price <= _entryPrice - StopLoss || price >= _entryPrice + TakeProfit)
SellMarket(Position);
}
else if (!_isLong && Position < 0)
{
if (price >= _entryPrice + StopLoss || price <= _entryPrice - TakeProfit)
BuyMarket(Math.Abs(Position));
}
}
private class TrendlessAgHist : BaseIndicator
{
public int FastLength { get; set; } = 7;
public int SlowLength { get; set; } = 5;
private readonly ExponentialMovingAverage _fast = new() { Length = 7 };
private readonly ExponentialMovingAverage _slow = new() { Length = 5 };
protected override IIndicatorValue OnProcess(IIndicatorValue input)
{
var candle = input.GetValue<ICandleMessage>();
var price = candle.ClosePrice;
var fastResult = _fast.Process(new DecimalIndicatorValue(_fast, price, input.Time) { IsFinal = true });
var fastVal = fastResult.IsEmpty ? price : fastResult.ToDecimal();
var diff = price - fastVal;
var slowResult = _slow.Process(new DecimalIndicatorValue(_slow, diff, input.Time) { IsFinal = true });
var slowVal = slowResult.IsEmpty ? diff : slowResult.ToDecimal();
IsFormed = _fast.IsFormed && _slow.IsFormed;
return new DecimalIndicatorValue(this, slowVal, input.Time);
}
public override void Reset()
{
base.Reset();
_fast.Reset();
_slow.Reset();
}
}
}
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
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class trendless_ag_hist_strategy(Strategy):
def __init__(self):
super(trendless_ag_hist_strategy, self).__init__()
self._fast_length = self.Param("FastLength", 7) \
.SetDisplay("Fast Length", "Period of the first smoothing", "Parameters")
self._slow_length = self.Param("SlowLength", 5) \
.SetDisplay("Slow Length", "Period of the second smoothing", "Parameters")
self._stop_loss = self.Param("StopLoss", 1000.0) \
.SetDisplay("Stop Loss", "Loss limit in price units", "Risk Management")
self._take_profit = self.Param("TakeProfit", 2000.0) \
.SetDisplay("Take Profit", "Profit target in price units", "Risk Management")
self._cooldown_bars = self.Param("CooldownBars", 1) \
.SetDisplay("Cooldown Bars", "Bars to wait after a completed trade", "Risk Management")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(12))) \
.SetDisplay("Candle Type", "Working candle timeframe", "General")
self._fast_ema = None
self._slow_ema = None
self._prev1 = 0.0
self._prev2 = 0.0
self._initialized = False
self._entry_price = 0.0
self._is_long = False
self._bars_since_trade = 0
@property
def FastLength(self):
return self._fast_length.Value
@FastLength.setter
def FastLength(self, value):
self._fast_length.Value = value
@property
def SlowLength(self):
return self._slow_length.Value
@SlowLength.setter
def SlowLength(self, value):
self._slow_length.Value = value
@property
def StopLoss(self):
return self._stop_loss.Value
@StopLoss.setter
def StopLoss(self, value):
self._stop_loss.Value = value
@property
def TakeProfit(self):
return self._take_profit.Value
@TakeProfit.setter
def TakeProfit(self, value):
self._take_profit.Value = value
@property
def CooldownBars(self):
return self._cooldown_bars.Value
@CooldownBars.setter
def CooldownBars(self, value):
self._cooldown_bars.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def _compute_trendless(self, candle):
price = float(candle.ClosePrice)
fast_result = process_float(self._fast_ema, price, candle.OpenTime, True)
fast_val = price if fast_result.IsEmpty else float(fast_result)
diff = price - fast_val
slow_result = process_float(self._slow_ema, diff, candle.OpenTime, True)
slow_val = diff if slow_result.IsEmpty else float(slow_result)
return slow_val
def _check_risk(self, price):
if self._is_long and self.Position > 0:
if price <= self._entry_price - self.StopLoss or price >= self._entry_price + self.TakeProfit:
self.SellMarket(self.Position)
elif not self._is_long and self.Position < 0:
if price >= self._entry_price + self.StopLoss or price <= self._entry_price - self.TakeProfit:
self.BuyMarket(abs(self.Position))
def OnStarted2(self, time):
super(trendless_ag_hist_strategy, self).OnStarted2(time)
self._fast_ema = ExponentialMovingAverage()
self._fast_ema.Length = self.FastLength
self._slow_ema = ExponentialMovingAverage()
self._slow_ema.Length = self.SlowLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.ProcessCandle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
value = self._compute_trendless(candle)
if not self._initialized:
self._prev2 = self._prev1
self._prev1 = value
self._initialized = True
return
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev2 = self._prev1
self._prev1 = value
return
if self._bars_since_trade < self.CooldownBars:
self._bars_since_trade += 1
if (self._bars_since_trade >= self.CooldownBars
and self._prev1 < self._prev2
and value > self._prev1
and self._prev1 < 0.0):
if self.Position <= 0:
self._entry_price = float(candle.ClosePrice)
self._is_long = True
self.BuyMarket(self.Volume + abs(self.Position))
self._bars_since_trade = 0
elif (self._bars_since_trade >= self.CooldownBars
and self._prev1 > self._prev2
and value < self._prev1
and self._prev1 > 0.0):
if self.Position >= 0:
self._entry_price = float(candle.ClosePrice)
self._is_long = False
self.SellMarket(self.Volume + abs(self.Position))
self._bars_since_trade = 0
self._prev2 = self._prev1
self._prev1 = value
if self.Position != 0 and self._entry_price != 0.0:
self._check_risk(float(candle.ClosePrice))
def OnReseted(self):
super(trendless_ag_hist_strategy, self).OnReseted()
self._fast_ema = None
self._slow_ema = None
self._prev1 = 0.0
self._prev2 = 0.0
self._initialized = False
self._entry_price = 0.0
self._is_long = False
self._bars_since_trade = self.CooldownBars
def CreateClone(self):
return trendless_ag_hist_strategy()