Stochastic ダイナミックゾーン戦略
Stochastic Dynamic Zones 戦略は、Stochastic オシレーターと動的な買われすぎ/売られすぎゾーンを中心に構築されています。
テストでは年間平均リターン約 52% を示しています。暗号通貨市場で最もよく機能します。
インジケーターがイントラデイ (5m) データ上のトレンド変化を Stochastic が確認したときにシグナルが発動します。これにより、この手法はアクティブトレーダーに適しています。
ストップは ATR の倍数と StochPeriod、StochKPeriod などの要素に依存します。リスクとリワードのバランスをとるためにこれらのデフォルト値を調整してください。
詳細
- エントリー条件: インジケーター条件については実装を参照してください。
- ロング/ショート: 両方。
- エグジット条件: 反対のシグナルまたはストップロジック。
- ストップ: はい、インジケーターベースの計算を使用。
- デフォルト値:
StochPeriod = 14StochKPeriod = 3StochDPeriod = 3LookbackPeriod = 20StandardDeviationFactor = 2.0mCandleType = TimeSpan.FromMinutes(5).TimeFrame()
- フィルター:
- カテゴリ: トレンドフォロー
- 方向: 両方
- インジケーター: Stochastic
- ストップ: はい
- 複雑さ: 中級
- 時間軸: イントラデイ (5m)
- 季節性: いいえ
- ニューラルネットワーク: いいえ
- ダイバージェンス: いいえ
- リスクレベル: 中
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// Strategy based on Stochastic Oscillator with dynamic overbought and oversold zones.
/// </summary>
public class StochasticWithDynamicZonesStrategy : Strategy
{
private readonly StrategyParam<int> _stochKPeriod;
private readonly StrategyParam<int> _stochDPeriod;
private readonly StrategyParam<int> _lookbackPeriod;
private readonly StrategyParam<decimal> _stdDevFactor;
private readonly StrategyParam<int> _signalCooldownBars;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevStochK;
private decimal _stochSum;
private decimal _stochSqSum;
private int _stochCount;
private int _cooldownRemaining;
private DateTimeOffset? _lastEntryTime;
private bool _wasBelowOversold;
private readonly Queue<decimal> _stochQueue = new();
public int StochKPeriod
{
get => _stochKPeriod.Value;
set => _stochKPeriod.Value = value;
}
public int StochDPeriod
{
get => _stochDPeriod.Value;
set => _stochDPeriod.Value = value;
}
public int LookbackPeriod
{
get => _lookbackPeriod.Value;
set => _lookbackPeriod.Value = value;
}
public decimal StdDevFactor
{
get => _stdDevFactor.Value;
set => _stdDevFactor.Value = value;
}
public int SignalCooldownBars
{
get => _signalCooldownBars.Value;
set => _signalCooldownBars.Value = value;
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public StochasticWithDynamicZonesStrategy()
{
_stochKPeriod = Param(nameof(StochKPeriod), 14)
.SetDisplay("Stoch %K Period", "Smoothing period for %K", "Indicators");
_stochDPeriod = Param(nameof(StochDPeriod), 3)
.SetDisplay("Stoch %D Period", "Smoothing period for %D", "Indicators");
_lookbackPeriod = Param(nameof(LookbackPeriod), 40)
.SetDisplay("Lookback Period", "Period for dynamic zones", "Indicators");
_stdDevFactor = Param(nameof(StdDevFactor), 3.0m)
.SetDisplay("StdDev Factor", "Factor for dynamic zones", "Indicators");
_signalCooldownBars = Param(nameof(SignalCooldownBars), 240)
.SetDisplay("Signal Cooldown", "Bars to wait between signals", "Trading")
.SetGreaterThanZero();
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevStochK = 50m;
_stochSum = 0m;
_stochSqSum = 0m;
_stochCount = 0;
_cooldownRemaining = 0;
_lastEntryTime = null;
_wasBelowOversold = false;
_stochQueue.Clear();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevStochK = 50m;
_stochSum = 0m;
_stochSqSum = 0m;
_stochCount = 0;
_cooldownRemaining = 0;
_lastEntryTime = null;
_wasBelowOversold = false;
_stochQueue.Clear();
var stochastic = new StochasticOscillator
{
K = { Length = StochKPeriod },
D = { Length = StochDPeriod },
};
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(stochastic, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue stochValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!stochValue.IsFormed)
return;
var stochTyped = (StochasticOscillatorValue)stochValue;
if (stochTyped.K is not decimal stochK)
return;
_stochQueue.Enqueue(stochK);
_stochSum += stochK;
_stochSqSum += stochK * stochK;
_stochCount++;
if (_stochCount > LookbackPeriod)
{
var removed = _stochQueue.Dequeue();
_stochSum -= removed;
_stochSqSum -= removed * removed;
_stochCount = LookbackPeriod;
}
if (_stochCount < LookbackPeriod)
{
_prevStochK = stochK;
return;
}
if (_cooldownRemaining > 0)
_cooldownRemaining--;
var average = _stochSum / _stochCount;
var variance = (_stochSqSum / _stochCount) - (average * average);
var stdDev = variance <= 0m ? 0m : (decimal)Math.Sqrt((double)variance);
var dynamicOversold = Math.Max(10m, average - StdDevFactor * stdDev);
var entryOversold = Math.Min(dynamicOversold, 10m);
var isReversingUp = stochK > _prevStochK;
if (Position > 0 && stochK >= 50m)
{
SellMarket();
_cooldownRemaining = SignalCooldownBars;
}
else if (_cooldownRemaining == 0 && !HasEntryToday(candle) && _wasBelowOversold && stochK >= entryOversold && isReversingUp && Position == 0)
{
BuyMarket();
_cooldownRemaining = SignalCooldownBars;
_lastEntryTime = candle.CloseTime != default ? candle.CloseTime : candle.OpenTime;
}
_wasBelowOversold = stochK < entryOversold;
_prevStochK = stochK;
}
private bool HasEntryToday(ICandleMessage candle)
{
if (!_lastEntryTime.HasValue)
return false;
var candleTime = candle.CloseTime != default ? candle.CloseTime : candle.OpenTime;
return (candleTime.Date - _lastEntryTime.Value.Date).TotalDays < 3;
}
}
import clr
import math
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from System.Collections.Generic import Queue
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import StochasticOscillator
from StockSharp.Algo.Strategies import Strategy
class stochastic_with_dynamic_zones_strategy(Strategy):
"""
Strategy based on Stochastic Oscillator with dynamic overbought and oversold zones.
"""
def __init__(self):
super(stochastic_with_dynamic_zones_strategy, self).__init__()
self._stoch_k_period = self.Param("StochKPeriod", 14) \
.SetDisplay("Stoch %K Period", "Smoothing period for %K", "Indicators")
self._stoch_d_period = self.Param("StochDPeriod", 3) \
.SetDisplay("Stoch %D Period", "Smoothing period for %D", "Indicators")
self._lookback_period = self.Param("LookbackPeriod", 40) \
.SetDisplay("Lookback Period", "Period for dynamic zones", "Indicators")
self._std_dev_factor = self.Param("StdDevFactor", 3.0) \
.SetDisplay("StdDev Factor", "Factor for dynamic zones", "Indicators")
self._signal_cooldown_bars = self.Param("SignalCooldownBars", 240) \
.SetDisplay("Signal Cooldown", "Bars to wait between signals", "Trading") \
.SetGreaterThanZero()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._prev_stoch_k = 50.0
self._stoch_sum = 0.0
self._stoch_sq_sum = 0.0
self._stoch_count = 0
self._cooldown_remaining = 0
self._last_entry_time = None
self._was_below_oversold = False
self._stoch_queue = []
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(stochastic_with_dynamic_zones_strategy, self).OnReseted()
self._prev_stoch_k = 50.0
self._stoch_sum = 0.0
self._stoch_sq_sum = 0.0
self._stoch_count = 0
self._cooldown_remaining = 0
self._last_entry_time = None
self._was_below_oversold = False
self._stoch_queue = []
def OnStarted2(self, time):
super(stochastic_with_dynamic_zones_strategy, self).OnStarted2(time)
self._prev_stoch_k = 50.0
self._stoch_sum = 0.0
self._stoch_sq_sum = 0.0
self._stoch_count = 0
self._cooldown_remaining = 0
self._last_entry_time = None
self._was_below_oversold = False
self._stoch_queue = []
stochastic = StochasticOscillator()
stochastic.K.Length = int(self._stoch_k_period.Value)
stochastic.D.Length = int(self._stoch_d_period.Value)
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(stochastic, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _process_candle(self, candle, stoch_value):
if candle.State != CandleStates.Finished:
return
if not stoch_value.IsFormed:
return
stoch_k_val = stoch_value.K
if stoch_k_val is None:
return
stoch_k = float(stoch_k_val)
lookback = int(self._lookback_period.Value)
self._stoch_queue.append(stoch_k)
self._stoch_sum += stoch_k
self._stoch_sq_sum += stoch_k * stoch_k
self._stoch_count += 1
if self._stoch_count > lookback:
removed = self._stoch_queue.pop(0)
self._stoch_sum -= removed
self._stoch_sq_sum -= removed * removed
self._stoch_count = lookback
if self._stoch_count < lookback:
self._prev_stoch_k = stoch_k
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
average = self._stoch_sum / self._stoch_count
variance = (self._stoch_sq_sum / self._stoch_count) - (average * average)
std_dev = 0.0 if variance <= 0 else math.sqrt(variance)
sdf = float(self._std_dev_factor.Value)
dynamic_oversold = max(10.0, average - sdf * std_dev)
entry_oversold = min(dynamic_oversold, 10.0)
is_reversing_up = stoch_k > self._prev_stoch_k
cd = int(self._signal_cooldown_bars.Value)
if self.Position > 0 and stoch_k >= 50.0:
self.SellMarket()
self._cooldown_remaining = cd
elif self._cooldown_remaining == 0 and not self._has_entry_today(candle) and self._was_below_oversold and stoch_k >= entry_oversold and is_reversing_up and self.Position == 0:
self.BuyMarket()
self._cooldown_remaining = cd
close_time = candle.CloseTime
open_time = candle.OpenTime
try:
if close_time is not None and str(close_time) != "01/01/0001 00:00:00 +00:00":
self._last_entry_time = close_time
else:
self._last_entry_time = open_time
except:
self._last_entry_time = open_time
self._was_below_oversold = stoch_k < entry_oversold
self._prev_stoch_k = stoch_k
def _has_entry_today(self, candle):
if self._last_entry_time is None:
return False
close_time = candle.CloseTime
open_time = candle.OpenTime
try:
if close_time is not None and str(close_time) != "01/01/0001 00:00:00 +00:00":
candle_time = close_time
else:
candle_time = open_time
except:
candle_time = open_time
try:
diff_days = (candle_time.Date - self._last_entry_time.Date).TotalDays
return diff_days < 3
except:
return False
def CreateClone(self):
return stochastic_with_dynamic_zones_strategy()