A Estratégia de Reequilíbrio de Grade é uma versão StockSharp de alto nível do consultor especialista "Grid" da Mission Automate. A estratégia alterna entre ciclos de grade longos e curtos e sempre mantém uma escada de ordens limitadas na direção ativa. Quando a posição agregada atinge um nível de lucro comum, o ciclo é fechado, todas as ordens pendentes são removidas e o próximo ciclo começa na direção oposta.
Como funciona
Início do ciclo – Quando não há posições ou ordens pendentes, a estratégia abre uma posição de mercado na direção definida por FirstTradeSide usando StartVolume lotes.
Colocação da grade – Após cada ordem preenchida na direção ativa, o algoritmo coloca uma nova ordem limite a uma distância de GridStepPoints (convertido em preço pelo instrumento PriceStep). O volume do próximo pedido é igual ao volume do último pedido preenchido multiplicado por LotMultiplier.
Take-profit baseado na média – Para cada pedido atendido, o preço médio ponderado de entrada é recalculado. O take-profit para toda a cesta é definido como o preço médio mais/menos TargetPoints (também convertido por meio de PriceStep). Os máximos e mínimos das velas são usados para modelar o comportamento do gatilho do corretor.
Conclusão do ciclo – Quando o nível de take-profit é atingido, a estratégia fecha toda a posição com uma ordem de mercado, cancela as ordens pendentes restantes, lembra a direção do ciclo finalizado e inverte a direção do próximo.
Parâmetros
FirstTradeSide – direção do primeiro ciclo (Buy ou Sell). Cada ciclo concluído muda automaticamente de direção.
StartVolume – tamanho do lote da ordem de mercado inicial em cada ciclo.
LotMultiplier – multiplicador aplicado ao volume de pedidos preenchidos mais recentemente ao preparar o próximo nível de grade. Valores maiores que um criam uma progressão semelhante a martingale.
GridStepPoints – distância entre os níveis da grade expressa em pontos. A estratégia multiplica por Security.PriceStep para obter a diferença absoluta de preço.
TargetPoints – distância do take-profit em relação ao preço médio ponderado de entrada, medido em pontos.
CandleType – série de velas usada para monitorar extremos de preços para desencadear saídas.
Gestão de riscos e comportamento
Nenhum stop-loss explícito é usado; a grade continua adicionando exposição enquanto o mercado se move contra a posição.
Apenas uma ordem pendente está ativa por vez. Quando o pedido é atendido, o próximo nível é agendado imediatamente.
O ciclo não pode começar até que a posição e a fila pendente estejam vazias e o instrumento tenha um PriceStep válido.
A conversão mantém todos os cálculos dentro da estratégia sem tocar em coleções globais ou buffers de indicadores, seguindo as regras do projeto.
As ordens pendentes são canceladas sempre que um ciclo termina, evitando limites órfãos de ciclos anteriores.
Notas
Todas as configurações baseadas em pontos são convertidas em preços com Security.PriceStep. Se o passo for zero, a estratégia espera até que o instrumento o forneça.
A implementação depende puramente do API de alto nível (SubscribeCandles, Bind, BuyMarket, SellMarket, BuyLimit, SellLimit) conforme necessário.
Uma versão Python não foi incluída intencionalmente nesta tarefa.
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Grid Rebalance strategy: RSI + EMA crossover-based.
/// Buys when close crosses above EMA with RSI confirmation.
/// Sells when close crosses below EMA with RSI confirmation.
/// </summary>
public class GridRebalanceStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<int> _emaPeriod;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int RsiPeriod
{
get => _rsiPeriod.Value;
set => _rsiPeriod.Value = value;
}
public int EmaPeriod
{
get => _emaPeriod.Value;
set => _emaPeriod.Value = value;
}
public GridRebalanceStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
_rsiPeriod = Param(nameof(RsiPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Period", "RSI period", "Indicators");
_emaPeriod = Param(nameof(EmaPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("EMA Period", "EMA period", "Indicators");
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var rsi = new RelativeStrengthIndex { Length = RsiPeriod };
var ema = new ExponentialMovingAverage { Length = EmaPeriod };
decimal? prevClose = null;
decimal? prevEma = null;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(rsi, ema, (candle, rsiVal, emaVal) =>
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var close = candle.ClosePrice;
if (prevClose.HasValue && prevEma.HasValue)
{
var crossUp = prevClose.Value <= prevEma.Value && close > emaVal;
var crossDown = prevClose.Value >= prevEma.Value && close < emaVal;
if (crossUp && rsiVal < 55m && Position <= 0)
BuyMarket();
else if (crossDown && rsiVal > 45m && Position >= 0)
SellMarket();
}
prevClose = close;
prevEma = emaVal;
})
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ema);
DrawOwnTrades(area);
}
}
}
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 RelativeStrengthIndex, ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class grid_rebalance_strategy(Strategy):
"""
Grid Rebalance: RSI + EMA crossover strategy.
Buys when close crosses above EMA with RSI < 55.
Sells when close crosses below EMA with RSI > 45.
"""
def __init__(self):
super(grid_rebalance_strategy, self).__init__()
self._rsi_period = self.Param("RsiPeriod", 14) \
.SetDisplay("RSI Period", "RSI period", "Indicators")
self._ema_period = self.Param("EmaPeriod", 20) \
.SetDisplay("EMA Period", "EMA period", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))) \
.SetDisplay("Candle Type", "Candle timeframe", "General")
self._prev_close = None
self._prev_ema = None
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(grid_rebalance_strategy, self).OnReseted()
self._prev_close = None
self._prev_ema = None
def OnStarted2(self, time):
super(grid_rebalance_strategy, self).OnStarted2(time)
rsi = RelativeStrengthIndex()
rsi.Length = self._rsi_period.Value
ema = ExponentialMovingAverage()
ema.Length = self._ema_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(rsi, ema, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ema)
self.DrawOwnTrades(area)
def _process_candle(self, candle, rsi_val, ema_val):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
rsi = float(rsi_val)
ema = float(ema_val)
if self._prev_close is not None and self._prev_ema is not None:
cross_up = self._prev_close <= self._prev_ema and close > ema
cross_down = self._prev_close >= self._prev_ema and close < ema
if cross_up and rsi < 55.0 and self.Position <= 0:
self.BuyMarket()
elif cross_down and rsi > 45.0 and self.Position >= 0:
self.SellMarket()
self._prev_close = close
self._prev_ema = ema
def CreateClone(self):
return grid_rebalance_strategy()