VRS Vegas Reversal Strategy
Реверсивная стратегия, использующая свечные хвосты.
Тестирование показывает среднегодовую доходность около 37%. Лучше всего работает на рынке криптовалют.
Система ищет большие шпильки относительно цены закрытия. Длинный нижний хвост сигнализирует покупку, а длинный верхний – продажу. Позиция закрывается, когда цена проходит двойной размер шпильки в прибыльную сторону.
Детали
- Условия входа:
- Лонг: нижний хвост ≥ Spike% * close и отсутствует верхний шпиль.
- Шорт: верхний хвост ≥ Spike% * close и отсутствует нижний шпиль.
- Длинные/Короткие: обе стороны.
- Условия выхода: цель на уровне входа ± (шпилька * 2).
- Стопы: нет.
- Значения по умолчанию:
SpikePercent= 0.025CandleType= TimeSpan.FromMinutes(5)
- Фильтры:
- Категория: Разворот
- Направление: Оба
- Индикаторы: Price action
- Стопы: Нет
- Сложность: Базовая
- Таймфрейм: Внутридневной
- Сезонность: Нет
- Нейросети: Нет
- Дивергенция: Нет
- Уровень риска: Высокий
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Vegas Reversal strategy based on spike percentage.
/// Enters long on large lower wick and short on large upper wick.
/// Exits when price moves twice the spike length in favor.
/// </summary>
public class VrsVegasReversalStrategy : Strategy
{
private readonly StrategyParam<decimal> _spikePercent;
private readonly StrategyParam<DataType> _candleType;
private decimal _entryPrice;
private decimal _spikeSize;
private bool _isLong;
/// <summary>
/// Spike percentage relative to close price.
/// </summary>
public decimal SpikePercent
{
get => _spikePercent.Value;
set => _spikePercent.Value = value;
}
/// <summary>
/// Candle type for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of <see cref="VrsVegasReversalStrategy"/>.
/// </summary>
public VrsVegasReversalStrategy()
{
_spikePercent = Param(nameof(SpikePercent), 0.025m)
.SetGreaterThanZero()
.SetDisplay("Spike %", "Spike percentage threshold", "Reversal")
.SetOptimize(0.01m, 0.05m, 0.005m);
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_entryPrice = 0;
_spikeSize = 0;
_isLong = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
Volume = NormalizeVolume(1m);
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 upperSpike = candle.HighPrice - Math.Max(candle.ClosePrice, candle.OpenPrice);
var lowerSpike = Math.Min(candle.ClosePrice, candle.OpenPrice) - candle.LowPrice;
var validUpper = upperSpike >= candle.ClosePrice * SpikePercent;
var validLower = lowerSpike >= candle.ClosePrice * SpikePercent;
var valid = (validUpper && !validLower) || (validLower && !validUpper);
var enterLong = valid && validLower;
var enterShort = valid && validUpper;
if (enterLong && Position <= 0)
{
_entryPrice = candle.ClosePrice;
_spikeSize = lowerSpike;
_isLong = true;
BuyMarket(NormalizeVolume(Volume + Math.Abs(Position)));
}
else if (enterShort && Position >= 0)
{
_entryPrice = candle.ClosePrice;
_spikeSize = upperSpike;
_isLong = false;
SellMarket(NormalizeVolume(Volume + Math.Abs(Position)));
}
if (Position > 0 && _isLong)
{
var target = _entryPrice + _spikeSize * 2m;
if (candle.ClosePrice >= target)
SellMarket(Position);
}
else if (Position < 0 && !_isLong)
{
var target = _entryPrice - _spikeSize * 2m;
if (candle.ClosePrice <= target)
BuyMarket(-Position);
}
}
private decimal NormalizeVolume(decimal volume)
{
var step = Security?.VolumeStep ?? 1m;
if (step <= 0m)
step = 1m;
var minimum = Security?.MinVolume ?? step;
if (volume < minimum)
volume = minimum;
var rounded = Math.Ceiling(volume / step) * step;
return rounded < minimum ? minimum : rounded;
}
}
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
from StockSharp.Algo.Strategies import Strategy
class vrs_vegas_reversal_strategy(Strategy):
def __init__(self):
super(vrs_vegas_reversal_strategy, self).__init__()
self._spike_percent = self.Param("SpikePercent", 0.025) \
.SetDisplay("Spike %", "Spike percentage threshold", "Reversal")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._entry_price = 0.0
self._spike_size = 0.0
self._is_long = False
@property
def spike_percent(self):
return self._spike_percent.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(vrs_vegas_reversal_strategy, self).OnReseted()
self._entry_price = 0.0
self._spike_size = 0.0
self._is_long = False
def OnStarted2(self, time):
super(vrs_vegas_reversal_strategy, self).OnStarted2(time)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self.on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def on_process(self, candle):
if candle.State != CandleStates.Finished:
return
upper_spike = float(candle.HighPrice) - max(float(candle.ClosePrice), float(candle.OpenPrice))
lower_spike = min(float(candle.ClosePrice), float(candle.OpenPrice)) - float(candle.LowPrice)
close = float(candle.ClosePrice)
sp = float(self.spike_percent)
valid_upper = upper_spike >= close * sp
valid_lower = lower_spike >= close * sp
valid = (valid_upper and not valid_lower) or (valid_lower and not valid_upper)
enter_long = valid and valid_lower
enter_short = valid and valid_upper
if enter_long and self.Position <= 0:
self._entry_price = close
self._spike_size = lower_spike
self._is_long = True
self.BuyMarket(self.Volume + Math.Abs(self.Position))
elif enter_short and self.Position >= 0:
self._entry_price = close
self._spike_size = upper_spike
self._is_long = False
self.SellMarket(self.Volume + Math.Abs(self.Position))
if self.Position > 0 and self._is_long:
target = self._entry_price + self._spike_size * 2.0
if close >= target:
self.SellMarket(self.Position)
elif self.Position < 0 and not self._is_long:
target = self._entry_price - self._spike_size * 2.0
if close <= target:
self.BuyMarket(-self.Position)
def CreateClone(self):
return vrs_vegas_reversal_strategy()