Стратегия "Аптраст-разворот"
Аптраст — медвежий аналог "spring" по Вайкоффу: цена ненадолго пробивает сопротивление и быстро возвращается ниже, выталкивая поздних покупателей перед разворотом вниз.
Тестирование показывает среднегодичную доходность около 58%. Стратегию лучше запускать на фондовом рынке.
Стратегия открывает короткую позицию, когда цена опускается ниже уровня пробоя, ожидая преобладания предложения над спросом.
Стоп размещается чуть выше максимума аптраста, а позиция закрывается, если цена восстанавливается выше этого уровня.
Детали
- Условия входа: сигнал индикатора
- Длинная/короткая: обе
- Условия выхода: стоп-лосс или противоположный сигнал
- Стопы: да, процентные
- Значения по умолчанию:
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>
/// Upthrust Reversal strategy (Wyckoff).
/// Enters short when price spikes above recent resistance then closes back below it.
/// Enters long when price dips below recent support then closes back above it.
/// Uses SMA for exit confirmation.
/// Uses cooldown to control trade frequency.
/// </summary>
public class UpthrustReversalStrategy : 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> _highs = new();
private readonly List<decimal> _lows = 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 UpthrustReversalStrategy()
{
_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();
_highs.Clear();
_lows.Clear();
_cooldown = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_highs.Clear();
_lows.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;
_highs.Add(candle.HighPrice);
_lows.Add(candle.LowPrice);
if (_highs.Count > LookbackPeriod + 1)
{
_highs.RemoveAt(0);
_lows.RemoveAt(0);
}
if (_highs.Count < LookbackPeriod + 1)
return;
if (_cooldown > 0)
{
_cooldown--;
return;
}
// Find resistance and support from previous N bars
decimal resistance = decimal.MinValue;
decimal support = decimal.MaxValue;
for (int i = 0; i < _highs.Count - 1; i++)
{
if (_highs[i] > resistance) resistance = _highs[i];
if (_lows[i] < support) support = _lows[i];
}
// Upthrust: price spikes above resistance but closes below it (bearish)
var isUpthrust = candle.HighPrice > resistance && candle.ClosePrice < resistance && candle.ClosePrice < candle.OpenPrice;
// Spring: price dips below support but closes above it (bullish)
var isSpring = candle.LowPrice < support && candle.ClosePrice > support && candle.ClosePrice > candle.OpenPrice;
if (Position == 0 && isUpthrust)
{
SellMarket();
_cooldown = CooldownBars;
}
else if (Position == 0 && isSpring)
{
BuyMarket();
_cooldown = CooldownBars;
}
else if (Position < 0 && candle.ClosePrice > smaValue)
{
BuyMarket();
_cooldown = CooldownBars;
}
else if (Position > 0 && candle.ClosePrice < smaValue)
{
SellMarket();
_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 upthrust_reversal_strategy(Strategy):
"""
Upthrust Reversal strategy (Wyckoff).
Enters short when price spikes above recent resistance then closes back below it.
Enters long when price dips below recent support then closes back above it.
Uses SMA for exit confirmation.
"""
def __init__(self):
super(upthrust_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._highs = []
self._lows = []
self._cooldown = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(upthrust_reversal_strategy, self).OnReseted()
self._highs = []
self._lows = []
self._cooldown = 0
def OnStarted2(self, time):
super(upthrust_reversal_strategy, self).OnStarted2(time)
self._highs = []
self._lows = []
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
self._highs.append(float(candle.HighPrice))
self._lows.append(float(candle.LowPrice))
if len(self._highs) > lookback + 1:
self._highs.pop(0)
self._lows.pop(0)
if len(self._highs) < lookback + 1:
return
if self._cooldown > 0:
self._cooldown -= 1
return
cd = self._cooldown_bars.Value
sv = float(sma_val)
# Find resistance and support from previous N bars
resistance = max(self._highs[:-1])
support = min(self._lows[:-1])
# 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
)
# 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
)
if self.Position == 0 and is_upthrust:
self.SellMarket()
self._cooldown = cd
elif self.Position == 0 and is_spring:
self.BuyMarket()
self._cooldown = cd
elif self.Position < 0 and float(candle.ClosePrice) > sv:
self.BuyMarket()
self._cooldown = cd
elif self.Position > 0 and float(candle.ClosePrice) < sv:
self.SellMarket()
self._cooldown = cd
def CreateClone(self):
return upthrust_reversal_strategy()