IU レンジ超え戦略
ローソク足の実体が直近のローソク足の前回レンジより大きいときにトレードを開くブレイクアウト戦略。
このシステムは現在のローソク足の実体を、設定可能なルックバック期間内の最高・最低の始値/終値間のレンジと比較します。実体が前回のレンジを超えた場合、ローソク足の方向にエントリーし、設定可能なストップ手法でリスクを管理します。
詳細
- エントリー条件: ローソク足の実体が前回レンジより大きい;方向はローソク足の実体による。
- ロング/ショート: 両方。
- エグジット条件: ストップロスまたはテイクプロフィット。
- ストップ: 前のローソク足、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()