Chaikin Volatility Stochastic Strategy
Стратегия применяет стохастический осциллятор к значениям волатильности Чайкина для поиска разворотов тренда. Диапазон «максимум-минимум» каждой свечи сглаживается EMA, затем нормализуется стохастиком и дополнительно сглаживается взвешенным средним.
Когда сглаженный осциллятор после роста разворачивается вниз, открывается длинная позиция и закрываются все короткие. Когда осциллятор после падения разворачивается вверх, открывается короткая позиция и закрываются длинные.
Параметры
- Candle Type: таймфрейм свечей.
- EMA Length: период сглаживания диапазона.
- Stochastic Length: окно расчёта стохастика.
- WMA Length: период взвешенного усреднения.
- Enable Longs / Enable Shorts: разрешённые направления торговли.
Индикаторы
- ExponentialMovingAverage
- Highest и Lowest
- WeightedMovingAverage
Правила торговли
- Вход в лонг: осциллятор рос и развернулся вниз.
- Вход в шорт: осциллятор падал и развернулся вверх.
- При смене сигнала противоположные позиции закрываются.
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>
/// Strategy using Chaikin Volatility Stochastic turning points.
/// Computes EMA of high-low range, then stochastic of that, then WMA smoothing.
/// Trades on turning points (peak/trough) of the smoothed value.
/// </summary>
public class ChaikinVolatilityStochasticStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _emaLength;
private readonly StrategyParam<int> _stochLength;
private readonly StrategyParam<int> _wmaLength;
private ExponentialMovingAverage _rangeEma;
private Highest _highest;
private Lowest _lowest;
private WeightedMovingAverage _wma;
private decimal? _prev;
private decimal? _prevPrev;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int EmaLength { get => _emaLength.Value; set => _emaLength.Value = value; }
public int StochLength { get => _stochLength.Value; set => _stochLength.Value = value; }
public int WmaLength { get => _wmaLength.Value; set => _wmaLength.Value = value; }
public ChaikinVolatilityStochasticStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Candles for calculation", "General");
_emaLength = Param(nameof(EmaLength), 10)
.SetDisplay("EMA Length", "Length for smoothing high-low range", "Indicator");
_stochLength = Param(nameof(StochLength), 5)
.SetDisplay("Stochastic Length", "Lookback for stochastic calculation", "Indicator");
_wmaLength = Param(nameof(WmaLength), 5)
.SetDisplay("WMA Length", "Weighted moving average period", "Indicator");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_rangeEma = null;
_highest = null;
_lowest = null;
_wma = null;
_prev = null;
_prevPrev = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prev = null;
_prevPrev = null;
_rangeEma = new ExponentialMovingAverage { Length = EmaLength };
_highest = new Highest { Length = StochLength };
_lowest = new Lowest { Length = StochLength };
_wma = new WeightedMovingAverage { Length = WmaLength };
Indicators.Add(_rangeEma);
Indicators.Add(_highest);
Indicators.Add(_lowest);
Indicators.Add(_wma);
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var t = candle.ServerTime;
var range = candle.HighPrice - candle.LowPrice;
// Step 1: EMA of high-low range
var emaResult = _rangeEma.Process(new DecimalIndicatorValue(_rangeEma, range, t) { IsFinal = true });
if (!_rangeEma.IsFormed)
return;
var emaVal = emaResult.GetValue<decimal>();
// Step 2: Highest and Lowest of the EMA values
var highResult = _highest.Process(new DecimalIndicatorValue(_highest, emaVal, t) { IsFinal = true });
var lowResult = _lowest.Process(new DecimalIndicatorValue(_lowest, emaVal, t) { IsFinal = true });
if (!_highest.IsFormed || !_lowest.IsFormed)
return;
var hh = highResult.GetValue<decimal>();
var ll = lowResult.GetValue<decimal>();
if (hh == ll)
return;
// Step 3: Stochastic percent
var percent = (emaVal - ll) / (hh - ll) * 100m;
// Step 4: WMA smoothing
var smoothResult = _wma.Process(new DecimalIndicatorValue(_wma, percent, t) { IsFinal = true });
if (!_wma.IsFormed)
return;
var current = smoothResult.GetValue<decimal>();
if (_prev.HasValue && _prevPrev.HasValue)
{
var wasRising = _prev.Value > _prevPrev.Value;
var isFalling = current < _prev.Value;
var wasFalling = _prev.Value < _prevPrev.Value;
var isRising = current > _prev.Value;
// Peak detected (was rising, now falling) -> sell
if (wasRising && isFalling && Position >= 0)
SellMarket();
// Trough detected (was falling, now rising) -> buy
else if (wasFalling && isRising && Position <= 0)
BuyMarket();
}
_prevPrev = _prev;
_prev = current;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage, Highest, Lowest, WeightedMovingAverage
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class chaikin_volatility_stochastic_strategy(Strategy):
def __init__(self):
super(chaikin_volatility_stochastic_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Candles for calculation", "General")
self._ema_length = self.Param("EmaLength", 10) \
.SetDisplay("EMA Length", "Length for smoothing high-low range", "Indicator")
self._stoch_length = self.Param("StochLength", 5) \
.SetDisplay("Stochastic Length", "Lookback for stochastic calculation", "Indicator")
self._wma_length = self.Param("WmaLength", 5) \
.SetDisplay("WMA Length", "Weighted moving average period", "Indicator")
self._range_ema = None
self._highest = None
self._lowest = None
self._wma = None
self._prev = None
self._prev_prev = None
@property
def candle_type(self):
return self._candle_type.Value
@property
def ema_length(self):
return self._ema_length.Value
@property
def stoch_length(self):
return self._stoch_length.Value
@property
def wma_length(self):
return self._wma_length.Value
def OnReseted(self):
super(chaikin_volatility_stochastic_strategy, self).OnReseted()
self._range_ema = None
self._highest = None
self._lowest = None
self._wma = None
self._prev = None
self._prev_prev = None
def OnStarted2(self, time):
super(chaikin_volatility_stochastic_strategy, self).OnStarted2(time)
self._prev = None
self._prev_prev = None
self._range_ema = ExponentialMovingAverage()
self._range_ema.Length = self.ema_length
self._highest = Highest()
self._highest.Length = self.stoch_length
self._lowest = Lowest()
self._lowest.Length = self.stoch_length
self._wma = WeightedMovingAverage()
self._wma.Length = self.wma_length
self.Indicators.Add(self._range_ema)
self.Indicators.Add(self._highest)
self.Indicators.Add(self._lowest)
self.Indicators.Add(self._wma)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
t = candle.ServerTime
range_val = float(candle.HighPrice) - float(candle.LowPrice)
ema_result = process_float(self._range_ema, range_val, t, True)
if not self._range_ema.IsFormed:
return
ema_val = float(ema_result)
high_result = process_float(self._highest, ema_val, t, True)
low_result = process_float(self._lowest, ema_val, t, True)
if not self._highest.IsFormed or not self._lowest.IsFormed:
return
hh = float(high_result)
ll = float(low_result)
if hh == ll:
return
percent = (ema_val - ll) / (hh - ll) * 100.0
smooth_result = process_float(self._wma, percent, t, True)
if not self._wma.IsFormed:
return
current = float(smooth_result)
if self._prev is not None and self._prev_prev is not None:
was_rising = self._prev > self._prev_prev
is_falling = current < self._prev
was_falling = self._prev < self._prev_prev
is_rising = current > self._prev
if was_rising and is_falling and self.Position >= 0:
self.SellMarket()
elif was_falling and is_rising and self.Position <= 0:
self.BuyMarket()
self._prev_prev = self._prev
self._prev = current
def CreateClone(self):
return chaikin_volatility_stochastic_strategy()