流動性ブレイクアウト戦略
この戦略は、ピボット高値と安値によって定義された最近の価格レンジからのブレイクアウトを取引します。価格が前のレンジの極値を超えてクローズしたときにポジションをオープンします。オプションのストップロスはSuperTrendラインまたは固定パーセンテージを使用できます。
詳細
- エントリー条件:
終値 > 前レンジ高値→ ロング終値 < 前レンジ安値→ ショート
- ロング/ショート: 設定可能(ロング、ショート、両方)。
- エグジット条件: 反対方向のブレイクアウトまたはストップロス。
- ストップ: SuperTrendまたは固定パーセンテージ。
- デフォルト値:
PivotLength= 12StopLoss= SuperTrendFixedPercentage= 0.1SuperTrendPeriod= 10SuperTrendMultiplier= 3
- フィルター:
- カテゴリ: ブレイクアウト
- 方向: 両方
- インジケーター: Highest, Lowest, SuperTrend
- ストップ: オプション
- 複雑さ: 低
- 時間軸: 1h
using System;
using System.Collections.Generic;
using System.Linq;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Liquidity Breakout strategy.
/// The range is bounded by the latest pivot high and pivot low, each confirmed by PivotLength candles on both sides. A close crossing
/// above the range high goes long and a close crossing below the range low goes short, reversing an opposite position. Direction limits
/// the allowed side: a breakout of the disabled side only closes the position. The stop is either the SuperTrend line (exit on a close
/// beyond it) or a fixed percentage from the entry price.
/// </summary>
public class LiquidityBreakoutStrategy : Strategy
{
/// <summary>
/// Stop loss types.
/// </summary>
public enum StopLossModes
{
/// <summary>
/// No stop loss.
/// </summary>
None,
/// <summary>
/// Exit when the close crosses the SuperTrend line.
/// </summary>
SuperTrend,
/// <summary>
/// Fixed percentage from the entry price.
/// </summary>
FixedPercentage,
}
/// <summary>
/// Allowed trade directions.
/// </summary>
public enum TradeDirections
{
/// <summary>
/// Long and short trades.
/// </summary>
Both,
/// <summary>
/// Long trades only.
/// </summary>
Long,
/// <summary>
/// Short trades only.
/// </summary>
Short,
}
private readonly StrategyParam<int> _pivotLength;
private readonly StrategyParam<StopLossModes> _stopLoss;
private readonly StrategyParam<decimal> _fixedPercentage;
private readonly StrategyParam<int> _superTrendPeriod;
private readonly StrategyParam<decimal> _superTrendMultiplier;
private readonly StrategyParam<TradeDirections> _direction;
private readonly StrategyParam<DataType> _candleType;
private readonly List<decimal> _highs = new();
private readonly List<decimal> _lows = new();
private decimal? _rangeHigh;
private decimal? _rangeLow;
private decimal? _prevClose;
/// <summary>
/// Candles on each side that confirm a pivot.
/// </summary>
public int PivotLength
{
get => _pivotLength.Value;
set => _pivotLength.Value = value;
}
/// <summary>
/// Stop loss type.
/// </summary>
public StopLossModes StopLoss
{
get => _stopLoss.Value;
set => _stopLoss.Value = value;
}
/// <summary>
/// Fixed stop loss percentage from the entry price.
/// </summary>
public decimal FixedPercentage
{
get => _fixedPercentage.Value;
set => _fixedPercentage.Value = value;
}
/// <summary>
/// ATR period of SuperTrend.
/// </summary>
public int SuperTrendPeriod
{
get => _superTrendPeriod.Value;
set => _superTrendPeriod.Value = value;
}
/// <summary>
/// ATR multiplier of SuperTrend.
/// </summary>
public decimal SuperTrendMultiplier
{
get => _superTrendMultiplier.Value;
set => _superTrendMultiplier.Value = value;
}
/// <summary>
/// Allowed trade directions.
/// </summary>
public TradeDirections Direction
{
get => _direction.Value;
set => _direction.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public LiquidityBreakoutStrategy()
{
_pivotLength = Param(nameof(PivotLength), 12)
.SetGreaterThanZero()
.SetDisplay("Pivot Length", "Candles on each side that confirm a pivot", "Range");
_stopLoss = Param(nameof(StopLoss), StopLossModes.SuperTrend)
.SetDisplay("Stop Loss", "Stop loss type", "Risk");
_fixedPercentage = Param(nameof(FixedPercentage), 0.1m)
.SetNotNegative()
.SetDisplay("Fixed Percentage", "Fixed stop loss percentage from the entry price", "Risk");
_superTrendPeriod = Param(nameof(SuperTrendPeriod), 10)
.SetGreaterThanZero()
.SetDisplay("SuperTrend Period", "ATR period of SuperTrend", "Risk");
_superTrendMultiplier = Param(nameof(SuperTrendMultiplier), 3m)
.SetGreaterThanZero()
.SetDisplay("SuperTrend Multiplier", "ATR multiplier of SuperTrend", "Risk");
_direction = Param(nameof(Direction), TradeDirections.Both)
.SetDisplay("Direction", "Allowed trade directions", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(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()
{
_highs.Clear();
_lows.Clear();
_rangeHigh = null;
_rangeLow = null;
_prevClose = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetState();
var superTrend = new SuperTrend { Length = SuperTrendPeriod, Multiplier = SuperTrendMultiplier };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(superTrend, ProcessCandle)
.Start();
if (StopLoss == StopLossModes.FixedPercentage && FixedPercentage > 0)
StartProtection(new Unit(), new Unit(FixedPercentage, UnitTypes.Percent), useMarketOrders: true);
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, superTrend);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue superTrendValue)
{
if (candle.State != CandleStates.Finished)
return;
// Breakouts are measured against the range known before this candle.
var rangeHigh = _rangeHigh;
var rangeLow = _rangeLow;
var prevClose = _prevClose;
_prevClose = candle.ClosePrice;
UpdatePivots(candle);
if (!IsFormedAndOnlineAndAllowTrading())
return;
var close = candle.ClosePrice;
if (StopLoss == StopLossModes.SuperTrend && superTrendValue.IsFormed)
{
var line = superTrendValue.GetValue<decimal>();
if (Position > 0 && close < line)
{
SellMarket(Position);
return;
}
if (Position < 0 && close > line)
{
BuyMarket(-Position);
return;
}
}
if (prevClose is not decimal lastClose)
return;
var longBreakout = rangeHigh is decimal high && lastClose <= high && close > high;
var shortBreakout = rangeLow is decimal low && lastClose >= low && close < low;
if (longBreakout)
{
if (Direction != TradeDirections.Short && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (Direction == TradeDirections.Short && Position < 0)
BuyMarket(-Position);
}
else if (shortBreakout)
{
if (Direction != TradeDirections.Long && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
else if (Direction == TradeDirections.Long && Position > 0)
SellMarket(Position);
}
}
private void UpdatePivots(ICandleMessage candle)
{
var size = PivotLength * 2 + 1;
_highs.Add(candle.HighPrice);
_lows.Add(candle.LowPrice);
if (_highs.Count > size)
{
_highs.RemoveAt(0);
_lows.RemoveAt(0);
}
if (_highs.Count < size)
return;
// The middle candle is a pivot when no candle within PivotLength on either side exceeds it.
var centerHigh = _highs[PivotLength];
if (centerHigh >= _highs.Max())
_rangeHigh = centerHigh;
var centerLow = _lows[PivotLength];
if (centerLow <= _lows.Min())
_rangeLow = centerLow;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import SuperTrend
from StockSharp.Algo.Strategies import Strategy
STOP_NONE = 0
STOP_SUPERTREND = 1
STOP_FIXED_PERCENTAGE = 2
DIRECTION_BOTH = 0
DIRECTION_LONG = 1
DIRECTION_SHORT = 2
class liquidity_breakout_strategy(Strategy):
"""
Liquidity Breakout strategy.
The range is bounded by the latest pivot high and pivot low, each confirmed by PivotLength candles on both sides. A close crossing
above the range high goes long and a close crossing below the range low goes short, reversing an opposite position. Direction limits
the allowed side: a breakout of the disabled side only closes the position. The stop is either the SuperTrend line (exit on a close
beyond it) or a fixed percentage from the entry price.
"""
def __init__(self):
super(liquidity_breakout_strategy, self).__init__()
self._pivot_length = self.Param("PivotLength", 12).SetGreaterThanZero().SetDisplay("Pivot Length", "Candles on each side that confirm a pivot", "Range")
self._stop_loss = self.Param("StopLoss", STOP_SUPERTREND).SetDisplay("Stop Loss", "0 None, 1 SuperTrend, 2 FixedPercentage", "Risk")
self._fixed_percentage = self.Param("FixedPercentage", 0.1).SetNotNegative().SetDisplay("Fixed Percentage", "Fixed stop loss percentage from the entry price", "Risk")
self._super_trend_period = self.Param("SuperTrendPeriod", 10).SetGreaterThanZero().SetDisplay("SuperTrend Period", "ATR period of SuperTrend", "Risk")
self._super_trend_multiplier = self.Param("SuperTrendMultiplier", 3.0).SetGreaterThanZero().SetDisplay("SuperTrend Multiplier", "ATR multiplier of SuperTrend", "Risk")
self._direction = self.Param("Direction", DIRECTION_BOTH).SetDisplay("Direction", "0 Both, 1 Long, 2 Short", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))).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._range_high = None
self._range_low = None
self._prev_close = None
def OnReseted(self):
super(liquidity_breakout_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(liquidity_breakout_strategy, self).OnStarted2(time)
self._reset_state()
super_trend = SuperTrend()
super_trend.Length = self._super_trend_period.Value
super_trend.Multiplier = Decimal(self._super_trend_multiplier.Value)
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(super_trend, self._process_candle).Start()
if self._stop_loss.Value == STOP_FIXED_PERCENTAGE and self._fixed_percentage.Value > 0:
self.StartProtection(Unit(), Unit(Decimal(self._fixed_percentage.Value), UnitTypes.Percent), useMarketOrders=True)
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, super_trend)
self.DrawOwnTrades(area)
def _process_candle(self, candle, super_trend_value):
if candle.State != CandleStates.Finished:
return
# Breakouts are measured against the range known before this candle.
range_high = self._range_high
range_low = self._range_low
prev_close = self._prev_close
self._prev_close = candle.ClosePrice
self._update_pivots(candle)
if not self.IsFormedAndOnlineAndAllowTrading():
return
close = candle.ClosePrice
if self._stop_loss.Value == STOP_SUPERTREND and super_trend_value.IsFormed:
line = super_trend_value.GetValue[Decimal](None)
if self.Position > 0 and close < line:
self.SellMarket(self.Position)
return
if self.Position < 0 and close > line:
self.BuyMarket(-self.Position)
return
if prev_close is None:
return
long_breakout = range_high is not None and prev_close <= range_high and close > range_high
short_breakout = range_low is not None and prev_close >= range_low and close < range_low
direction = self._direction.Value
if long_breakout:
if direction != DIRECTION_SHORT and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif direction == DIRECTION_SHORT and self.Position < 0:
self.BuyMarket(-self.Position)
elif short_breakout:
if direction != DIRECTION_LONG and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
elif direction == DIRECTION_LONG and self.Position > 0:
self.SellMarket(self.Position)
def _update_pivots(self, candle):
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:
return
# The middle candle is a pivot when no candle within PivotLength on either side exceeds it.
center_high = self._highs[length]
if center_high >= max(self._highs):
self._range_high = center_high
center_low = self._lows[length]
if center_low <= min(self._lows):
self._range_low = center_low
def CreateClone(self):
return liquidity_breakout_strategy()