Динамическая стратегия поддержки и сопротивления по пивотам
Эта стратегия строит динамические уровни поддержки и сопротивления на основе последних пивотных максимумов и минимумов. Вход в лонг выполняется при пересечении цены уровня поддержки снизу, а вход в шорт — при пересечении сопротивления сверху. Управление рисками использует фиксированные проценты стоп-лосса и тейк-профита.
Детали
- Условия входа: Цена рядом с поддержкой/сопротивлением в пределах
SupportResistanceDistanceпроцентов и пересечение уровня. - Лонг/Шорт: Оба.
- Условия выхода: Фиксированные тейк-профит и стоп-лосс.
- Стопы: Да.
- Значения по умолчанию:
PivotLength= 2SupportResistanceDistance= 0.4mStopLossPercent= 10.0mTakeProfitPercent= 26.0mCandleType= TimeSpan.FromMinutes(5)
- Фильтры:
- Категория: Пробой
- Направление: Оба
- Индикаторы: Пивоты
- Стопы: Да
- Сложность: Начальная
- Таймфрейм: Внутридневной
- Сезонность: Нет
- Нейросети: Нет
- Дивергенция: Нет
- Уровень риска: Средний
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Dynamic Support and Resistance Pivot strategy.
/// Support is the last pivot low and resistance the last pivot high, a pivot being a candle whose low (high) is beyond those of the
/// PivotLength candles on each side. A close crossing above support goes long and a close crossing below resistance goes short,
/// reversing an opposite position, when the close is within SupportResistanceDistance percent of that level. Percent stop loss and
/// take profit manage the position.
/// </summary>
public class DynamicSupportAndResistancePivotStrategy : Strategy
{
private readonly StrategyParam<int> _pivotLength;
private readonly StrategyParam<decimal> _supportResistanceDistance;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<decimal> _takeProfitPercent;
private readonly StrategyParam<DataType> _candleType;
private readonly List<decimal> _highs = [];
private readonly List<decimal> _lows = [];
private decimal? _support;
private decimal? _resistance;
private decimal? _prevClose;
/// <summary>
/// Candles on each side of a pivot.
/// </summary>
public int PivotLength
{
get => _pivotLength.Value;
set => _pivotLength.Value = value;
}
/// <summary>
/// Maximum distance of the close from the level, in percent.
/// </summary>
public decimal SupportResistanceDistance
{
get => _supportResistanceDistance.Value;
set => _supportResistanceDistance.Value = value;
}
/// <summary>
/// Stop loss percentage from entry price.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Take profit percentage from entry price.
/// </summary>
public decimal TakeProfitPercent
{
get => _takeProfitPercent.Value;
set => _takeProfitPercent.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public DynamicSupportAndResistancePivotStrategy()
{
_pivotLength = Param(nameof(PivotLength), 2)
.SetGreaterThanZero()
.SetDisplay("Pivot Length", "Candles on each side of a pivot", "Pivots");
_supportResistanceDistance = Param(nameof(SupportResistanceDistance), 0.4m)
.SetNotNegative()
.SetDisplay("S/R Distance %", "Maximum distance of the close from the level, in percent", "Pivots");
_stopLossPercent = Param(nameof(StopLossPercent), 10.0m)
.SetNotNegative()
.SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk");
_takeProfitPercent = Param(nameof(TakeProfitPercent), 26.0m)
.SetNotNegative()
.SetDisplay("Take Profit %", "Take profit percentage from entry price", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
ResetState();
}
private void ResetState()
{
_highs.Clear();
_lows.Clear();
_support = null;
_resistance = null;
_prevClose = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetState();
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var take = TakeProfitPercent > 0 ? new Unit(TakeProfitPercent, UnitTypes.Percent) : new Unit();
var stop = StopLossPercent > 0 ? new Unit(StopLossPercent, UnitTypes.Percent) : new Unit();
StartProtection(take, stop, useMarketOrders: true);
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var length = PivotLength;
var size = length * 2 + 1;
_highs.Add(candle.HighPrice);
_lows.Add(candle.LowPrice);
if (_highs.Count > size)
{
_highs.RemoveAt(0);
_lows.RemoveAt(0);
}
if (_highs.Count == size)
{
var centerHigh = _highs[length];
var centerLow = _lows[length];
var isPivotHigh = true;
var isPivotLow = true;
for (var i = 0; i < size; i++)
{
if (i == length)
continue;
if (_highs[i] >= centerHigh)
isPivotHigh = false;
if (_lows[i] <= centerLow)
isPivotLow = false;
}
if (isPivotHigh)
_resistance = centerHigh;
if (isPivotLow)
_support = centerLow;
}
var close = candle.ClosePrice;
var prevClose = _prevClose;
_prevClose = close;
if (prevClose is not decimal pc)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var maxDistance = SupportResistanceDistance / 100m;
var longSignal = _support is decimal support && support > 0
&& pc <= support && close > support
&& Math.Abs(close - support) / support <= maxDistance;
var shortSignal = _resistance is decimal resistance && resistance > 0
&& pc >= resistance && close < resistance
&& Math.Abs(close - resistance) / resistance <= maxDistance;
if (longSignal && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (shortSignal && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Strategies import Strategy
class dynamic_support_and_resistance_pivot_strategy(Strategy):
"""
Dynamic Support and Resistance Pivot strategy.
Support is the last pivot low and resistance the last pivot high, a pivot being a candle whose low (high) is beyond those of the
PivotLength candles on each side. A close crossing above support goes long and a close crossing below resistance goes short,
reversing an opposite position, when the close is within SupportResistanceDistance percent of that level. Percent stop loss and
take profit manage the position.
"""
def __init__(self):
super(dynamic_support_and_resistance_pivot_strategy, self).__init__()
self._pivot_length = self.Param("PivotLength", 2).SetGreaterThanZero().SetDisplay("Pivot Length", "Candles on each side of a pivot", "Pivots")
self._support_resistance_distance = self.Param("SupportResistanceDistance", 0.4).SetNotNegative().SetDisplay("S/R Distance %", "Maximum distance of the close from the level, in percent", "Pivots")
self._stop_loss_percent = self.Param("StopLossPercent", 10.0).SetNotNegative().SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk")
self._take_profit_percent = self.Param("TakeProfitPercent", 26.0).SetNotNegative().SetDisplay("Take Profit %", "Take profit percentage from entry price", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def _reset_state(self):
self._highs = []
self._lows = []
self._support = None
self._resistance = None
self._prev_close = None
def OnReseted(self):
super(dynamic_support_and_resistance_pivot_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(dynamic_support_and_resistance_pivot_strategy, self).OnStarted2(time)
self._reset_state()
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._process_candle).Start()
take_percent = Decimal(self._take_profit_percent.Value)
stop_percent = Decimal(self._stop_loss_percent.Value)
take = Unit(take_percent, UnitTypes.Percent) if take_percent > 0 else Unit()
stop = Unit(stop_percent, UnitTypes.Percent) if stop_percent > 0 else Unit()
self.StartProtection(take, stop, useMarketOrders=True)
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
length = self._pivot_length.Value
size = length * 2 + 1
self._highs.append(candle.HighPrice)
self._lows.append(candle.LowPrice)
if len(self._highs) > size:
self._highs.pop(0)
self._lows.pop(0)
if len(self._highs) == size:
center_high = self._highs[length]
center_low = self._lows[length]
is_pivot_high = True
is_pivot_low = True
for i in range(size):
if i == length:
continue
if self._highs[i] >= center_high:
is_pivot_high = False
if self._lows[i] <= center_low:
is_pivot_low = False
if is_pivot_high:
self._resistance = center_high
if is_pivot_low:
self._support = center_low
close = candle.ClosePrice
pc = self._prev_close
self._prev_close = close
if pc is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
max_distance = Decimal(self._support_resistance_distance.Value) / Decimal(100)
support = self._support
resistance = self._resistance
long_signal = support is not None and support > 0 and pc <= support and close > support and \
abs(close - support) / support <= max_distance
short_signal = resistance is not None and resistance > 0 and pc >= resistance and close < resistance and \
abs(close - resistance) / resistance <= max_distance
if long_signal and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif short_signal and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
def CreateClone(self):
return dynamic_support_and_resistance_pivot_strategy()