Estratégia de Zonas SMC de Blocos de Ordens
A estratégia identifica máximas e mínimas de swing para definir zonas premium e de desconto. Uma média móvel simples atua como filtro de tendência e blocos de ordens recentes confirmam as entradas. As operações são executadas quando o preço se move de uma zona em direção ao equilíbrio com confirmação do bloco de ordens, usando um stop loss percentual para proteção.
Detalhes
- Critérios de entrada:
- Fechamento abaixo do equilíbrio mas acima da zona de desconto e SMA para operações compradas.
- Fechamento acima do equilíbrio mas abaixo da zona premium e SMA para operações vendidas.
- O preço deve tocar o nível do bloco de ordens respectivo.
- Comprado/Vendido: Configurável comprado, vendido ou ambos.
- Critérios de saída: Sinal oposto ou stop loss.
- Stops: Stop loss percentual.
- Valores padrão:
SwingHighLength= 8SwingLowLength= 8SmaLength= 50OrderBlockLength= 20StopLossPercent= 2
- Filtros:
- Categoria: Tendência e SMC
- Direção: Definido pelo usuário
- Indicadores: SMA, Highest, Lowest
- Stops: Sim
- Complexidade: Médio
- Período: Qualquer
- Sazonalidade: Não
- Redes neurais: Não
- Divergência: Não
- Nível de risco: Médio
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()