SMCオーダーブロックゾーン戦略
この戦略は、スイング高値と安値を特定してプレミアムゾーンとディスカウントゾーンを定義します。単純移動平均線がトレンドフィルターとして機能し、最近のオーダーブロックがエントリーを確認します。オーダーブロックの確認を伴いながら価格が一方のゾーンから均衡に向かって動いたときに取引を実行し、保護のためにパーセンテージストップロスを使用します。
詳細
- エントリー条件:
- ロング取引では終値が均衡を下回るがディスカウントゾーンとSMAを上回る。
- ショート取引では終値が均衡を上回るがプレミアムゾーンとSMAを下回る。
- 価格は各オーダーブロックレベルに触れる必要がある。
- ロング/ショート: ロング、ショート、または両方を設定可能。
- エグジット条件: 反対のシグナルまたはストップロス。
- ストップ: パーセンテージストップロス。
- デフォルト値:
SwingHighLength= 8SwingLowLength= 8SmaLength= 50OrderBlockLength= 20StopLossPercent= 2
- フィルター:
- カテゴリ: トレンドとSMC
- 方向: ユーザー定義
- インジケーター: SMA, Highest, Lowest
- ストップ: はい
- 複雑さ: 中
- 時間軸: 任意
- 季節性: いいえ
- ニューラルネットワーク: いいえ
- ダイバージェンス: いいえ
- リスクレベル: 中
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// SMC Order Block Zones Strategy.
/// The swing high of SwingHighLength bars is the premium zone, the swing low of SwingLowLength bars the discount zone and
/// their midpoint the equilibrium. The bullish order block is the lowest low and the bearish order block the highest high of
/// the previous OrderBlockLength bars. A long opens when the close is between the discount zone and the equilibrium, above
/// the SMA, and the candle touched the bullish order block; a short mirrors this between the equilibrium and the premium zone
/// below the SMA after touching the bearish order block. An opposite signal closes or reverses the position and a percent stop
/// limits the loss.
/// </summary>
public class SmcOrderBlockZonesStrategy : Strategy
{
private readonly StrategyParam<int> _swingHighLength;
private readonly StrategyParam<int> _swingLowLength;
private readonly StrategyParam<int> _smaLength;
private readonly StrategyParam<int> _orderBlockLength;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<bool> _allowLong;
private readonly StrategyParam<bool> _allowShort;
private readonly StrategyParam<DataType> _candleType;
private Highest _swingHigh;
private Lowest _swingLow;
private Highest _blockHigh;
private Lowest _blockLow;
private decimal? _prevBlockHigh;
private decimal? _prevBlockLow;
/// <summary>
/// Bars of the swing high.
/// </summary>
public int SwingHighLength
{
get => _swingHighLength.Value;
set => _swingHighLength.Value = value;
}
/// <summary>
/// Bars of the swing low.
/// </summary>
public int SwingLowLength
{
get => _swingLowLength.Value;
set => _swingLowLength.Value = value;
}
/// <summary>
/// SMA period of the trend filter.
/// </summary>
public int SmaLength
{
get => _smaLength.Value;
set => _smaLength.Value = value;
}
/// <summary>
/// Bars searched for order blocks.
/// </summary>
public int OrderBlockLength
{
get => _orderBlockLength.Value;
set => _orderBlockLength.Value = value;
}
/// <summary>
/// Stop loss percentage from entry price.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Allow long trades.
/// </summary>
public bool AllowLong
{
get => _allowLong.Value;
set => _allowLong.Value = value;
}
/// <summary>
/// Allow short trades.
/// </summary>
public bool AllowShort
{
get => _allowShort.Value;
set => _allowShort.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public SmcOrderBlockZonesStrategy()
{
_swingHighLength = Param(nameof(SwingHighLength), 8)
.SetGreaterThanZero()
.SetDisplay("Swing High Length", "Bars of the swing high", "Zones");
_swingLowLength = Param(nameof(SwingLowLength), 8)
.SetGreaterThanZero()
.SetDisplay("Swing Low Length", "Bars of the swing low", "Zones");
_smaLength = Param(nameof(SmaLength), 50)
.SetGreaterThanZero()
.SetDisplay("SMA Length", "SMA period of the trend filter", "Indicators");
_orderBlockLength = Param(nameof(OrderBlockLength), 20)
.SetGreaterThanZero()
.SetDisplay("Order Block Length", "Bars searched for order blocks", "Zones");
_stopLossPercent = Param(nameof(StopLossPercent), 2m)
.SetNotNegative()
.SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk");
_allowLong = Param(nameof(AllowLong), true)
.SetDisplay("Allow Long", "Allow long trades", "Trading");
_allowShort = Param(nameof(AllowShort), true)
.SetDisplay("Allow Short", "Allow short trades", "Trading");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevBlockHigh = null;
_prevBlockLow = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevBlockHigh = null;
_prevBlockLow = null;
var sma = new SimpleMovingAverage { Length = SmaLength };
_swingHigh = new Highest { Length = SwingHighLength };
_swingLow = new Lowest { Length = SwingLowLength };
_blockHigh = new Highest { Length = OrderBlockLength };
_blockLow = new Lowest { Length = OrderBlockLength };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(sma, ProcessCandle)
.Start();
if (StopLossPercent > 0m)
{
StartProtection(new Unit(), new Unit(StopLossPercent, UnitTypes.Percent), useMarketOrders: true, isLocalStop: true);
// The stop has to see prices between candles, not only at their close.
foreach (var field in new[] { Level1Fields.BestBidPrice, Level1Fields.BestAskPrice })
{
var quotes = new Subscription(DataType.Level1, Security);
quotes.MarketData.BuildField = field;
SubscribeLevel1(quotes).Bind(ObserveProtectionQuote).Start();
}
}
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, sma);
DrawOwnTrades(area);
}
}
private void ObserveProtectionQuote(Level1ChangeMessage quote)
{
// The high-level handler activates native protection before this callback, also between signal bars.
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue smaValue)
{
if (candle.State != CandleStates.Finished)
return;
var time = candle.OpenTime;
var premium = _swingHigh.Process(candle.HighPrice, time, true).ToDecimal();
var discount = _swingLow.Process(candle.LowPrice, time, true).ToDecimal();
// Order blocks are taken from the candles before this one so the current candle can touch them.
var bullishBlock = _prevBlockLow;
var bearishBlock = _prevBlockHigh;
var blockHigh = _blockHigh.Process(candle.HighPrice, time, true).ToDecimal();
var blockLow = _blockLow.Process(candle.LowPrice, time, true).ToDecimal();
if (_blockHigh.IsFormed && _blockLow.IsFormed)
{
_prevBlockHigh = blockHigh;
_prevBlockLow = blockLow;
}
if (!smaValue.IsFormed || !_swingHigh.IsFormed || !_swingLow.IsFormed || bullishBlock is not decimal bullBlock || bearishBlock is not decimal bearBlock)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var sma = smaValue.GetValue<decimal>();
var close = candle.ClosePrice;
var equilibrium = (premium + discount) / 2m;
var longSignal = close < equilibrium && close > discount && close > sma && candle.LowPrice <= bullBlock;
var shortSignal = close > equilibrium && close < premium && close < sma && candle.HighPrice >= bearBlock;
if (longSignal)
{
if (AllowLong && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (Position < 0)
BuyMarket(-Position);
}
else if (shortSignal)
{
if (AllowShort && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
else if (Position > 0)
SellMarket(Position);
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes, Level1Fields
from StockSharp.BusinessEntities import Subscription
from StockSharp.Algo.Indicators import SimpleMovingAverage, Highest, Lowest
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class smc_order_block_zones_strategy(Strategy):
"""
SMC Order Block Zones Strategy.
The swing high of SwingHighLength bars is the premium zone, the swing low of SwingLowLength bars the discount zone and
their midpoint the equilibrium. The bullish order block is the lowest low and the bearish order block the highest high of
the previous OrderBlockLength bars. A long opens when the close is between the discount zone and the equilibrium, above
the SMA, and the candle touched the bullish order block; a short mirrors this between the equilibrium and the premium zone
below the SMA after touching the bearish order block. An opposite signal closes or reverses the position and a percent stop
limits the loss.
"""
def __init__(self):
super(smc_order_block_zones_strategy, self).__init__()
self._swing_high_length = self.Param("SwingHighLength", 8).SetGreaterThanZero().SetDisplay("Swing High Length", "Bars of the swing high", "Zones")
self._swing_low_length = self.Param("SwingLowLength", 8).SetGreaterThanZero().SetDisplay("Swing Low Length", "Bars of the swing low", "Zones")
self._sma_length = self.Param("SmaLength", 50).SetGreaterThanZero().SetDisplay("SMA Length", "SMA period of the trend filter", "Indicators")
self._order_block_length = self.Param("OrderBlockLength", 20).SetGreaterThanZero().SetDisplay("Order Block Length", "Bars searched for order blocks", "Zones")
self._stop_loss_percent = self.Param("StopLossPercent", 2.0).SetNotNegative().SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk")
self._allow_long = self.Param("AllowLong", True).SetDisplay("Allow Long", "Allow long trades", "Trading")
self._allow_short = self.Param("AllowShort", True).SetDisplay("Allow Short", "Allow short trades", "Trading")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._swing_high = None
self._swing_low = None
self._block_high = None
self._block_low = None
self._prev_block_high = None
self._prev_block_low = None
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(smc_order_block_zones_strategy, self).OnReseted()
self._prev_block_high = None
self._prev_block_low = None
def OnStarted2(self, time):
super(smc_order_block_zones_strategy, self).OnStarted2(time)
self._prev_block_high = None
self._prev_block_low = None
sma = SimpleMovingAverage()
sma.Length = self._sma_length.Value
self._swing_high = Highest()
self._swing_high.Length = self._swing_high_length.Value
self._swing_low = Lowest()
self._swing_low.Length = self._swing_low_length.Value
self._block_high = Highest()
self._block_high.Length = self._order_block_length.Value
self._block_low = Lowest()
self._block_low.Length = self._order_block_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(sma, self._process_candle).Start()
stop = float(self._stop_loss_percent.Value)
if stop > 0:
self.StartProtection(Unit(), Unit(Decimal(stop), UnitTypes.Percent), useMarketOrders=True, isLocalStop=True)
# The stop has to see prices between candles, not only at their close.
for field in (Level1Fields.BestBidPrice, Level1Fields.BestAskPrice):
quotes = Subscription(DataType.Level1, self.Security)
quotes.MarketData.BuildField = field
self.SubscribeLevel1(quotes).Bind(self._observe_protection_quote).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, sma)
self.DrawOwnTrades(area)
def _observe_protection_quote(self, quote):
# The high-level handler activates native protection before the callback, including between bars.
pass
def _process_candle(self, candle, sma_value):
if candle.State != CandleStates.Finished:
return
time = candle.OpenTime
premium = float(process_float(self._swing_high, candle.HighPrice, time, True).GetValue[Decimal](None))
discount = float(process_float(self._swing_low, candle.LowPrice, time, True).GetValue[Decimal](None))
# Order blocks are taken from the candles before this one so the current candle can touch them.
bull_block = self._prev_block_low
bear_block = self._prev_block_high
block_high = float(process_float(self._block_high, candle.HighPrice, time, True).GetValue[Decimal](None))
block_low = float(process_float(self._block_low, candle.LowPrice, time, True).GetValue[Decimal](None))
if self._block_high.IsFormed and self._block_low.IsFormed:
self._prev_block_high = block_high
self._prev_block_low = block_low
if not sma_value.IsFormed or not self._swing_high.IsFormed or not self._swing_low.IsFormed or bull_block is None or bear_block is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
sma = float(sma_value.GetValue[Decimal](None))
close = float(candle.ClosePrice)
equilibrium = (premium + discount) / 2.0
long_signal = close < equilibrium and close > discount and close > sma and float(candle.LowPrice) <= bull_block
short_signal = close > equilibrium and close < premium and close < sma and float(candle.HighPrice) >= bear_block
if long_signal:
if self._allow_long.Value and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif self.Position < 0:
self.BuyMarket(-self.Position)
elif short_signal:
if self._allow_short.Value and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
elif self.Position > 0:
self.SellMarket(self.Position)
def CreateClone(self):
return smc_order_block_zones_strategy()