Stochastic 買われすぎ/売られすぎリバーサル
この戦略はStochasticオシレーターの極端なレベルに反応します。%Kラインが売られすぎ圏に沈むと、システムは反発を期待します。一方、買われすぎの読みは下落を予感させる可能性があります。本手法は短いイントラデイ足で動作するため、シグナルが素早く届きます。
テストでは年平均リターン約73%を示しています。暗号資産市場で最もよく機能します。
選択した時間軸にサブスクライブした後、%Kと%Dラインを監視します。%Kが20を下回り回復し始めると強気のセットアップが形成されます。逆に、%Kが80を超えて下向きに転じると弱気のセットアップが現れます。固定パーセントのストップが両方向のリスクをコントロールします。
%Kラインが50レベルを再び越えると、モメンタムが反対方向にシフトしたことを示してポジションが決済されます。
詳細
- エントリー条件:
- ロング:
%K < 20で強気転換。 - ショート:
%K > 80で弱気転換。
- ロング:
- ロング/ショート: 両方。
- エグジット条件: %Kが50レベルを越えるかストップロス。
- ストップ: はい、
2%の距離。 - デフォルト値:
StochPeriod= 14KPeriod= 3DPeriod= 3CandleType= 5 minute
- フィルター:
- カテゴリ: オシレーター
- 方向: 両方
- インジケーター: Stochastic
- ストップ: はい
- 複雑さ: 基本
- 時間軸: イントラデイ
- 季節性: いいえ
- ニューラルネットワーク: いいえ
- ダイバージェンス: いいえ
- リスクレベル: 中
using System;
using System.Collections.Generic;
using System.Linq;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Fades confirmed stochastic extremes and exits on the return to neutral.
/// The native oscillator's D is the smoothed %K; a second SMA forms %D.
/// </summary>
public class StochasticOverboughtOversoldStrategy : Strategy
{
private const decimal ComparisonTolerance = 0.00000001m;
private readonly StrategyParam<int> _stochPeriod;
private readonly StrategyParam<int> _kPeriod;
private readonly StrategyParam<int> _dPeriod;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly Queue<decimal> _dWindow = new();
private decimal? _previousK;
private Order _pendingOrder;
public int StochPeriod { get => _stochPeriod.Value; set => _stochPeriod.Value = value; }
public int KPeriod { get => _kPeriod.Value; set => _kPeriod.Value = value; }
public int DPeriod { get => _dPeriod.Value; set => _dPeriod.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public decimal StopLossPercent { get => _stopLossPercent.Value; set => _stopLossPercent.Value = value; }
public StochasticOverboughtOversoldStrategy()
{
_stochPeriod = Param(nameof(StochPeriod), 14).SetGreaterThanZero()
.SetDisplay("Stochastic Period", "Raw high/low lookback", "Indicators");
_kPeriod = Param(nameof(KPeriod), 3).SetGreaterThanZero()
.SetDisplay("K Period", "SMA length of raw %K", "Indicators");
_dPeriod = Param(nameof(DPeriod), 3).SetGreaterThanZero()
.SetDisplay("D Period", "SMA length of smoothed %K", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Stochastic timeframe", "General");
_stopLossPercent = Param(nameof(StopLossPercent), 2m).SetNotNegative()
.SetDisplay("Stop Loss (%)", "Actual-fill percent stop; zero disables it.", "Protection");
OrderRegistering += order => _pendingOrder = order;
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType), (Security, DataType.Level1)];
protected override void OnReseted()
{
base.OnReseted();
ClearState();
}
private void ClearState()
{
_dWindow.Clear();
_previousK = null;
_pendingOrder = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ClearState();
StartProtection(new Unit(), new Unit(StopLossPercent, UnitTypes.Percent), useMarketOrders: true, isLocalStop: true);
foreach (var field in new[] { Level1Fields.BestBidPrice, Level1Fields.BestAskPrice })
{
var quotes = new Subscription(DataType.Level1, Security);
quotes.MarketData.BuildField = field;
SubscribeLevel1(quotes).Bind(ObserveProtectionQuote).Start();
}
var stochastic = new StochasticOscillator
{
K = { Length = StochPeriod },
D = { Length = KPeriod },
};
var candles = SubscribeCandles(CandleType);
candles.BindEx(stochastic, ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, candles);
DrawIndicator(area, stochastic);
DrawOwnTrades(area);
}
}
private void ObserveProtectionQuote(Level1ChangeMessage quote)
{
// Native actual-fill protection evaluates executable quotes between signal candles.
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue output)
{
if (candle.State != CandleStates.Finished || !output.Indicator.IsFormed ||
output is not StochasticOscillatorValue value || value.D is not decimal k)
return;
_dWindow.Enqueue(k);
if (_dWindow.Count > DPeriod)
_dWindow.Dequeue();
if (_dWindow.Count < DPeriod)
return;
var d = _dWindow.Average();
var previous = _previousK;
_previousK = k;
if (previous is not decimal prior || !IsFormedAndOnlineAndAllowTrading() ||
_pendingOrder is not null && _pendingOrder.State is not (OrderStates.Done or OrderStates.Failed))
return;
// The native rolling SMA can leave a sub-ulp remainder at exact levels (e.g. 50).
if (Position == 0m && prior < 20m - ComparisonTolerance &&
k > prior + ComparisonTolerance && k > d + ComparisonTolerance)
BuyMarket(Volume);
else if (Position == 0m && prior > 80m + ComparisonTolerance &&
k < prior - ComparisonTolerance && k < d - ComparisonTolerance)
SellMarket(Volume);
// %K crossing 50 either way closes, so a position whose entry bar already passed 50 exits on the cross back.
else if (Position > 0m && (prior < 50m - ComparisonTolerance) != (k < 50m - ComparisonTolerance))
SellMarket(Position);
else if (Position < 0m && (prior > 50m + ComparisonTolerance) != (k > 50m + ComparisonTolerance))
BuyMarket(Math.Abs(Position));
}
}
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 TimeSpan, Decimal, Math
from System.Globalization import CultureInfo
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes, OrderStates, Level1Fields
from StockSharp.BusinessEntities import Subscription
from StockSharp.Algo.Indicators import StochasticOscillator
from StockSharp.Algo.Strategies import Strategy
_COMPARISON_TOLERANCE = Decimal.Parse("0.00000001", CultureInfo.InvariantCulture)
class stochastic_overbought_oversold_strategy(Strategy):
"""
Fades confirmed stochastic extremes and exits on the return to neutral.
The native oscillator's D is the smoothed %K; a second SMA forms %D.
"""
def __init__(self):
super(stochastic_overbought_oversold_strategy, self).__init__()
self._stoch_period = self.Param("StochPeriod", 14).SetGreaterThanZero().SetDisplay("Stochastic Period", "Raw high/low lookback", "Indicators")
self._k_period = self.Param("KPeriod", 3).SetGreaterThanZero().SetDisplay("K Period", "SMA length of raw %K", "Indicators")
self._d_period = self.Param("DPeriod", 3).SetGreaterThanZero().SetDisplay("D Period", "SMA length of smoothed %K", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Stochastic timeframe", "General")
self._stop_loss_percent = self.Param("StopLossPercent", 2.0).SetNotNegative().SetDisplay("Stop Loss (%)", "Actual-fill percent stop; zero disables it.", "Protection")
self._clear_state()
self.OrderRegistering += self._track_pending
@property
def candle_type(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
return [(self.Security, self.candle_type), (self.Security, DataType.Level1)]
def _track_pending(self, order):
self._pending_order = order
def _clear_state(self):
self._d_window = []
self._previous_k = None
self._pending_order = None
def OnReseted(self):
super(stochastic_overbought_oversold_strategy, self).OnReseted()
self._clear_state()
def OnStarted2(self, time):
super(stochastic_overbought_oversold_strategy, self).OnStarted2(time)
self._clear_state()
self.StartProtection(Unit(), Unit(Decimal(self._stop_loss_percent.Value), UnitTypes.Percent), useMarketOrders=True, isLocalStop=True)
for field in (Level1Fields.BestBidPrice, Level1Fields.BestAskPrice):
quotes = Subscription(DataType.Level1, self.Security)
quotes.MarketData.BuildField = field
self.SubscribeLevel1(quotes).Bind(self._observe_protection_quote).Start()
stochastic = StochasticOscillator()
stochastic.K.Length = self._stoch_period.Value
stochastic.D.Length = self._k_period.Value
candles = self.SubscribeCandles(self.candle_type)
candles.BindEx(stochastic, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, candles)
self.DrawIndicator(area, stochastic)
self.DrawOwnTrades(area)
def _observe_protection_quote(self, quote):
# Native actual-fill protection evaluates executable quotes between signal candles.
pass
def _process_candle(self, candle, output):
if candle.State != CandleStates.Finished or not output.Indicator.IsFormed:
return
k = output.D
if k is None:
return
self._d_window.append(k)
d_period = self._d_period.Value
if len(self._d_window) > d_period:
self._d_window.pop(0)
if len(self._d_window) < d_period:
return
d = sum(self._d_window, Decimal(0)) / Decimal(d_period)
prior = self._previous_k
self._previous_k = k
if prior is None or not self.IsFormedAndOnlineAndAllowTrading():
return
if self._pending_order is not None and self._pending_order.State not in (OrderStates.Done, OrderStates.Failed):
return
# Native rolling SMA can leave a sub-ulp remainder at exact levels (e.g. 50).
tolerance = _COMPARISON_TOLERANCE
if self.Position == 0 and prior < 20 - tolerance and k > prior + tolerance and k > d + tolerance:
self.BuyMarket(self.Volume)
elif self.Position == 0 and prior > 80 + tolerance and k < prior - tolerance and k < d - tolerance:
self.SellMarket(self.Volume)
# %K crossing 50 either way closes, so a position whose entry bar already passed 50 exits on the cross back.
elif self.Position > 0 and (prior < 50 - tolerance) != (k < 50 - tolerance):
self.SellMarket(self.Position)
elif self.Position < 0 and (prior > 50 + tolerance) != (k > 50 + tolerance):
self.BuyMarket(Math.Abs(self.Position))
def CreateClone(self):
return stochastic_overbought_oversold_strategy()