一目均衡表とハースト指数
転換線と基準線の交差、一目均衡表の雲、対数リターンによるハーストフィルターを組み合わせたトレンド戦略です。シグナルには確定足を使います。
計算
Tenkan と Kijun は、それぞれの期間の (最高 High + 最低 Low) / 2 です。シフト前の先行スパン A は両線の平均、先行スパン B は SenkouSpanBPeriod 期間の高値と安値の中点です。StockSharp Ichimoku の因果的にシフトされた雲を使い、KijunPeriod のシフトと初期化バッファーに従います。将来の足は参照しません。
直近 HurstPeriod 個の終値から n = HurstPeriod - 1 個のリターン r[i] = ln(C[i] / C[i-1]) を求めます。平均を mu、X[k] = sum(r[i] - mu)、R = max(X) - min(X)、母標準偏差を S とすると、H = ln(R / S) / ln(n) です。StockSharp HurstExponent にこのリターン系列を渡し、最初の確定足から蓄積します。最低十一個の終値、完全な窓、ゼロでない分散が必要です。正でない終値で窓を初期化し直します。有効なハースト値がなければ新規注文を出しません。
売買条件
- 買い: ポジションなし、終値が両方の雲の線より厳密に上、Tenkan が Kijun を
<=から>へ上抜け、H > HurstThreshold、形成済みの正の ATR、待機終了。 - 売り: ポジションなし、終値が両線より厳密に下で逆向きの交差。ハースト、ATR、待機の条件は同じです。
- シグナル決済: 買いは終値が両線を下回るか Tenkan が下抜けたとき全量決済し、売りは対称条件で全量決済します。待機中も決済を行い、決済にエントリー用ハースト条件は不要です。
- 注文:
Volumeの成行で新規注文し、実際の残存ポジション全量で決済します。未完了注文があれば次のシグナル注文を待ちます。決済後に改めて新規注文できます。 - 待機: シグナル注文ごとに
SignalCooldownBarsを再設定し、その本数の確定足が経過すると次の新規注文が可能です。
保護
Wilder ATR の期間は AtrPeriod です。新規注文ごとにシグナル足の StopLossAtr * ATR を固定距離とし、実際の新規約定価格から損切り水準を設定します。ローカル保護は Level1 更新を処理し、シグナル足の間にも成行で決済できます。その後の ATR は距離を変えません。StopLossAtr = 0 はその実行では保護を無効にします。利確注文とトレーリングはありません。
初期値
| パラメーター | 初期値 |
|---|---|
TenkanPeriod |
9 |
KijunPeriod |
26 |
SenkouSpanBPeriod |
52 |
HurstPeriod |
終値 100 個 |
HurstThreshold |
0.5 |
SignalCooldownBars |
6 |
AtrPeriod |
14 |
StopLossAtr |
2 |
CandleType |
15 分 |
同梱の BTC/TON 履歴上で、独立計算した雲とリターンの R/S を使って各シグナル注文と約定を照合します。ハーストのウォームアップ、固定 ATR 距離、足の途中の実際の保護約定も別途検証します。
履歴での保護比較は Tenkan/Kijun/Senkou B 15/40/70 と HurstThreshold 0.45 を使い、初期距離 2 × ATR の実際の発動を確認します。初期値 9/26/52 のシグナルは別途照合します。保護試験の新規数量は 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()