Стратегия "Объёмный кульминационный разворот"
Данная стратегия ищет точки разворота, отмеченные экстремально высоким объёмом после сильного тренда. Подобные всплески сигнализируют об истощении, когда последние покупатели или продавцы спешат войти перед затуханием импульса.
Тестирование показывает среднегодичную доходность около 82%. Стратегию лучше запускать на фондовом рынке.
Вход осуществляется против предыдущего движения после закрытия бара с большим объёмом и начала отката цены.
Жёсткий процентный стоп защищает позицию, а выход происходит, если объём не снижается или цена продолжает первоначальное движение.
Детали
- Условия входа: сигнал индикатора
- Длинная/короткая: обе
- Условия выхода: стоп-лосс или противоположный сигнал
- Стопы: да, процентные
- Значения по умолчанию:
CandleType= 15 minuteStopLoss= 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>
/// Volume Climax Reversal strategy.
/// Enters counter-trend when volume spikes above average with MA confirmation.
/// Uses cooldown and MA cross for exits.
/// </summary>
public class VolumeClimaxReversalStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<decimal> _volumeMultiplier;
private readonly StrategyParam<int> _cooldownBars;
private SimpleMovingAverage _ma;
private decimal _prevMa;
private decimal _prevClose;
private readonly List<decimal> _volumes = new();
private int _cooldown;
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// MA period.
/// </summary>
public int MaPeriod
{
get => _maPeriod.Value;
set => _maPeriod.Value = value;
}
/// <summary>
/// Volume multiplier for climax detection.
/// </summary>
public decimal VolumeMultiplier
{
get => _volumeMultiplier.Value;
set => _volumeMultiplier.Value = value;
}
/// <summary>
/// Cooldown bars.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public VolumeClimaxReversalStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
_maPeriod = Param(nameof(MaPeriod), 20)
.SetDisplay("MA Period", "SMA period", "Indicators")
.SetRange(10, 50);
_volumeMultiplier = Param(nameof(VolumeMultiplier), 2m)
.SetDisplay("Volume Multiplier", "Volume spike threshold", "Volume")
.SetRange(1.5m, 5m);
_cooldownBars = Param(nameof(CooldownBars), 400)
.SetDisplay("Cooldown Bars", "Bars between trades", "General")
.SetRange(10, 2000);
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_ma = default;
_prevMa = 0;
_prevClose = 0;
_volumes.Clear();
_cooldown = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_ma = new SimpleMovingAverage { Length = MaPeriod };
_volumes.Clear();
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_ma, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _ma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal ma)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var close = candle.ClosePrice;
var volume = candle.TotalVolume;
// Track volumes for average calculation
_volumes.Add(volume);
if (_volumes.Count > MaPeriod)
_volumes.RemoveAt(0);
if (_volumes.Count < MaPeriod || _prevMa == 0)
{
_prevMa = ma;
_prevClose = close;
return;
}
// Calculate average volume
decimal avgVolume = 0;
for (int i = 0; i < _volumes.Count; i++)
avgVolume += _volumes[i];
avgVolume /= _volumes.Count;
var isVolumeClimax = avgVolume > 0 && volume > avgVolume * VolumeMultiplier;
var isBullish = close > candle.OpenPrice;
var isBearish = close < candle.OpenPrice;
if (_cooldown > 0)
{
_cooldown--;
_prevMa = ma;
_prevClose = close;
return;
}
// Exit logic: MA cross
if (Position > 0 && close < ma && _prevClose >= _prevMa)
{
SellMarket();
_cooldown = CooldownBars;
}
else if (Position < 0 && close > ma && _prevClose <= _prevMa)
{
BuyMarket();
_cooldown = CooldownBars;
}
// Entry logic: volume climax reversal
if (Position == 0 && isVolumeClimax)
{
// Bullish reversal: high volume bearish candle below MA (selling climax)
if (isBearish && close < ma)
{
BuyMarket();
_cooldown = CooldownBars;
}
// Bearish reversal: high volume bullish candle above MA (buying climax)
else if (isBullish && close > ma)
{
SellMarket();
_cooldown = CooldownBars;
}
}
_prevMa = ma;
_prevClose = close;
}
}
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 SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class volume_climax_reversal_strategy(Strategy):
"""
Volume Climax Reversal strategy.
Enters counter-trend when volume spikes above average with MA confirmation.
Uses cooldown and MA cross for exits.
"""
def __init__(self):
super(volume_climax_reversal_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Candle timeframe", "General")
self._ma_period = self.Param("MaPeriod", 20).SetDisplay("MA Period", "SMA period", "Indicators")
self._volume_multiplier = self.Param("VolumeMultiplier", 2.0).SetDisplay("Volume Multiplier", "Volume spike threshold", "Volume")
self._cooldown_bars = self.Param("CooldownBars", 400).SetDisplay("Cooldown Bars", "Bars between trades", "General")
self._prev_ma = 0.0
self._prev_close = 0.0
self._volumes = []
self._cooldown = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(volume_climax_reversal_strategy, self).OnReseted()
self._prev_ma = 0.0
self._prev_close = 0.0
self._volumes = []
self._cooldown = 0
def OnStarted2(self, time):
super(volume_climax_reversal_strategy, self).OnStarted2(time)
self._prev_ma = 0.0
self._prev_close = 0.0
self._volumes = []
self._cooldown = 0
sma = SimpleMovingAverage()
sma.Length = self._ma_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(sma, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, sma)
self.DrawOwnTrades(area)
def _process_candle(self, candle, ma_val):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
ma = float(ma_val)
volume = float(candle.TotalVolume)
ma_period = self._ma_period.Value
cd = self._cooldown_bars.Value
# Track volumes for average calculation
self._volumes.append(volume)
if len(self._volumes) > ma_period:
self._volumes.pop(0)
if len(self._volumes) < ma_period or self._prev_ma == 0:
self._prev_ma = ma
self._prev_close = close
return
# Calculate average volume
avg_volume = sum(self._volumes) / len(self._volumes)
is_volume_climax = avg_volume > 0 and volume > avg_volume * self._volume_multiplier.Value
is_bullish = candle.ClosePrice > candle.OpenPrice
is_bearish = candle.ClosePrice < candle.OpenPrice
if self._cooldown > 0:
self._cooldown -= 1
self._prev_ma = ma
self._prev_close = close
return
# Exit logic: MA cross
if self.Position > 0 and close < ma and self._prev_close >= self._prev_ma:
self.SellMarket()
self._cooldown = cd
elif self.Position < 0 and close > ma and self._prev_close <= self._prev_ma:
self.BuyMarket()
self._cooldown = cd
# Entry logic: volume climax reversal
if self.Position == 0 and is_volume_climax:
# Bullish reversal: high volume bearish candle below MA (selling climax)
if is_bearish and close < ma:
self.BuyMarket()
self._cooldown = cd
# Bearish reversal: high volume bullish candle above MA (buying climax)
elif is_bullish and close > ma:
self.SellMarket()
self._cooldown = cd
self._prev_ma = ma
self._prev_close = close
def CreateClone(self):
return volume_climax_reversal_strategy()