Estratégia de escadas
A Estratégia de escadas reproduz o comportamento do especialista MetaTrader original. Ele começa colocando ordens de stop simétricas em torno do preço de venda atual e, em seguida, reconstrói continuamente a grade em torno do preenchimento mais recente. Os lucros são acumulados em etapas de preços (pips) sem ponderação por volume, exatamente como no script de origem. Quando uma meta de lucro é atingida, a estratégia liquida todas as posições por ordem de mercado, remove quaisquer stops pendentes e redefine a grade.
Lógica de negociação
- Quando nenhuma posição estiver aberta, coloque um stop de compra e um stop de venda a uma distância de
ChannelSteps / 2 passos de preço acima e abaixo do preço de venda atual.
- Depois que a primeira ordem stop for preenchida, rearme a grade em torno do preço executado:
- Se houver menos de duas ordens stop ativas, cancele as obsoletas.
- Contanto que o preço de oferta atual permaneça dentro da metade da distância do canal da última entrada, coloque um novo stop de compra e um novo stop de venda a
ChannelSteps de distância do preenchimento mais recente.
- Quando
AddLots estiver ativado, aumente o volume do pedido pendente no lote base após cada preenchimento.
- Mantenha duas listas contínuas com todas as entradas longas e curtas para reproduzir a cesta coberta usada pela versão MT4.
- Calcule o lucro não realizado da cesta em cada vela acabada usando a melhor oferta para posições compradas e a melhor oferta para posições vendidas. As distâncias são normalizadas pela etapa de preço do instrumento, refletindo o cálculo do ponto original.
- Acione uma liquidação total quando um dos limites for excedido:
ProfitSteps – lucro produzido apenas pelo símbolo atual.
CommonProfitSteps – lucro em toda a cesta.
- A liquidação envia ordens de mercado para fechar cada exposição longa e curta separadamente. As ordens stop pendentes são canceladas quando o cesto fica estável.
Nota: O especialista original anexou níveis de stop loss ao registrar ordens pendentes. StockSharp não suporta níveis de proteção por ordem através do API de alto nível, portanto a porta fecha negociações exclusivamente através da lógica baseada no lucro descrita acima.
Parâmetros
| Parâmetro |
Descrição |
Padrão |
ChannelSteps |
Distância (em passos de preço mínimo) entre as ordens stop simétricas. |
1000 |
ProfitSteps |
Limite de lucro (em etapas) necessário para fechar a cesta local. |
1500 |
CommonProfitSteps |
Limite de lucro global (em etapas) que força uma liquidação total. |
1000 |
AddLots |
Quando ativado, aumenta o volume do próximo pedido pendente no lote base após cada preenchimento. |
true |
BaseVolume |
Volume usado para o primeiro par de ordens stop. |
0.1m |
CandleType |
Prazo usado para assinaturas de velas e gerenciamento comercial. |
1 minute |
Notas de implementação
- Usa o StockSharp API de alto nível com
SubscribeCandles() e Bind() para processar apenas velas concluídas.
- Rastreia entradas individuais dentro de
OnOwnTradeReceived para que o cálculo do lucro possa imitar a lógica de hedge da versão MQL.
- Os limites de lucro operam em distâncias puras de preços, sem multiplicar pelo volume executado, correspondendo à forma como o especialista MT4 somou os pips.
- Todas as ordens de parada são criadas por meio de
BuyStop e SellStop, enquanto as saídas são executadas com ordens de mercado para manter a lógica portátil entre provedores de dados.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Stairs grid strategy: places trades at regular ATR-based intervals,
/// adding to position on trending moves, closing on profit target.
/// </summary>
public class StairsStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _atrLength;
private readonly StrategyParam<decimal> _gridMultiplier;
private readonly StrategyParam<int> _maxLayers;
private readonly StrategyParam<decimal> _profitMultiplier;
private readonly StrategyParam<int> _emaLength;
private decimal _entryPrice;
private decimal _lastGridPrice;
private int _gridCount;
private decimal _prevEma;
public StairsStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_atrLength = Param(nameof(AtrLength), 14)
.SetDisplay("ATR Length", "ATR period for grid step.", "Indicators");
_gridMultiplier = Param(nameof(GridMultiplier), 1.5m)
.SetDisplay("Grid Multiplier", "ATR multiplier for grid step.", "Grid");
_maxLayers = Param(nameof(MaxLayers), 5)
.SetDisplay("Max Layers", "Maximum grid layers.", "Grid");
_profitMultiplier = Param(nameof(ProfitMultiplier), 2.0m)
.SetDisplay("Profit Multiplier", "ATR multiplier for profit target.", "Grid");
_emaLength = Param(nameof(EmaLength), 20)
.SetDisplay("EMA Length", "EMA for trend direction.", "Indicators");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
public decimal GridMultiplier
{
get => _gridMultiplier.Value;
set => _gridMultiplier.Value = value;
}
public int MaxLayers
{
get => _maxLayers.Value;
set => _maxLayers.Value = value;
}
public decimal ProfitMultiplier
{
get => _profitMultiplier.Value;
set => _profitMultiplier.Value = value;
}
public int EmaLength
{
get => _emaLength.Value;
set => _emaLength.Value = value;
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_entryPrice = 0;
_lastGridPrice = 0;
_gridCount = 0;
_prevEma = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var atr = new AverageTrueRange { Length = AtrLength };
var ema = new ExponentialMovingAverage { Length = EmaLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(atr, ema, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ema);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal atrVal, decimal emaVal)
{
if (candle.State != CandleStates.Finished)
return;
if (atrVal <= 0 || _prevEma == 0)
{
_prevEma = emaVal;
return;
}
var close = candle.ClosePrice;
var gridStep = atrVal * GridMultiplier;
var profitTarget = atrVal * ProfitMultiplier;
// Check profit target
if (Position > 0 && _entryPrice > 0)
{
if (close - _entryPrice >= profitTarget || close < emaVal)
{
SellMarket();
_gridCount = 0;
_entryPrice = 0;
_lastGridPrice = 0;
}
}
else if (Position < 0 && _entryPrice > 0)
{
if (_entryPrice - close >= profitTarget || close > emaVal)
{
BuyMarket();
_gridCount = 0;
_entryPrice = 0;
_lastGridPrice = 0;
}
}
// Grid: add to winning direction
if (Position > 0 && _lastGridPrice > 0 && _gridCount < MaxLayers)
{
if (close - _lastGridPrice >= gridStep)
{
BuyMarket();
_lastGridPrice = close;
_gridCount++;
}
}
else if (Position < 0 && _lastGridPrice > 0 && _gridCount < MaxLayers)
{
if (_lastGridPrice - close >= gridStep)
{
SellMarket();
_lastGridPrice = close;
_gridCount++;
}
}
// Initial entry based on trend
if (Position == 0)
{
var emaRising = emaVal > _prevEma;
var emaFalling = emaVal < _prevEma;
if (emaRising && close > emaVal)
{
_entryPrice = close;
_lastGridPrice = close;
_gridCount = 0;
BuyMarket();
}
else if (emaFalling && close < emaVal)
{
_entryPrice = close;
_lastGridPrice = close;
_gridCount = 0;
SellMarket();
}
}
_prevEma = emaVal;
}
}
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.Strategies import Strategy
from StockSharp.Algo.Indicators import AverageTrueRange, ExponentialMovingAverage
class stairs_strategy(Strategy):
def __init__(self):
super(stairs_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._atr_length = self.Param("AtrLength", 14) \
.SetDisplay("ATR Length", "ATR period for grid step", "Indicators")
self._grid_multiplier = self.Param("GridMultiplier", 1.5) \
.SetDisplay("Grid Multiplier", "ATR multiplier for grid step", "Grid")
self._max_layers = self.Param("MaxLayers", 5) \
.SetDisplay("Max Layers", "Maximum grid layers", "Grid")
self._profit_multiplier = self.Param("ProfitMultiplier", 2.0) \
.SetDisplay("Profit Multiplier", "ATR multiplier for profit target", "Grid")
self._ema_length = self.Param("EmaLength", 20) \
.SetDisplay("EMA Length", "EMA for trend direction", "Indicators")
self._entry_price = 0.0
self._last_grid_price = 0.0
self._grid_count = 0
self._prev_ema = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@property
def AtrLength(self):
return self._atr_length.Value
@property
def GridMultiplier(self):
return self._grid_multiplier.Value
@property
def MaxLayers(self):
return self._max_layers.Value
@property
def ProfitMultiplier(self):
return self._profit_multiplier.Value
@property
def EmaLength(self):
return self._ema_length.Value
def OnStarted2(self, time):
super(stairs_strategy, self).OnStarted2(time)
self._entry_price = 0.0
self._last_grid_price = 0.0
self._grid_count = 0
self._prev_ema = 0.0
self._atr = AverageTrueRange()
self._atr.Length = self.AtrLength
self._ema = ExponentialMovingAverage()
self._ema.Length = self.EmaLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._atr, self._ema, self.ProcessCandle).Start()
def ProcessCandle(self, candle, atr_val, ema_val):
if candle.State != CandleStates.Finished:
return
atr_v = float(atr_val)
ema_v = float(ema_val)
if atr_v <= 0 or self._prev_ema == 0:
self._prev_ema = ema_v
return
close = float(candle.ClosePrice)
grid_step = atr_v * float(self.GridMultiplier)
profit_target = atr_v * float(self.ProfitMultiplier)
# Check profit target
if self.Position > 0 and self._entry_price > 0:
if close - self._entry_price >= profit_target or close < ema_v:
self.SellMarket()
self._grid_count = 0
self._entry_price = 0.0
self._last_grid_price = 0.0
elif self.Position < 0 and self._entry_price > 0:
if self._entry_price - close >= profit_target or close > ema_v:
self.BuyMarket()
self._grid_count = 0
self._entry_price = 0.0
self._last_grid_price = 0.0
# Grid: add to winning direction
max_layers = self.MaxLayers
if self.Position > 0 and self._last_grid_price > 0 and self._grid_count < max_layers:
if close - self._last_grid_price >= grid_step:
self.BuyMarket()
self._last_grid_price = close
self._grid_count += 1
elif self.Position < 0 and self._last_grid_price > 0 and self._grid_count < max_layers:
if self._last_grid_price - close >= grid_step:
self.SellMarket()
self._last_grid_price = close
self._grid_count += 1
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_ema = ema_v
return
# Initial entry based on trend
if self.Position == 0:
ema_rising = ema_v > self._prev_ema
ema_falling = ema_v < self._prev_ema
if ema_rising and close > ema_v:
self._entry_price = close
self._last_grid_price = close
self._grid_count = 0
self.BuyMarket()
elif ema_falling and close < ema_v:
self._entry_price = close
self._last_grid_price = close
self._grid_count = 0
self.SellMarket()
self._prev_ema = ema_v
def OnReseted(self):
super(stairs_strategy, self).OnReseted()
self._entry_price = 0.0
self._last_grid_price = 0.0
self._grid_count = 0
self._prev_ema = 0.0
def CreateClone(self):
return stairs_strategy()