Стратегия Trendless AG Histogram
Стратегия торгует развороты, определяемые индикатором Trendless AG Histogram. Индикатор измеряет расстояние между ценой и сглаженной скользящей средней, затем снова сглаживает результат, формируя гистограмму вокруг нуля. Локальные минимумы указывают на возможные развороты вверх, а локальные максимумы — на развороты вниз.
Позиции открываются при изменении направления гистограммы. Если после снижения гистограмма начинает расти, открывается длинная позиция. Если после роста гистограмма начинает падать, открывается короткая позиция. Опциональные уровни стоп-лосса и тейк-профита помогают управлять рисками.
Детали
- Условия входа:
- Покупка: значение гистограммы растет, а предыдущее значение было ниже ещё предыдущего.
- Продажа: значение гистограммы падает, а предыдущее значение было выше ещё предыдущего.
- Длинные/короткие позиции: обе стороны.
- Условия выхода:
- Противоположный сигнал или достижение стоп-лосса/тейк-профита.
- Стопы: фиксированные уровни стоп-лосса и тейк-профита в ценовых единицах.
- Значения по умолчанию:
Fast Length= 7.Slow Length= 5.Stop Loss= 1000.Take Profit= 2000.Candle Type= 12-часовые свечи.
- Фильтры:
- Категория: Следование тренду.
- Направление: Обе стороны.
- Индикаторы: Пользовательский индикатор на основе скользящих средних.
- Стопы: Да.
- Сложность: Средняя.
- Таймфрейм: Среднесрочный.
- Сезонность: Нет.
- Нейросети: Нет.
- Дивергенция: Да.
- Уровень риска: Средний.
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()