Conversão do consultor especialista MetaTrader 4 RUBBERBANDS_2.mq4.
Executa uma grade simétrica em torno do preço atual usando as melhores cotações de compra/venda em vez de velas.
Mantém livros contábeis separados para exposição longa e curta para que o comportamento corresponda à implementação do MT4 coberto.
Implementa controles de lucros e perdas em nível de sessão e um modo de suspensão/parada manual idêntico às entradas originais.
Lógica de negociação
A estratégia assina SubscribeLevel1() e reage a cada alteração do melhor lance e do melhor pedido.
Dois extremos flutuantes (_upperExtreme / _lowerExtreme) capturam o preço de venda mais alto e mais baixo alcançado desde a última redefinição. Eles são inicializados a partir de parâmetros quando UseInitialValues for verdadeiro, caso contrário, o primeiro preço de venda recebido será usado.
Quando não há negociações abertas e o horário do servidor atinge o primeiro tick de um minuto (o segundo é igual a zero), a estratégia solicita uma compra e uma venda no mercado. Isso reflete o comportamento do MT4, onde os sinalizadores de compra/venda são definidos a cada minuto enquanto o livro está vazio.
Cada vez que o preço de venda avança GridStepPoints pontos acima da máxima armazenada, uma nova ordem de venda é emitida. Cada queda na mesma distância abaixo do mínimo armazenado aciona uma nova ordem de compra. Os extremos são atualizados para a oferta atual após cada gatilho, para que a escada “se alargue” com o preço.
O número total de negociações abertas simultaneamente (soma das pernas longas e curtas) é limitado por MaxTrades.
O lucro flutuante é calculado a partir do lance/pedido atual: os lucros longos usam o lance menos o preço médio do longo, os lucros curtos usam o preço médio do curto menos o pedido. O auxiliar PriceToMoney converte as diferenças de preço na moeda da conta usando PriceStep/StepPrice quando disponível.
Quando o lucro flutuante atinge SessionTakeProfitPerLot * OrderVolume e UseSessionTakeProfit está ativado, toda a exposição é nivelada. Da mesma forma, a perda flutuante abaixo de -SessionStopLossPerLot * OrderVolume aciona uma saída completa quando UseSessionStopLoss está ativado.
Os sinalizadores manuais reproduzem as opções originais do EA: CloseNow impõe um início plano, QuiesceMode mantém a estratégia inativa enquanto está plana e StopNow interrompe novas entradas sem interferir nas posições existentes.
Parâmetros
Parâmetro
Descrição
OrderVolume
Volume para cada ordem de mercado (MT4 Lots).
MaxTrades
Contagem máxima de negociações abertas simultaneamente (MT4 maxcount).
GridStepPoints
Distância em faixas de preço entre as camadas da grade (MT4 pipstep).
QuiesceMode
Se ativada, a estratégia espera uma vez, idêntico a quiescenow.
TriggerImmediateEntries
Abre uma compra e venda inicial assim que a estratégia estiver pronta (donow).
StopNow
Pausa entradas automatizadas enquanto mantém as posições atuais ativas (stopnow).
CloseNow
Solicita um nivelamento imediato no início (closenow).
UseSessionTakeProfit & SessionTakeProfitPerLot
Meta de lucro flutuante no nível da sessão por lote.
UseSessionStopLoss & SessionStopLossPerLot
Limite de perda flutuante no nível da sessão por lote.
UseInitialValues, InitialMax, InitialMin
Suporte de reinicialização opcional que reutiliza extremos anteriores (useinvalues, inmax, inmin).
Notas de implementação
Todo o estado interno é recuado por tabulação e armazenado em campos, em vez de coleções, para seguir as diretrizes do projeto.
As ordens de mercado são aceleradas pelo rastreamento de _activeBuyOrder e _activeSellOrder para que nenhuma solicitação duplicada seja enviada enquanto a anterior estiver pendente.
A contabilidade de hedge é realizada em OnOwnTradeReceived onde os preços/volumes médios longos e curtos são atualizados de forma independente e convertidos em lucro flutuante para lógica de parada.
TryCloseAll() espelha a rotina MT4 close1by1(), enviando ordens de mercado opostas até que ambos os livros estejam estáveis e, em seguida, redefinindo os extremos para a última venda.
A estratégia depende exclusivamente de chamadas API de alto nível (SubscribeLevel1(), BuyMarket, SellMarket) e evita o acesso direto ao indicador conforme exigido pelas regras do repositório.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Rubber Bands Grid: Mean reversion grid using SMA+ATR bands.
/// </summary>
public class RubberBandsGridStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _smaLength;
private readonly StrategyParam<int> _atrLength;
private decimal _entryPrice;
private int _gridCount;
public RubberBandsGridStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_smaLength = Param(nameof(SmaLength), 20)
.SetDisplay("SMA Length", "SMA period.", "Indicators");
_atrLength = Param(nameof(AtrLength), 14)
.SetDisplay("ATR Length", "ATR period.", "Indicators");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int SmaLength
{
get => _smaLength.Value;
set => _smaLength.Value = value;
}
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_entryPrice = 0;
_gridCount = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_entryPrice = 0;
_gridCount = 0;
var sma = new SimpleMovingAverage { Length = SmaLength };
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(sma, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, sma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal smaVal, decimal atrVal)
{
if (candle.State != CandleStates.Finished)
return;
if (atrVal <= 0)
return;
var close = candle.ClosePrice;
var lower = smaVal - atrVal * 2m;
var upper = smaVal + atrVal * 2m;
if (Position > 0)
{
if (close >= smaVal)
{
SellMarket();
_entryPrice = 0;
_gridCount = 0;
}
else if (close <= _entryPrice - atrVal * 4m)
{
SellMarket();
_entryPrice = 0;
_gridCount = 0;
}
else if (_gridCount < 3 && close <= _entryPrice - atrVal)
{
_entryPrice = (_entryPrice + close) / 2m;
_gridCount++;
BuyMarket();
}
}
else if (Position < 0)
{
if (close <= smaVal)
{
BuyMarket();
_entryPrice = 0;
_gridCount = 0;
}
else if (close >= _entryPrice + atrVal * 4m)
{
BuyMarket();
_entryPrice = 0;
_gridCount = 0;
}
else if (_gridCount < 3 && close >= _entryPrice + atrVal)
{
_entryPrice = (_entryPrice + close) / 2m;
_gridCount++;
SellMarket();
}
}
if (Position == 0)
{
if (close <= lower)
{
_entryPrice = close;
_gridCount = 0;
BuyMarket();
}
else if (close >= upper)
{
_entryPrice = close;
_gridCount = 0;
SellMarket();
}
}
}
}
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 SimpleMovingAverage, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class rubber_bands_grid_strategy(Strategy):
def __init__(self):
super(rubber_bands_grid_strategy, self).__init__()
self._sma_length = self.Param("SmaLength", 20).SetDisplay("SMA Length", "SMA period", "Indicators")
self._atr_length = self.Param("AtrLength", 14).SetDisplay("ATR Length", "ATR period", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))).SetDisplay("Candle Type", "Timeframe", "General")
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, value): self._candle_type.Value = value
def OnReseted(self):
super(rubber_bands_grid_strategy, self).OnReseted()
self._entry_price = 0
self._grid_count = 0
def OnStarted2(self, time):
super(rubber_bands_grid_strategy, self).OnStarted2(time)
self._entry_price = 0
self._grid_count = 0
sma = SimpleMovingAverage()
sma.Length = self._sma_length.Value
atr = AverageTrueRange()
atr.Length = self._atr_length.Value
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(sma, atr, self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, sub)
self.DrawIndicator(area, sma)
self.DrawOwnTrades(area)
def OnProcess(self, candle, sma_val, atr_val):
if candle.State != CandleStates.Finished:
return
if atr_val <= 0:
return
close = float(candle.ClosePrice)
lower = sma_val - atr_val * 2.0
upper = sma_val + atr_val * 2.0
if self.Position > 0:
if close >= sma_val:
self.SellMarket()
self._entry_price = 0
self._grid_count = 0
elif close <= self._entry_price - atr_val * 4.0:
self.SellMarket()
self._entry_price = 0
self._grid_count = 0
elif self._grid_count < 3 and close <= self._entry_price - atr_val:
self._entry_price = (self._entry_price + close) / 2.0
self._grid_count += 1
self.BuyMarket()
elif self.Position < 0:
if close <= sma_val:
self.BuyMarket()
self._entry_price = 0
self._grid_count = 0
elif close >= self._entry_price + atr_val * 4.0:
self.BuyMarket()
self._entry_price = 0
self._grid_count = 0
elif self._grid_count < 3 and close >= self._entry_price + atr_val:
self._entry_price = (self._entry_price + close) / 2.0
self._grid_count += 1
self.SellMarket()
if self.Position == 0:
if close <= lower:
self._entry_price = close
self._grid_count = 0
self.BuyMarket()
elif close >= upper:
self._entry_price = close
self._grid_count = 0
self.SellMarket()
def CreateClone(self):
return rubber_bands_grid_strategy()