Стохастический перекос подразумеваемой волатильности
Стратегия Stochastic Implied Volatility Skew основана на стохастическом анализе перекоса подразумеваемой волатильности. Сигналы формируются, когда Stochastic подтверждает смену тренда на внутридневных данных (5м). Такой подход подходит активным трейдерам. Стопы рассчитываются исходя из кратных ATR и параметров StochLength, StochK. Эти значения можно изменять для баланса риска и прибыли.
Подробности
- Условия входа: см. реализацию для условий по индикаторам.
- Длинные/короткие позиции: обе стороны.
- Условия выхода: обратный сигнал или логика стопов.
- Стопы: да, вычисляются на основе индикаторов.
- Значения по умолчанию:
StochLength = 14StochK = 3StochD = 3IvPeriod = 20StopLoss = 2mCandleType = TimeSpan.FromMinutes(5).TimeFrame()
- Фильтры:
- Категория: Следование за трендом
- Направление: Оба
- Индикаторы: Stochastic, Skew
- Стопы: Да
- Сложность: Средняя
- Таймфрейм: Внутридневной (5m)
- Сезонность: Нет
- Нейросети: Нет
- Дивергенция: Нет
- Уровень риска: Средний
using System;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Stochastic strategy filtered by deterministic implied-volatility skew regime changes.
/// </summary>
public class StochasticImpliedVolatilitySkewStrategy : Strategy
{
private readonly StrategyParam<int> _stochLength;
private readonly StrategyParam<int> _stochK;
private readonly StrategyParam<int> _stochD;
private readonly StrategyParam<int> _ivPeriod;
private readonly StrategyParam<decimal> _stopLoss;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<DataType> _candleType;
private StochasticOscillator _stochastic;
private SimpleMovingAverage _ivSkewSma;
private decimal _currentIvSkew;
private decimal _avgIvSkew;
private decimal? _prevK;
private bool _prevHighSkew;
private bool _prevLowSkew;
private int _cooldownRemaining;
/// <summary>
/// Stochastic length parameter.
/// </summary>
public int StochLength
{
get => _stochLength.Value;
set => _stochLength.Value = value;
}
/// <summary>
/// Stochastic %K smoothing parameter.
/// </summary>
public int StochK
{
get => _stochK.Value;
set => _stochK.Value = value;
}
/// <summary>
/// Stochastic %D smoothing parameter.
/// </summary>
public int StochD
{
get => _stochD.Value;
set => _stochD.Value = value;
}
/// <summary>
/// IV skew averaging period.
/// </summary>
public int IvPeriod
{
get => _ivPeriod.Value;
set => _ivPeriod.Value = value;
}
/// <summary>
/// Stop loss percentage.
/// </summary>
public decimal StopLoss
{
get => _stopLoss.Value;
set => _stopLoss.Value = value;
}
/// <summary>
/// Closed candles to wait before another position change.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Candle type for strategy calculation.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initialize strategy.
/// </summary>
public StochasticImpliedVolatilitySkewStrategy()
{
_stochLength = Param(nameof(StochLength), 14)
.SetRange(5, 30)
.SetDisplay("Stoch Length", "Period for stochastic oscillator", "Indicators");
_stochK = Param(nameof(StochK), 3)
.SetRange(1, 10)
.SetDisplay("Stoch %K", "Smoothing for stochastic %K line", "Indicators");
_stochD = Param(nameof(StochD), 3)
.SetRange(1, 10)
.SetDisplay("Stoch %D", "Smoothing for stochastic %D line", "Indicators");
_ivPeriod = Param(nameof(IvPeriod), 20)
.SetRange(10, 50)
.SetDisplay("IV Period", "Period for IV skew averaging", "Options");
_stopLoss = Param(nameof(StopLoss), 2m)
.SetRange(1m, 5m)
.SetDisplay("Stop Loss %", "Stop loss percentage", "Risk Management");
_cooldownBars = Param(nameof(CooldownBars), 18)
.SetNotNegative()
.SetDisplay("Cooldown Bars", "Closed candles to wait before another position change", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).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();
_stochastic = null;
_ivSkewSma = null;
_currentIvSkew = 0m;
_avgIvSkew = 0m;
_prevK = null;
_prevHighSkew = false;
_prevLowSkew = false;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_stochastic = new StochasticOscillator
{
K = { Length = StochLength },
D = { Length = StochD },
};
_ivSkewSma = new SimpleMovingAverage
{
Length = IvPeriod
};
Indicators.Add(_stochastic);
Indicators.Add(_ivSkewSma);
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _stochastic);
DrawOwnTrades(area);
}
StartProtection(
new Unit(2, UnitTypes.Percent),
new Unit(StopLoss, UnitTypes.Percent)
);
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
SimulateIvSkew(candle);
var ivSkewSmaValue = _ivSkewSma.Process(new DecimalIndicatorValue(_ivSkewSma, _currentIvSkew, candle.OpenTime) { IsFinal = true });
if (!_ivSkewSma.IsFormed || ivSkewSmaValue.IsEmpty)
return;
_avgIvSkew = ivSkewSmaValue.ToDecimal();
var stochResult = _stochastic.Process(candle);
if (!_stochastic.IsFormed)
return;
if (stochResult is not StochasticOscillatorValue stochTyped || stochTyped.K is not decimal stochK)
return;
if (_cooldownRemaining > 0)
_cooldownRemaining--;
var highSkew = _currentIvSkew > _avgIvSkew;
var lowSkew = _currentIvSkew < _avgIvSkew;
var oversold = stochK < 25m;
var overbought = stochK > 75m;
if (_cooldownRemaining == 0 && Position == 0 && oversold && highSkew)
{
BuyMarket();
_cooldownRemaining = CooldownBars;
}
else if (_cooldownRemaining == 0 && Position == 0 && overbought && lowSkew)
{
SellMarket();
_cooldownRemaining = CooldownBars;
}
_prevK = stochK;
_prevHighSkew = highSkew;
_prevLowSkew = lowSkew;
}
private void SimulateIvSkew(ICandleMessage candle)
{
var range = Math.Max(candle.HighPrice - candle.LowPrice, 1m);
var body = candle.ClosePrice - candle.OpenPrice;
var rangeRatio = range / Math.Max(candle.OpenPrice, 1m);
var bodyRatio = body / range;
// Rising candles tend to keep skew more negative, falling candles less negative or positive.
_currentIvSkew = (bodyRatio * 0.2m) - Math.Min(0.15m, rangeRatio * 10m);
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import StochasticOscillator, SimpleMovingAverage, CandleIndicatorValue
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class stochastic_implied_volatility_skew_strategy(Strategy):
"""Stochastic strategy filtered by deterministic implied-volatility skew regime changes."""
def __init__(self):
super(stochastic_implied_volatility_skew_strategy, self).__init__()
self._stoch_length = self.Param("StochLength", 14) \
.SetRange(5, 30) \
.SetDisplay("Stoch Length", "Period for stochastic oscillator", "Indicators")
self._stoch_k = self.Param("StochK", 3) \
.SetRange(1, 10) \
.SetDisplay("Stoch %K", "Smoothing for stochastic %K line", "Indicators")
self._stoch_d = self.Param("StochD", 3) \
.SetRange(1, 10) \
.SetDisplay("Stoch %D", "Smoothing for stochastic %D line", "Indicators")
self._iv_period = self.Param("IvPeriod", 20) \
.SetRange(10, 50) \
.SetDisplay("IV Period", "Period for IV skew averaging", "Options")
self._stop_loss = self.Param("StopLoss", 2.0) \
.SetRange(1.0, 5.0) \
.SetDisplay("Stop Loss %", "Stop loss percentage", "Risk Management")
self._cooldown_bars = self.Param("CooldownBars", 18) \
.SetNotNegative() \
.SetDisplay("Cooldown Bars", "Closed candles to wait before another position change", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._stochastic = None
self._iv_skew_sma = None
self._current_iv_skew = 0.0
self._avg_iv_skew = 0.0
self._prev_k = None
self._prev_high_skew = False
self._prev_low_skew = False
self._cooldown_remaining = 0
@property
def candle_type(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
return [(self.Security, self.candle_type)]
def OnReseted(self):
super(stochastic_implied_volatility_skew_strategy, self).OnReseted()
self._stochastic = None
self._iv_skew_sma = None
self._current_iv_skew = 0.0
self._avg_iv_skew = 0.0
self._prev_k = None
self._prev_high_skew = False
self._prev_low_skew = False
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(stochastic_implied_volatility_skew_strategy, self).OnStarted2(time)
self._stochastic = StochasticOscillator()
self._stochastic.K.Length = int(self._stoch_length.Value)
self._stochastic.D.Length = int(self._stoch_d.Value)
self._iv_skew_sma = SimpleMovingAverage()
self._iv_skew_sma.Length = int(self._iv_period.Value)
self.Indicators.Add(self._stochastic)
self.Indicators.Add(self._iv_skew_sma)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self.ProcessCandle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._stochastic)
self.DrawOwnTrades(area)
self.StartProtection(
Unit(2, UnitTypes.Percent),
Unit(float(self._stop_loss.Value), UnitTypes.Percent)
)
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
self.SimulateIvSkew(candle)
iv_sma_result = process_float(self._iv_skew_sma, self._current_iv_skew, candle.OpenTime, True)
if not self._iv_skew_sma.IsFormed or iv_sma_result.IsEmpty:
return
self._avg_iv_skew = float(iv_sma_result)
civ = CandleIndicatorValue(self._stochastic, candle)
civ.IsFinal = True
stoch_result = self._stochastic.Process(civ)
if not self._stochastic.IsFormed:
return
stoch_k_val = stoch_result.K
if stoch_k_val is None:
return
stoch_k = float(stoch_k_val)
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
cooldown = int(self._cooldown_bars.Value)
high_skew = self._current_iv_skew > self._avg_iv_skew
low_skew = self._current_iv_skew < self._avg_iv_skew
oversold = stoch_k < 25.0
overbought = stoch_k > 75.0
if self._cooldown_remaining == 0 and self.Position == 0 and oversold and high_skew:
self.BuyMarket()
self._cooldown_remaining = cooldown
elif self._cooldown_remaining == 0 and self.Position == 0 and overbought and low_skew:
self.SellMarket()
self._cooldown_remaining = cooldown
self._prev_k = stoch_k
self._prev_high_skew = high_skew
self._prev_low_skew = low_skew
def SimulateIvSkew(self, candle):
range_val = max(float(candle.HighPrice - candle.LowPrice), 1.0)
body = float(candle.ClosePrice - candle.OpenPrice)
range_ratio = range_val / max(float(candle.OpenPrice), 1.0)
body_ratio = body / range_val
self._current_iv_skew = (body_ratio * 0.2) - min(0.15, range_ratio * 10.0)
def CreateClone(self):
return stochastic_implied_volatility_skew_strategy()