Стратегия "Весенний разворот"
Концепция "весеннего" разворота по Вайкоффу описывает ситуацию, когда цена краткосрочно пробивает поддержку и затем быстро возвращается выше неё. Такой ложный пробой выбивает поздних продавцов и часто означает начало восходящего тренда.
Тестирование показывает среднегодичную доходность около 55%. Стратегию лучше запускать на фондовом рынке.
Стратегия покупает, когда цена вновь поднимается выше пробитого уровня, рассчитывая на быстрое закрытие шортов и приток спроса.
Стоп размещается чуть ниже минимума "spring", а позиция закрывается, если дальнейшее движение не развивается.
Детали
- Условия входа: сигнал индикатора
- Длинная/короткая: обе
- Условия выхода: стоп-лосс или противоположный сигнал
- Стопы: да, процентные
- Значения по умолчанию:
CandleType= 15 minuteStopLoss= 2%
- Фильтры:
- Категория: Разворот
- Направление: обе
- Индикаторы: Wyckoff
- Стопы: да
- Сложность: средняя
- Таймфрейм: внутридневной
- Сезонность: нет
- Нейронные сети: нет
- Дивергенция: нет
- Уровень риска: средний
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>
/// Spring Reversal strategy (Wyckoff).
/// Enters long when price dips below recent support then closes back above it.
/// Enters short when price spikes above recent resistance then closes back below it.
/// Uses SMA for exit confirmation.
/// Uses cooldown to control trade frequency.
/// </summary>
public class SpringReversalStrategy : Strategy
{
private readonly StrategyParam<int> _lookbackPeriod;
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _cooldownBars;
private readonly List<decimal> _lows = new();
private readonly List<decimal> _highs = new();
private int _cooldown;
/// <summary>
/// Lookback period.
/// </summary>
public int LookbackPeriod
{
get => _lookbackPeriod.Value;
set => _lookbackPeriod.Value = value;
}
/// <summary>
/// MA period for exit.
/// </summary>
public int MaPeriod
{
get => _maPeriod.Value;
set => _maPeriod.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Cooldown bars.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public SpringReversalStrategy()
{
_lookbackPeriod = Param(nameof(LookbackPeriod), 20)
.SetRange(5, 50)
.SetDisplay("Lookback", "Period for support/resistance", "Range");
_maPeriod = Param(nameof(MaPeriod), 20)
.SetRange(5, 50)
.SetDisplay("MA Period", "Period for SMA exit", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_cooldownBars = Param(nameof(CooldownBars), 500)
.SetRange(1, 1000)
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_lows.Clear();
_highs.Clear();
_cooldown = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_lows.Clear();
_highs.Clear();
_cooldown = 0;
var sma = new SimpleMovingAverage { Length = MaPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(sma, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, sma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal smaValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
// Maintain rolling window of lows and highs
_lows.Add(candle.LowPrice);
_highs.Add(candle.HighPrice);
if (_lows.Count > LookbackPeriod + 1)
{
_lows.RemoveAt(0);
_highs.RemoveAt(0);
}
if (_lows.Count < LookbackPeriod + 1)
return;
if (_cooldown > 0)
{
_cooldown--;
return;
}
// Find support (lowest low) and resistance (highest high) of previous N bars
decimal support = decimal.MaxValue;
decimal resistance = decimal.MinValue;
for (int i = 0; i < _lows.Count - 1; i++)
{
if (_lows[i] < support) support = _lows[i];
if (_highs[i] > resistance) resistance = _highs[i];
}
// Spring: price dips below support but closes above it (bullish)
var isSpring = candle.LowPrice < support && candle.ClosePrice > support && candle.ClosePrice > candle.OpenPrice;
// Upthrust: price spikes above resistance but closes below it (bearish)
var isUpthrust = candle.HighPrice > resistance && candle.ClosePrice < resistance && candle.ClosePrice < candle.OpenPrice;
if (Position == 0 && isSpring)
{
BuyMarket();
_cooldown = CooldownBars;
}
else if (Position == 0 && isUpthrust)
{
SellMarket();
_cooldown = CooldownBars;
}
else if (Position > 0 && candle.ClosePrice < smaValue)
{
SellMarket();
_cooldown = CooldownBars;
}
else if (Position < 0 && candle.ClosePrice > smaValue)
{
BuyMarket();
_cooldown = CooldownBars;
}
}
}
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 spring_reversal_strategy(Strategy):
"""
Spring Reversal strategy (Wyckoff).
Enters long when price dips below recent support then closes back above it.
Enters short when price spikes above recent resistance then closes back below it.
Uses SMA for exit confirmation.
"""
def __init__(self):
super(spring_reversal_strategy, self).__init__()
self._lookback_period = self.Param("LookbackPeriod", 20).SetDisplay("Lookback", "Period for support/resistance", "Range")
self._ma_period = self.Param("MaPeriod", 20).SetDisplay("MA Period", "Period for SMA exit", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._cooldown_bars = self.Param("CooldownBars", 500).SetDisplay("Cooldown Bars", "Bars to wait between trades", "General")
self._lows = []
self._highs = []
self._cooldown = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(spring_reversal_strategy, self).OnReseted()
self._lows = []
self._highs = []
self._cooldown = 0
def OnStarted2(self, time):
super(spring_reversal_strategy, self).OnStarted2(time)
self._lows = []
self._highs = []
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, sma_val):
if candle.State != CandleStates.Finished:
return
lookback = self._lookback_period.Value
# Maintain rolling window of lows and highs
self._lows.append(float(candle.LowPrice))
self._highs.append(float(candle.HighPrice))
if len(self._lows) > lookback + 1:
self._lows.pop(0)
self._highs.pop(0)
if len(self._lows) < lookback + 1:
return
if self._cooldown > 0:
self._cooldown -= 1
return
cd = self._cooldown_bars.Value
sv = float(sma_val)
# Find support (lowest low) and resistance (highest high) of previous N bars
support = min(self._lows[:-1])
resistance = max(self._highs[:-1])
# Spring: price dips below support but closes above it (bullish)
is_spring = (
float(candle.LowPrice) < support and
float(candle.ClosePrice) > support and
candle.ClosePrice > candle.OpenPrice
)
# Upthrust: price spikes above resistance but closes below it (bearish)
is_upthrust = (
float(candle.HighPrice) > resistance and
float(candle.ClosePrice) < resistance and
candle.ClosePrice < candle.OpenPrice
)
if self.Position == 0 and is_spring:
self.BuyMarket()
self._cooldown = cd
elif self.Position == 0 and is_upthrust:
self.SellMarket()
self._cooldown = cd
elif self.Position > 0 and float(candle.ClosePrice) < sv:
self.SellMarket()
self._cooldown = cd
elif self.Position < 0 and float(candle.ClosePrice) > sv:
self.BuyMarket()
self._cooldown = cd
def CreateClone(self):
return spring_reversal_strategy()