IU Bigger Than Range 策略
当当前K线实体大于之前区间时入场的突破策略。
系统比较当前K线实体与设定周期内开收价的最高和最低范围。如果实体超过前一范围,则按K线方向开仓,并根据所选方法设置止损和止盈。
细节
- 入场条件:K线实体大于前一范围;方向取决于K线实体。
- 多空方向:双向。
- 出场条件:止损或止盈触发。
- 止损:前一K线、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()