Trendless AG Histogram 策略
该策略基于 Trendless AG Histogram 指标交易反转。该指标测量价格与平滑移动平均之间的距离,并再次平滑结果,形成围绕零轴的直方图。当指标形成局部低点并开始上升时,可能出现向上的反转;当形成局部高点并开始下降时,可能出现向下的反转。
当直方图改变方向时开仓。如果指标在较低值后开始上升,则开多头;如果在较高值后开始下降,则开空头。可选的止损和止盈用于风险管理。
细节
- 入场条件:
- 做多:直方图当前值上升,且前一值低于更早的值。
- 做空:直方图当前值下降,且前一值高于更早的值。
- 多空方向:双向。
- 离场条件:
- 反向信号或触发止损/止盈。
- 止损:使用固定价格距离的止损和止盈。
- 默认值:
Fast Length= 7。Slow Length= 5。Stop Loss= 1000。Take Profit= 2000。Candle Type= 12 小时K线。
- 过滤器:
- 类型:趋势跟随。
- 方向:双向。
- 指标:基于移动平均的自定义指标。
- 止损:有。
- 复杂度:中等。
- 时间框架:中期。
- 季节性:无。
- 神经网络:无。
- 背离:有。
- 风险等级:中等。
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()