Стратегия "IU Bigger Than Range"
Стратегия пробоя, открывающая сделки, когда тело свечи больше предыдущего диапазона за заданный период.
Система сравнивает тело текущей свечи с диапазоном между максимальными и минимальными значениями цены открытия/закрытия за период. При превышении диапазона открывается сделка в направлении свечи, стопы задаются выбранным методом.
Детали
- Условия входа: тело свечи больше предыдущего диапазона; направление по телу свечи.
- Лонг/Шорт: оба направления.
- Условия выхода: достижение стоп-лосса или тейк-профита.
- Стопы: предыдущая свеча, ATR или уровни свинга.
- Значения по умолчанию:
LookbackPeriod= 22RiskToReward= 3StopLossMethod= PreviousHighLowAtrLength= 14AtrFactor= 2mSwingLength= 10CandleType= TimeSpan.FromMinutes(1)
- Фильтры:
- Категория: Пробой
- Направление: Оба
- Индикаторы: Highest, Lowest, ATR
- Стопы: Да
- Сложность: Средняя
- Таймфрейм: Любой
- Сезонность: Нет
- Нейросети: Нет
- Дивергенция: Нет
- Уровень риска: Средний
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>
/// Stop loss placement methods.
/// </summary>
public enum IuBiggerThanRangeStopMethods
{
/// <summary>
/// Low (long) or high (short) of the previous candle.
/// </summary>
PreviousHighLow,
/// <summary>
/// ATR multiplied by AtrFactor away from the entry close.
/// </summary>
Atr,
/// <summary>
/// Lowest low (long) or highest high (short) of the last SwingLength candles.
/// </summary>
Swing,
}
/// <summary>
/// IU bigger than range strategy.
/// The previous range spans the highest open/close and lowest open/close of the LookbackPeriod candles before the current one.
/// A candle whose body is larger than that range enters in its direction, reversing an opposite position. The stop is placed by
/// StopLossMethod and the target sits RiskToReward times the stop distance away; touching either closes the trade.
/// </summary>
public class IuBiggerThanRangeStrategy : Strategy
{
private readonly StrategyParam<int> _lookbackPeriod;
private readonly StrategyParam<int> _riskToReward;
private readonly StrategyParam<IuBiggerThanRangeStopMethods> _stopLossMethod;
private readonly StrategyParam<int> _atrLength;
private readonly StrategyParam<decimal> _atrFactor;
private readonly StrategyParam<int> _swingLength;
private readonly StrategyParam<DataType> _candleType;
private Highest _bodyHigh;
private Lowest _bodyLow;
private Highest _swingHigh;
private Lowest _swingLow;
private decimal? _prevRangeHigh;
private decimal? _prevRangeLow;
private decimal? _prevCandleHigh;
private decimal? _prevCandleLow;
private decimal? _stopPrice;
private decimal? _targetPrice;
/// <summary>
/// Candles the previous range spans.
/// </summary>
public int LookbackPeriod
{
get => _lookbackPeriod.Value;
set => _lookbackPeriod.Value = value;
}
/// <summary>
/// Target distance as a multiple of the stop distance.
/// </summary>
public int RiskToReward
{
get => _riskToReward.Value;
set => _riskToReward.Value = value;
}
/// <summary>
/// How the stop loss is placed.
/// </summary>
public IuBiggerThanRangeStopMethods StopLossMethod
{
get => _stopLossMethod.Value;
set => _stopLossMethod.Value = value;
}
/// <summary>
/// ATR period for the ATR stop.
/// </summary>
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
/// <summary>
/// ATR multiplier for the ATR stop.
/// </summary>
public decimal AtrFactor
{
get => _atrFactor.Value;
set => _atrFactor.Value = value;
}
/// <summary>
/// Candles the swing stop looks back over.
/// </summary>
public int SwingLength
{
get => _swingLength.Value;
set => _swingLength.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public IuBiggerThanRangeStrategy()
{
_lookbackPeriod = Param(nameof(LookbackPeriod), 22)
.SetGreaterThanZero()
.SetDisplay("Lookback Period", "Candles the previous range spans", "Parameters");
_riskToReward = Param(nameof(RiskToReward), 3)
.SetGreaterThanZero()
.SetDisplay("Risk To Reward", "Target distance as a multiple of the stop distance", "Risk");
_stopLossMethod = Param(nameof(StopLossMethod), IuBiggerThanRangeStopMethods.PreviousHighLow)
.SetDisplay("Stop Loss Method", "How the stop loss is placed", "Risk");
_atrLength = Param(nameof(AtrLength), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Length", "ATR period for the ATR stop", "Risk");
_atrFactor = Param(nameof(AtrFactor), 2m)
.SetGreaterThanZero()
.SetDisplay("ATR Factor", "ATR multiplier for the ATR stop", "Risk");
_swingLength = Param(nameof(SwingLength), 10)
.SetGreaterThanZero()
.SetDisplay("Swing Length", "Candles the swing stop looks back over", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).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()
{
_prevRangeHigh = _prevRangeLow = null;
_prevCandleHigh = _prevCandleLow = null;
_stopPrice = _targetPrice = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetState();
_bodyHigh = new Highest { Length = LookbackPeriod };
_bodyLow = new Lowest { Length = LookbackPeriod };
_swingHigh = new Highest { Length = SwingLength };
_swingLow = new Lowest { Length = SwingLength };
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal atrValue)
{
if (candle.State != CandleStates.Finished)
return;
var time = candle.OpenTime;
var bodyTop = Math.Max(candle.OpenPrice, candle.ClosePrice);
var bodyBottom = Math.Min(candle.OpenPrice, candle.ClosePrice);
// The range is measured on the candles before this one.
var rangeHigh = _prevRangeHigh;
var rangeLow = _prevRangeLow;
var prevHigh = _prevCandleHigh;
var prevLow = _prevCandleLow;
var bodyHighValue = _bodyHigh.Process(new DecimalIndicatorValue(_bodyHigh, bodyTop, time) { IsFinal = true });
var bodyLowValue = _bodyLow.Process(new DecimalIndicatorValue(_bodyLow, bodyBottom, time) { IsFinal = true });
var swingHighValue = _swingHigh.Process(new DecimalIndicatorValue(_swingHigh, candle.HighPrice, time) { IsFinal = true });
var swingLowValue = _swingLow.Process(new DecimalIndicatorValue(_swingLow, candle.LowPrice, time) { IsFinal = true });
_prevRangeHigh = _bodyHigh.IsFormed ? bodyHighValue.GetValue<decimal>() : null;
_prevRangeLow = _bodyLow.IsFormed ? bodyLowValue.GetValue<decimal>() : null;
_prevCandleHigh = candle.HighPrice;
_prevCandleLow = candle.LowPrice;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (Position > 0 && _stopPrice is decimal longStop && _targetPrice is decimal longTarget)
{
if (candle.LowPrice <= longStop || candle.HighPrice >= longTarget)
{
SellMarket(Position);
_stopPrice = _targetPrice = null;
return;
}
}
else if (Position < 0 && _stopPrice is decimal shortStop && _targetPrice is decimal shortTarget)
{
if (candle.HighPrice >= shortStop || candle.LowPrice <= shortTarget)
{
BuyMarket(-Position);
_stopPrice = _targetPrice = null;
return;
}
}
if (rangeHigh is not decimal high || rangeLow is not decimal low || prevHigh is not decimal lastHigh || prevLow is not decimal lastLow)
return;
var body = bodyTop - bodyBottom;
if (body <= high - low)
return;
var close = candle.ClosePrice;
if (candle.ClosePrice > candle.OpenPrice && Position <= 0)
{
decimal? stop = StopLossMethod switch
{
IuBiggerThanRangeStopMethods.Atr => close - atrValue * AtrFactor,
IuBiggerThanRangeStopMethods.Swing => _swingLow.IsFormed ? swingLowValue.GetValue<decimal>() : null,
_ => lastLow,
};
if (stop is not decimal stopPrice || stopPrice >= close)
return;
BuyMarket(Volume + Math.Abs(Position));
_stopPrice = stopPrice;
_targetPrice = close + (close - stopPrice) * RiskToReward;
}
else if (candle.ClosePrice < candle.OpenPrice && Position >= 0)
{
decimal? stop = StopLossMethod switch
{
IuBiggerThanRangeStopMethods.Atr => close + atrValue * AtrFactor,
IuBiggerThanRangeStopMethods.Swing => _swingHigh.IsFormed ? swingHighValue.GetValue<decimal>() : null,
_ => lastHigh,
};
if (stop is not decimal stopPrice || stopPrice <= close)
return;
SellMarket(Volume + Math.Abs(Position));
_stopPrice = stopPrice;
_targetPrice = close - (stopPrice - close) * RiskToReward;
}
}
}
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 AverageTrueRange, Highest, Lowest
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class iu_bigger_than_range_strategy(Strategy):
"""
IU bigger than range strategy.
The previous range spans the highest open/close and lowest open/close of the LookbackPeriod candles before the current one.
A candle whose body is larger than that range enters in its direction, reversing an opposite position. The stop is placed by
StopLossMethod (PreviousHighLow, Atr or Swing) and the target sits RiskToReward times the stop distance away; touching either
closes the trade.
"""
def __init__(self):
super(iu_bigger_than_range_strategy, self).__init__()
self._lookback_period = self.Param("LookbackPeriod", 22).SetGreaterThanZero().SetDisplay("Lookback Period", "Candles the previous range spans", "Parameters")
self._risk_to_reward = self.Param("RiskToReward", 3).SetGreaterThanZero().SetDisplay("Risk To Reward", "Target distance as a multiple of the stop distance", "Risk")
self._stop_loss_method = self.Param("StopLossMethod", "PreviousHighLow").SetDisplay("Stop Loss Method", "How the stop loss is placed: PreviousHighLow, Atr or Swing", "Risk")
self._atr_length = self.Param("AtrLength", 14).SetGreaterThanZero().SetDisplay("ATR Length", "ATR period for the ATR stop", "Risk")
self._atr_factor = self.Param("AtrFactor", 2.0).SetGreaterThanZero().SetDisplay("ATR Factor", "ATR multiplier for the ATR stop", "Risk")
self._swing_length = self.Param("SwingLength", 10).SetGreaterThanZero().SetDisplay("Swing Length", "Candles the swing stop looks back over", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._body_high = None
self._body_low = None
self._swing_high = None
self._swing_low = None
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def _reset_state(self):
self._prev_range_high = None
self._prev_range_low = None
self._prev_candle_high = None
self._prev_candle_low = None
self._stop_price = None
self._target_price = None
def OnReseted(self):
super(iu_bigger_than_range_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(iu_bigger_than_range_strategy, self).OnStarted2(time)
self._reset_state()
self._body_high = Highest()
self._body_high.Length = self._lookback_period.Value
self._body_low = Lowest()
self._body_low.Length = self._lookback_period.Value
self._swing_high = Highest()
self._swing_high.Length = self._swing_length.Value
self._swing_low = Lowest()
self._swing_low.Length = self._swing_length.Value
atr = AverageTrueRange()
atr.Length = self._atr_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(atr, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _process_candle(self, candle, atr_value):
if candle.State != CandleStates.Finished:
return
time = candle.OpenTime
open_p = float(candle.OpenPrice)
close = float(candle.ClosePrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
atr = float(atr_value)
body_top = max(open_p, close)
body_bottom = min(open_p, close)
# The range is measured on the candles before this one.
range_high = self._prev_range_high
range_low = self._prev_range_low
prev_high = self._prev_candle_high
prev_low = self._prev_candle_low
body_high_value = float(process_float(self._body_high, body_top, time, True))
body_low_value = float(process_float(self._body_low, body_bottom, time, True))
swing_high_value = float(process_float(self._swing_high, high, time, True))
swing_low_value = float(process_float(self._swing_low, low, time, True))
self._prev_range_high = body_high_value if self._body_high.IsFormed else None
self._prev_range_low = body_low_value if self._body_low.IsFormed else None
self._prev_candle_high = high
self._prev_candle_low = low
if not self.IsFormedAndOnlineAndAllowTrading():
return
if self.Position > 0 and self._stop_price is not None and self._target_price is not None:
if low <= self._stop_price or high >= self._target_price:
self.SellMarket(self.Position)
self._stop_price = None
self._target_price = None
return
elif self.Position < 0 and self._stop_price is not None and self._target_price is not None:
if high >= self._stop_price or low <= self._target_price:
self.BuyMarket(-self.Position)
self._stop_price = None
self._target_price = None
return
if range_high is None or range_low is None or prev_high is None or prev_low is None:
return
if body_top - body_bottom <= range_high - range_low:
return
method = str(self._stop_loss_method.Value)
rr = float(self._risk_to_reward.Value)
factor = float(self._atr_factor.Value)
if close > open_p and self.Position <= 0:
if method == "Atr":
stop = close - atr * factor
elif method == "Swing":
stop = swing_low_value if self._swing_low.IsFormed else None
else:
stop = prev_low
if stop is None or stop >= close:
return
self.BuyMarket(self.Volume + abs(self.Position))
self._stop_price = stop
self._target_price = close + (close - stop) * rr
elif close < open_p and self.Position >= 0:
if method == "Atr":
stop = close + atr * factor
elif method == "Swing":
stop = swing_high_value if self._swing_high.IsFormed else None
else:
stop = prev_high
if stop is None or stop <= close:
return
self.SellMarket(self.Volume + abs(self.Position))
self._stop_price = stop
self._target_price = close - (stop - close) * rr
def CreateClone(self):
return iu_bigger_than_range_strategy()