Стратегия разворота по Stochastic Hook
Стратегия Stochastic Hook Reversal наблюдает за линией %K, ищущей «зацеп» из перекупленности или перепроданности. После выхода осциллятора к экстремуму он часто загибается обратно, что указывает на ослабление импульса.
Тестирование показывает среднегодичную доходность около 166%. Стратегию лучше запускать на фондовом рынке.
Система открывает длинную позицию, когда %K разворачивается вверх ниже уровня двадцать и цена обновляет минимум. Продажа открывается, когда осциллятор загибается вниз выше уровня восемьдесят во время финального рывка вверх.
Позиции имеют небольшой процентный стоп и закрываются, когда стохастик поворачивается в другую сторону или срабатывает стоп.
Детали
- Условия входа: сигнал индикатора
- Лонг/шорт: оба направления
- Условия выхода: стоп-лосс или противоположный сигнал
- Стопы: да, на процентной основе
- Значения по умолчанию:
CandleType= 15 минутKPeriod= 14DPeriod= 3OversoldLevel= 20OverboughtLevel= 80StopLossPercent= 2
- Фильтры:
- Категория: Разворот
- Направление: Оба
- Индикаторы: Стохастик
- Стопы: Да
- Сложность: Средняя
- Таймфрейм: Внутридневной
- Сезонность: Нет
- Нейронные сети: Нет
- Дивергенция: Нет
- Уровень риска: Средний
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Stochastic Hook Reversal strategy.
/// A long opens when %K was below the oversold level on the previous candle and turns up while the low makes a new low
/// against the previous candle; a short opens when it was above the overbought level and turns down while the high makes a new high.
/// The position closes when %K turns the other way, reversing on the opposite signal, and a percent stop limits the loss.
/// </summary>
public class StochasticHookReversalStrategy : Strategy
{
private readonly StrategyParam<int> _kPeriod;
private readonly StrategyParam<int> _dPeriod;
private readonly StrategyParam<int> _oversoldLevel;
private readonly StrategyParam<int> _overboughtLevel;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<DataType> _candleType;
private decimal? _prevValue;
private decimal _prevHigh;
private decimal _prevLow;
/// <summary>
/// Period for %K.
/// </summary>
public int KPeriod
{
get => _kPeriod.Value;
set => _kPeriod.Value = value;
}
/// <summary>
/// Period for %D.
/// </summary>
public int DPeriod
{
get => _dPeriod.Value;
set => _dPeriod.Value = value;
}
/// <summary>
/// %K level of the oversold zone.
/// </summary>
public int OversoldLevel
{
get => _oversoldLevel.Value;
set => _oversoldLevel.Value = value;
}
/// <summary>
/// %K level of the overbought zone.
/// </summary>
public int OverboughtLevel
{
get => _overboughtLevel.Value;
set => _overboughtLevel.Value = value;
}
/// <summary>
/// Stop-loss percentage.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public StochasticHookReversalStrategy()
{
_kPeriod = Param(nameof(KPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("K Period", "Period for %K", "Indicators");
_dPeriod = Param(nameof(DPeriod), 3)
.SetGreaterThanZero()
.SetDisplay("D Period", "Period for %D", "Indicators");
_oversoldLevel = Param(nameof(OversoldLevel), 20)
.SetDisplay("Oversold Level", "%K level of the oversold zone", "Levels");
_overboughtLevel = Param(nameof(OverboughtLevel), 80)
.SetDisplay("Overbought Level", "%K level of the overbought zone", "Levels");
_stopLossPercent = Param(nameof(StopLossPercent), 2m)
.SetNotNegative()
.SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).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();
_prevValue = null;
_prevHigh = default;
_prevLow = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevValue = null;
_prevHigh = default;
_prevLow = default;
var oscillator = new StochasticOscillator
{
K = { Length = KPeriod },
D = { Length = DPeriod },
};
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(oscillator, ProcessCandle)
.Start();
StartProtection(new Unit(), new Unit(StopLossPercent, UnitTypes.Percent), useMarketOrders: true, isLocalStop: true);
// The stop has to see prices between candles, not only at their close.
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 area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, oscillator);
DrawOwnTrades(area);
}
}
private void ObserveProtectionQuote(Level1ChangeMessage quote)
{
// The high-level handler activates native protection before this callback, also between signal bars.
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue value)
{
if (candle.State != CandleStates.Finished || !value.IsFormed || value.IsEmpty || value is not IStochasticOscillatorValue { K: decimal k })
return;
var current = k;
var previous = _prevValue;
var prevHigh = _prevHigh;
var prevLow = _prevLow;
_prevValue = current;
_prevHigh = candle.HighPrice;
_prevLow = candle.LowPrice;
if (previous is not decimal last || !IsFormedAndOnlineAndAllowTrading())
return;
var hooksUp = current > last;
var hooksDown = current < last;
var longSignal = last < OversoldLevel && hooksUp && candle.LowPrice < prevLow;
var shortSignal = last > OverboughtLevel && hooksDown && candle.HighPrice > prevHigh;
if (Position > 0)
{
if (hooksDown)
SellMarket(shortSignal ? Volume + Position : Position);
}
else if (Position < 0)
{
if (hooksUp)
BuyMarket(longSignal ? Volume - Position : -Position);
}
else if (longSignal)
{
BuyMarket(Volume);
}
else if (shortSignal)
{
SellMarket(Volume);
}
}
}
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
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes, Level1Fields
from StockSharp.BusinessEntities import Subscription
from StockSharp.Algo.Indicators import StochasticOscillator
from StockSharp.Algo.Strategies import Strategy
class stochastic_hook_reversal_strategy(Strategy):
"""
Stochastic Hook Reversal strategy.
A long opens when %K was below the oversold level on the previous candle and turns up while the low makes a new low
against the previous candle; a short opens when it was above the overbought level and turns down while the high makes a new high.
The position closes when %K turns the other way, reversing on the opposite signal, and a percent stop limits the loss.
"""
def __init__(self):
super(stochastic_hook_reversal_strategy, self).__init__()
self._k_period = self.Param("KPeriod", 14).SetGreaterThanZero().SetDisplay("K Period", "Period for %K", "Indicators")
self._d_period = self.Param("DPeriod", 3).SetGreaterThanZero().SetDisplay("D Period", "Period for %D", "Indicators")
self._oversold_level = self.Param("OversoldLevel", 20).SetDisplay("Oversold Level", "%K level of the oversold zone", "Levels")
self._overbought_level = self.Param("OverboughtLevel", 80).SetDisplay("Overbought Level", "%K level of the overbought zone", "Levels")
self._stop_loss_percent = self.Param("StopLossPercent", 2.0).SetNotNegative().SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._prev_value = None
self._prev_high = Decimal(0)
self._prev_low = Decimal(0)
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(stochastic_hook_reversal_strategy, self).OnReseted()
self._prev_value = None
self._prev_high = Decimal(0)
self._prev_low = Decimal(0)
def OnStarted2(self, time):
super(stochastic_hook_reversal_strategy, self).OnStarted2(time)
self._prev_value = None
self._prev_high = Decimal(0)
self._prev_low = Decimal(0)
oscillator = StochasticOscillator()
oscillator.K.Length = self._k_period.Value
oscillator.D.Length = self._d_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(oscillator, self._process_candle).Start()
self.StartProtection(Unit(), Unit(Decimal(self._stop_loss_percent.Value), UnitTypes.Percent), useMarketOrders=True, isLocalStop=True)
# The stop has to see prices between candles, not only at their close.
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()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, oscillator)
self.DrawOwnTrades(area)
def _observe_protection_quote(self, quote):
# The high-level handler activates native protection before the callback, including between bars.
pass
def _process_candle(self, candle, value):
if candle.State != CandleStates.Finished or not value.IsFormed or value.IsEmpty or value.K is None:
return
current = value.K
previous = self._prev_value
prev_high = self._prev_high
prev_low = self._prev_low
self._prev_value = current
self._prev_high = candle.HighPrice
self._prev_low = candle.LowPrice
if previous is None or not self.IsFormedAndOnlineAndAllowTrading():
return
hooks_up = current > previous
hooks_down = current < previous
long_signal = previous < Decimal(self._oversold_level.Value) and hooks_up and candle.LowPrice < prev_low
short_signal = previous > Decimal(self._overbought_level.Value) and hooks_down and candle.HighPrice > prev_high
if self.Position > 0:
if hooks_down:
self.SellMarket(self.Volume + self.Position if short_signal else self.Position)
elif self.Position < 0:
if hooks_up:
self.BuyMarket(self.Volume - self.Position if long_signal else -self.Position)
elif long_signal:
self.BuyMarket(self.Volume)
elif short_signal:
self.SellMarket(self.Volume)
def CreateClone(self):
return stochastic_hook_reversal_strategy()