Convertida do consultor especialista MetaTrader 4 "CycleMarketOrder_V181". A estratégia organiza um número fixo de slots dentro de uma escada de preços e abre ordens de mercado quando o bid/ask ao vivo negocia através de um slot individual. Cada slot carrega seu próprio volume, limiar de ponto de equilíbrio e valor de trailing stop, para que o grid possa gradualmente escalonar em uma posição enquanto protege lucros que já atingiram a distância necessária.
Lógica de trading
O tamanho do pip é derivado do passo de preço do instrumento e da precisão decimal (símbolos de 5/3 dígitos mapeiam para 10 pontos por pip). Os parâmetros MaxPrice, SpanPips e MaxCount são então usados para pré-calcular o intervalo de preços gerenciado por cada slot.
Os dados de mercado de nível 1 são consumidos para imitar o comportamento baseado em ticks do Expert Advisor original. Cada atualização atualiza os preços de melhor bid/ask em cache.
Se UseWeekendMode estiver habilitado, a estratégia recusa negociar fora da janela de fim de semana configurada (sábado a partir de WeekendHour, o domingo inteiro e segunda-feira antes de WeekstartHour).
Para ciclos longos (EntryDirection = 1), o algoritmo escaneia slots do identificador mais baixo ao mais alto. Sempre que o preço ask atual cair entre startPrice e endPrice do slot, uma ordem de compra de mercado com volume OrderVolume é enviada. Ciclos curtos (EntryDirection = -1) espelham essa lógica e usam o preço bid.
Os estados dos slots rastreiam ordens de entrada/saída pendentes, volume preenchido e o preço médio de entrada. O registro usa MagicNumberBase + index para corresponder aos identificadores "mágicos" do MT4.
O gerenciamento do trailing é executado em cada atualização de nível 1 antes de avaliar novas entradas. Uma vez que o lucro em um slot longo excede BreakEvenPips + TrailingStopPips, o stop é empurrado para Bid - TrailingStopPips. Slots curtos usam Ask + TrailingStopPips e a condição de ponto de equilíbrio espelhada. Quando o preço de mercado cruza o stop armazenado, o slot é fechado com uma ordem de mercado.
Como apenas ordens de mercado são usadas, não há ordens pendentes para cancelar. Preenchimentos parciais ajustam o volume restante do slot para que a estratégia possa continuar fazendo trailing ou re-armar o slot quando ficar neutro.
Parâmetros
Parâmetro
Descrição
EntryDirection
Direção de trading: 1 compra a escada, -1 a vende, 0 desabilita novas entradas enquanto mantém o trailing ativo.
MaxPrice
Preço âncora superior usado para calcular os intervalos de slots.
MaxCount
Número total de slots ativos dentro do grid.
SpanPips
Distância em pips entre limites de slots consecutivos.
OrderVolume
Volume enviado quando um slot é acionado.
BreakEvenPips
Distância de lucro que deve ser excedida antes que o trailing stop seja armado.
TrailingStopPips
Distância de trailing aplicada após o ponto de equilíbrio ser atingido.
UseWeekendMode
Habilita a janela de bloqueio de trading de fim de semana.
WeekendHour
Hora do sábado (horário do terminal) quando o trading é interrompido.
WeekstartHour
Hora da segunda-feira quando o trading é retomado.
MagicNumberBase
Deslocamento de identificador usado em mensagens de log para corresponder aos números mágicos originais.
Notas de implementação
O gerenciamento de slots rastreia ordens de entrada e saída pendentes para que preenchimentos repetidos não registrem volume duplicado.
A estratégia reinicia seu trailing stop sempre que um novo preenchimento aumenta a exposição do slot, garantindo que o stop reflita o preço médio de entrada mais recente.
A proteção de fim de semana simplesmente pula tanto a lógica de trailing quanto de entrada; posições existentes permanecem intocadas enquanto o bloqueio está ativo.
Os dados de nível 1 são necessários porque a lógica compara preços brutos de bid/ask em vez de fechamentos de velas, replicando de perto o comportamento tick a tick da versão MT4.
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
public class CycleMarketOrderStrategy : Strategy
{
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private ExponentialMovingAverage _fast;
private ExponentialMovingAverage _slow;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _entryPrice;
private int _cooldown;
public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }
public CycleMarketOrderStrategy()
{
_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
}
protected override void OnReseted()
{
base.OnReseted();
_fast = null; _slow = null;
_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fast = new ExponentialMovingAverage { Length = FastPeriod };
_slow = new ExponentialMovingAverage { Length = SlowPeriod };
var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
subscription.Bind(_fast, _slow, ProcessCandle);
subscription.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
{
if (candle.State != CandleStates.Finished) return;
if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }
var close = candle.ClosePrice;
var step = Security?.PriceStep ?? 1m;
if (Position > 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
}
else if (Position < 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
}
if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }
_prevFast = fastValue; _prevSlow = slowValue;
}
}
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 ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class cycle_market_order_strategy(Strategy):
def __init__(self):
super(cycle_market_order_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 14) \
.SetDisplay("Fast Period", "Fast MA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 50) \
.SetDisplay("Slow Period", "Slow MA period", "Indicator")
self._stop_loss_points = self.Param("StopLossPoints", 200) \
.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 400) \
.SetDisplay("Take Profit", "Take-profit in price steps", "Risk")
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
@property
def fast_period(self):
return self._fast_period.Value
@property
def slow_period(self):
return self._slow_period.Value
@property
def stop_loss_points(self):
return self._stop_loss_points.Value
@property
def take_profit_points(self):
return self._take_profit_points.Value
def OnReseted(self):
super(cycle_market_order_strategy, self).OnReseted()
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(cycle_market_order_strategy, self).OnStarted2(time)
self._fast = ExponentialMovingAverage()
self._fast.Length = self.fast_period
self._slow = ExponentialMovingAverage()
self._slow.Length = self.slow_period
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(5)))
subscription.Bind(self._fast, self._slow, self._process_candle)
subscription.Start()
def _process_candle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_value)
slow_val = float(slow_value)
if not self._fast.IsFormed or not self._slow.IsFormed:
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = fast_val
self._prev_slow = slow_val
return
close = float(candle.ClosePrice)
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if self.Position > 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close <= self._entry_price - self.stop_loss_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close >= self._entry_price + self.take_profit_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
elif self.Position < 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close >= self._entry_price + self.stop_loss_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close <= self._entry_price - self.take_profit_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 100
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return cycle_market_order_strategy()