Estratégia de tendência que combina cruzamentos Tenkan/Kijun, a nuvem Ichimoku e um filtro Hurst sobre retornos logarítmicos. Os sinais usam velas fechadas.
Cálculo
Tenkan e Kijun são (maior High + menor Low) / 2 das respectivas janelas. Senkou A sem deslocamento é a média das duas linhas; Senkou B é o ponto médio de SenkouSpanBPeriod. São usadas as linhas causais deslocadas do StockSharp Ichimoku, com deslocamento KijunPeriod e seu buffer de inicialização; velas futuras não são lidas.
Os últimos HurstPeriod fechamentos geram n = HurstPeriod - 1 retornos r[i] = ln(C[i] / C[i-1]). Com média mu, X[k] = sum(r[i] - mu), R = max(X) - min(X) e desvio padrão populacional S, o filtro é H = ln(R / S) / ln(n). StockSharp HurstExponent processa esses retornos desde a primeira vela fechada. São necessários ao menos onze fechamentos, a janela completa e variância positiva. Um fechamento não positivo reinicia a janela; sem Hurst válido nenhuma entrada é permitida.
Negociação
- Compra: posição zerada, fechamento estritamente acima das duas linhas, Tenkan cruza Kijun de
<= para >, H > HurstThreshold, ATR formado e positivo e espera encerrada.
- Venda: posição zerada, fechamento estritamente abaixo das linhas e cruzamento inverso; valem os mesmos requisitos de Hurst, ATR e espera.
- Saída por sinal: fechar toda a compra abaixo das duas linhas ou no cruzamento descendente de Tenkan; fechar toda a venda nas condições inversas. As saídas permanecem ativas durante a espera e não exigem o filtro Hurst de entrada.
- Ordens: entrada a mercado de
Volume e saída pelo saldo real da posição. Uma ordem ainda não concluída bloqueia novas ordens por sinal. A posição é encerrada antes de uma entrada posterior.
- Espera: cada ordem por sinal reinicia
SignalCooldownBars; a próxima entrada pode ocorrer após esse número de velas fechadas.
Proteção
O ATR de Wilder usa AtrPeriod. Cada entrada fixa StopLossAtr * ATR da vela de sinal como distância ao preço real da execução. A proteção local processa Level1 e pode zerar a posição a mercado entre velas de sinal. Mudanças posteriores do ATR não alteram a distância. StopLossAtr = 0 desativa a proteção para a execução. Não há take-profit nem trailing stop.
Valores padrão
| Parâmetro |
Valor |
TenkanPeriod |
9 |
KijunPeriod |
26 |
SenkouSpanBPeriod |
52 |
HurstPeriod |
100 fechamentos |
HurstThreshold |
0.5 |
SignalCooldownBars |
6 |
AtrPeriod |
14 |
StopLossAtr |
2 |
CandleType |
15 minutos |
Os testes comparam cada ordem por sinal e suas execuções com nuvens e R/S de retornos calculados independentemente no arquivo BTC/TON incluído. Também verificam aquecimento do Hurst, distâncias ATR fixas e execuções protetoras reais dentro da vela.
A comparação de proteção usa períodos Tenkan/Kijun/Senkou B 15/40/70 e HurstThreshold 0.45 para acionar a distância padrão de 2 × ATR no arquivo. Os sinais padrão 9/26/52 são verificados separadamente. Os testes de proteção usam volume de entrada 3.
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// Ichimoku cloud crossings filtered by the rescaled range of logarithmic returns, with entry-frozen ATR protection.
/// </summary>
public class IchimokuHurstStrategy : Strategy
{
private readonly StrategyParam<int> _tenkanPeriod;
private readonly StrategyParam<int> _kijunPeriod;
private readonly StrategyParam<int> _senkouSpanBPeriod;
private readonly StrategyParam<int> _hurstPeriod;
private readonly StrategyParam<decimal> _hurstThreshold;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _signalCooldownBars;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<decimal> _stopLossAtr;
private Ichimoku _ichimoku;
private HurstExponent _hurst;
private AverageTrueRange _atr;
private decimal? _previousClose;
private decimal? _previousTenkan;
private decimal? _previousKijun;
private int _cooldownRemaining;
private Order _pendingOrder;
/// <summary>Tenkan rolling high/low midpoint period.</summary>
public int TenkanPeriod
{
get => _tenkanPeriod.Value;
set => _tenkanPeriod.Value = value;
}
/// <summary>Kijun midpoint period and cloud displacement.</summary>
public int KijunPeriod
{
get => _kijunPeriod.Value;
set => _kijunPeriod.Value = value;
}
/// <summary>Senkou B rolling high/low midpoint period.</summary>
public int SenkouSpanBPeriod
{
get => _senkouSpanBPeriod.Value;
set => _senkouSpanBPeriod.Value = value;
}
/// <summary>Number of closes supplying the Hurst log-return window.</summary>
public int HurstPeriod
{
get => _hurstPeriod.Value;
set => _hurstPeriod.Value = value;
}
/// <summary>Strict lower threshold for a formed Hurst estimate.</summary>
public decimal HurstThreshold
{
get => _hurstThreshold.Value;
set => _hurstThreshold.Value = value;
}
/// <summary>Signal candle type.</summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>Finished candles between signal orders; exits bypass this entry restriction.</summary>
public int SignalCooldownBars
{
get => _signalCooldownBars.Value;
set => _signalCooldownBars.Value = value;
}
/// <summary>Wilder ATR period.</summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>ATR stop distance multiplier; zero disables protection for the run.</summary>
public decimal StopLossAtr
{
get => _stopLossAtr.Value;
set => _stopLossAtr.Value = value;
}
/// <summary>Initializes the strategy parameters.</summary>
public IchimokuHurstStrategy()
{
_tenkanPeriod = Param(nameof(TenkanPeriod), 9)
.SetDisplay("Tenkan Period", "Tenkan rolling high/low midpoint period", "Ichimoku")
.SetOptimize(5, 15, 1);
_kijunPeriod = Param(nameof(KijunPeriod), 26)
.SetDisplay("Kijun Period", "Kijun midpoint period and cloud displacement", "Ichimoku")
.SetOptimize(20, 40, 2);
_senkouSpanBPeriod = Param(nameof(SenkouSpanBPeriod), 52)
.SetDisplay("Senkou Span B Period", "Senkou B rolling high/low midpoint period", "Ichimoku")
.SetOptimize(40, 70, 5);
_hurstPeriod = Param(nameof(HurstPeriod), 100)
.SetDisplay("Hurst Period", "Closes in the Hurst log-return window", "Hurst Exponent")
.SetOptimize(50, 200, 10);
_hurstThreshold = Param(nameof(HurstThreshold), 0.5m)
.SetDisplay("Hurst Threshold", "Strict lower threshold for the Hurst estimate", "Hurst Exponent")
.SetOptimize(0.45m, 0.6m, 0.05m);
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle Type", "Signal candle type", "General");
_signalCooldownBars = Param(nameof(SignalCooldownBars), 6)
.SetGreaterThanZero()
.SetDisplay("Signal Cooldown", "Finished candles between signal orders; exits remain active", "Trading");
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "Wilder ATR period", "Risk");
_stopLossAtr = Param(nameof(StopLossAtr), 2m)
.SetNotNegative()
.SetDisplay("ATR Stop", "Entry ATR multiplier; zero disables the stop for the run", "Risk");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
if (Security is not null)
yield return (Security, CandleType);
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_ichimoku = null;
_hurst = null;
_atr = null;
_previousClose = null;
_previousTenkan = null;
_previousKijun = null;
_cooldownRemaining = 0;
_pendingOrder = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
if (Security is null)
throw new InvalidOperationException("Security is not specified.");
if (HurstPeriod < 11)
throw new ArgumentOutOfRangeException(nameof(HurstPeriod), "At least eleven closes are required for ten log returns.");
_ichimoku = new Ichimoku
{
Tenkan = { Length = TenkanPeriod },
Kijun = { Length = KijunPeriod },
SenkouB = { Length = SenkouSpanBPeriod },
};
_hurst = new HurstExponent { Length = HurstPeriod - 1 };
_atr = new AverageTrueRange { Length = AtrPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.BindEx(_ichimoku, _atr, ProcessCandle, allowEmpty: true).Start();
foreach (var field in new[] { Level1Fields.BestBidPrice, Level1Fields.BestAskPrice })
{
var quotes = new Subscription(DataType.Level1, Security);
quotes.MarketData.BuildField = field;
SubscribeLevel1(quotes).Start();
}
var area = CreateChartArea();
if (area is not null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _ichimoku);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue cloudValue, IIndicatorValue atrValue)
{
if (candle.State != CandleStates.Finished)
return;
decimal? hurst = null;
if (candle.ClosePrice > 0m)
{
if (_previousClose is decimal previous)
{
var value = _hurst.Process((decimal)Math.Log((double)(candle.ClosePrice / previous)), candle.OpenTime, true);
if (!value.IsEmpty)
hurst = value.GetValue<decimal>();
}
_previousClose = candle.ClosePrice;
}
else
{
_previousClose = null;
_hurst.Reset();
}
if (!IsFormedAndOnlineAndAllowTrading()
|| cloudValue is not IIchimokuValue { Tenkan: decimal tenkan, Kijun: decimal kijun, SenkouA: decimal spanA, SenkouB: decimal spanB })
return;
var crossUp = _previousTenkan is decimal oldTenkan && _previousKijun is decimal oldKijun
&& oldTenkan <= oldKijun && tenkan > kijun;
var crossDown = _previousTenkan is decimal oldFast && _previousKijun is decimal oldSlow
&& oldFast >= oldSlow && tenkan < kijun;
_previousTenkan = tenkan;
_previousKijun = kijun;
if (_cooldownRemaining > 0)
_cooldownRemaining--;
if (_pendingOrder is { State: not (OrderStates.Done or OrderStates.Failed) })
return;
var above = candle.ClosePrice > Math.Max(spanA, spanB);
var below = candle.ClosePrice < Math.Min(spanA, spanB);
if (Position > 0m && (below || crossDown))
SubmitOrder(Sides.Sell, Position, "Ichimoku Hurst exit");
else if (Position < 0m && (above || crossUp))
SubmitOrder(Sides.Buy, Math.Abs(Position), "Ichimoku Hurst exit");
else if (Position == 0m && _cooldownRemaining == 0 && hurst is decimal estimate && estimate > HurstThreshold
&& !atrValue.IsEmpty && atrValue.GetValue<decimal>() > 0m && (above && crossUp || below && crossDown))
{
var distance = atrValue.GetValue<decimal>() * StopLossAtr;
StartProtection(new Unit(), new Unit(distance, UnitTypes.Absolute), useMarketOrders: true, isLocalStop: true);
SubmitOrder(crossUp ? Sides.Buy : Sides.Sell, Volume, "Ichimoku Hurst entry");
}
else
return;
_cooldownRemaining = SignalCooldownBars;
}
private void SubmitOrder(Sides side, decimal volume, string comment)
{
_pendingOrder = CreateOrder(side, 0m, volume);
_pendingOrder.Type = OrderTypes.Market;
_pendingOrder.Comment = comment;
RegisterOrder(_pendingOrder);
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
from System import ArgumentOutOfRangeException, Decimal, InvalidOperationException, Math, TimeSpan
from StockSharp.BusinessEntities import Subscription
from StockSharp.Messages import CandleStates, DataType, Level1Fields, OrderStates, OrderTypes, Sides, Unit, UnitTypes
from StockSharp.Algo.Indicators import AverageTrueRange, HurstExponent, Ichimoku, IndicatorHelper
from StockSharp.Algo.Strategies import Strategy
class ichimoku_hurst_exponent_strategy(Strategy):
"""Ichimoku cloud crossings, formed log-return Hurst filtering and frozen ATR protection."""
def __init__(self):
super(ichimoku_hurst_exponent_strategy, self).__init__()
self._tenkan_period = self.Param("TenkanPeriod", 9) \
.SetDisplay("Tenkan Period", "Tenkan rolling high/low midpoint period", "Ichimoku")
self._kijun_period = self.Param("KijunPeriod", 26) \
.SetDisplay("Kijun Period", "Kijun midpoint period and cloud displacement", "Ichimoku")
self._senkou_spanb_period = self.Param("SenkouSpanBPeriod", 52) \
.SetDisplay("Senkou Span B Period", "Senkou B rolling high/low midpoint period", "Ichimoku")
self._hurst_period = self.Param("HurstPeriod", 100) \
.SetDisplay("Hurst Period", "Closes in the Hurst log-return window", "Hurst Exponent")
self._hurst_threshold = self.Param("HurstThreshold", 0.5) \
.SetDisplay("Hurst Threshold", "Strict lower threshold for the Hurst estimate", "Hurst Exponent")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))) \
.SetDisplay("Candle Type", "Signal candle type", "General")
self._signal_cooldown_bars = self.Param("SignalCooldownBars", 6) \
.SetGreaterThanZero() \
.SetDisplay("Signal Cooldown", "Finished candles between signal orders; exits remain active", "Trading")
self._atr_period = self.Param("AtrPeriod", 14) \
.SetGreaterThanZero() \
.SetDisplay("ATR Period", "Wilder ATR period", "Risk")
self._stop_loss_atr = self.Param("StopLossAtr", 2.0) \
.SetNotNegative() \
.SetDisplay("ATR Stop", "Entry ATR multiplier; zero disables the stop for the run", "Risk")
self._ichimoku = None
self._hurst = None
self._atr = None
self._previous_close = None
self._previous_tenkan = None
self._previous_kijun = None
self._cooldown_remaining = 0
self._pending_order = None
@property
def candle_type(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
return [(self.Security, self.candle_type)] if self.Security is not None else []
def OnReseted(self):
super(ichimoku_hurst_exponent_strategy, self).OnReseted()
self._ichimoku = None
self._hurst = None
self._atr = None
self._previous_close = None
self._previous_tenkan = None
self._previous_kijun = None
self._cooldown_remaining = 0
self._pending_order = None
def OnStarted2(self, time):
super(ichimoku_hurst_exponent_strategy, self).OnStarted2(time)
if self.Security is None:
raise InvalidOperationException("Security is not specified.")
period = int(self._hurst_period.Value)
if period < 11:
raise ArgumentOutOfRangeException("HurstPeriod", "At least eleven closes are required for ten log returns.")
self._ichimoku = Ichimoku()
self._ichimoku.Tenkan.Length = int(self._tenkan_period.Value)
self._ichimoku.Kijun.Length = int(self._kijun_period.Value)
self._ichimoku.SenkouB.Length = int(self._senkou_spanb_period.Value)
self._hurst = HurstExponent()
self._hurst.Length = period - 1
self._atr = AverageTrueRange()
self._atr.Length = int(self._atr_period.Value)
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(self._ichimoku, self._atr, self._process_candle, True).Start()
for field in (Level1Fields.BestBidPrice, Level1Fields.BestAskPrice):
quotes = Subscription(DataType.Level1, self.Security)
quotes.MarketData.BuildField = field
self.SubscribeLevel1(quotes).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._ichimoku)
self.DrawOwnTrades(area)
def _process_candle(self, candle, cloud, atr_value):
if candle.State != CandleStates.Finished:
return
close = candle.ClosePrice
hurst = None
if close > 0:
if self._previous_close is not None:
log_return = Decimal(Math.Log(float(close / self._previous_close)))
value = IndicatorHelper.Process(self._hurst, log_return, candle.OpenTime, True)
if not value.IsEmpty:
hurst = value.GetValue[Decimal](None)
self._previous_close = close
else:
self._previous_close = None
self._hurst.Reset()
if not self.IsFormedAndOnlineAndAllowTrading():
return
tenkan, kijun, span_a, span_b = cloud.Tenkan, cloud.Kijun, cloud.SenkouA, cloud.SenkouB
if any(value is None for value in (tenkan, kijun, span_a, span_b)):
return
cross_up = self._previous_tenkan is not None and self._previous_kijun is not None \
and self._previous_tenkan <= self._previous_kijun and tenkan > kijun
cross_down = self._previous_tenkan is not None and self._previous_kijun is not None \
and self._previous_tenkan >= self._previous_kijun and tenkan < kijun
self._previous_tenkan, self._previous_kijun = tenkan, kijun
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
if self._pending_order is not None and self._pending_order.State not in (OrderStates.Done, OrderStates.Failed):
return
above = close > max(span_a, span_b)
below = close < min(span_a, span_b)
if self.Position > 0 and (below or cross_down):
self._submit_order(Sides.Sell, self.Position, "Ichimoku Hurst exit")
elif self.Position < 0 and (above or cross_up):
self._submit_order(Sides.Buy, Math.Abs(self.Position), "Ichimoku Hurst exit")
elif self.Position == 0 and self._cooldown_remaining == 0 \
and hurst is not None and hurst > Decimal(self._hurst_threshold.Value) \
and not atr_value.IsEmpty and atr_value.GetValue[Decimal](None) > 0 \
and ((above and cross_up) or (below and cross_down)):
distance = atr_value.GetValue[Decimal](None) * Decimal(self._stop_loss_atr.Value)
self.StartProtection(Unit(), Unit(distance, UnitTypes.Absolute), useMarketOrders=True, isLocalStop=True)
self._submit_order(Sides.Buy if cross_up else Sides.Sell, self.Volume, "Ichimoku Hurst entry")
else:
return
self._cooldown_remaining = int(self._signal_cooldown_bars.Value)
def _submit_order(self, side, volume, comment):
self._pending_order = self.CreateOrder(side, Decimal(0), volume)
self._pending_order.Type = OrderTypes.Market
self._pending_order.Comment = comment
self.RegisterOrder(self._pending_order)
def CreateClone(self):
return ichimoku_hurst_exponent_strategy()