Open Close é um port do assessor especialista MetaTrader 5 Open Close.mq5 (ticket 23090). A estratégia observa a relação entre as aberturas e fechamentos das duas velas finalizadas mais recentes. Negocia uma única posição por vez: quando a vela mais nova se reverte em relação à anterior, a estratégia entra; e quando ambas as velas apontam na mesma direção, ela sai. A versão em C# reproduz o dimensionamento adaptativo de lote original que reduz a exposição após uma série de operações perdedoras.
Lógica da estratégia
Filtro de padrão de velas
A estratégia trabalha exclusivamente com velas completadas fornecidas pelo parâmetro configurável CandleType.
Mantém uma janela deslizante das duas últimas velas finalizadas (denominadas previous e older).
O padrão compara tanto as aberturas quanto os fechamentos dessas velas:
Se nenhuma posição estiver aberta e o padrão de reversão altista aparecer, a estratégia envia uma ordem de compra de mercado.
Se nenhuma posição estiver aberta e o padrão de reversão baixista aparecer, envia uma ordem de venda de mercado.
Apenas uma posição é permitida. Sinais opostos são ignorados até que a negociação ativa seja fechada.
Regras de saída
Quando uma posição longa é mantida, a estratégia sai se as duas velas rastreadas se moverem para baixo (previous.Open < older.Open e previous.Close < older.Close).
Quando uma posição curta é mantida, o gatilho de saída é simétrico (previous.Open > older.Open e previous.Close > older.Close).
Não há ordens de stop-loss ou take-profit no assessor original, portanto o port depende exclusivamente da relação de velas para fechar negociações.
Dimensionamento de posição e tratamento de série de perdas
O volume da ordem é determinado principalmente por MaximumRiskPercent – a fração desejada do valor do portfólio investida por negociação. O tamanho bruto é Portfolio.CurrentValue × MaximumRiskPercent ÷ referencePrice usando o último fechamento como proxy de preço.
Se a valoração do portfólio ou o preço não estiver disponível, o parâmetro FallbackVolume atua como padrão seguro.
Após cada negociação completamente fechada, o PnL realizado é armazenado. A série de perdas consecutivas é contada nos últimos HistoryDays dias.
Quando a série é maior que uma negociação, o tamanho da próxima ordem é reduzido por volume × losses ÷ DecreaseFactor, imitando a lógica do MT5.
O volume final respeita o step de volume do instrumento, bem como os limites mínimo e máximo de volume.
Notas adicionais de implementação
A estratégia reage apenas a CandleStates.Finished, garantindo que o padrão use dados de mercado completos.
As verificações de entrada e saída ocorrem no fechamento da vela mais nova. No MetaTrader, a ordem é enviada na abertura da próxima barra; a diferença é insignificante para períodos mais altos, mas deve ser considerada para intervalos muito curtos.
As métricas de portfólio no StockSharp aproximam as informações de conta do MetaTrader. Ajuste MaximumRiskPercent ou FallbackVolume se o broker usar multiplicadores de contrato diferentes.
Parâmetros
Parâmetro
Tipo
Padrão
Descrição
MaximumRiskPercent
decimal
0.02
Fração do valor do portfólio usada para dimensionar uma nova posição (0.02 = 2%).
DecreaseFactor
decimal
3
Divisor aplicado ao tamanho do lote após negociações perdedoras consecutivas. Valores maiores suavizam a redução.
HistoryDays
int
60
Número de dias de calendário verificados ao contar a série de perdas mais recente.
FallbackVolume
decimal
0.1
Volume de ordem usado quando o cálculo baseado em risco não pode ser realizado.
CandleType
DataType
TimeFrame(15m)
Série de velas que fornece os valores de abertura/fechamento para geração de sinais.
Diferenças em relação à versão MetaTrader
As verificações de margem de conta dependem do Portfolio.CurrentValue do StockSharp; o MetaTrader usava AccountFreeMargin. O comportamento corresponde à regra de risco original apenas quando ambas as plataformas reportam valorações similares.
O histórico de negociações é coletado das próprias execuções da estratégia em vez do histórico de toda a terminal. Certifique-se de que a estratégia funcione por tempo suficiente para acumular estatísticas de série.
O port mantém o modelo de posição única (sem pirâmide) e reflete a falta original de ordens protetoras. Adicione stops externamente se necessário para controle de risco.
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;
/// <summary>
/// Open Close strategy using EMA crossover.
/// Buys when fast EMA crosses above slow EMA, sells on reverse.
/// </summary>
public class OpenClose23090Strategy : 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 OpenClose23090Strategy()
{
_fastPeriod = Param(nameof(FastPeriod), 15).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
_slowPeriod = Param(nameof(SlowPeriod), 60).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 open_close23090_strategy(Strategy):
def __init__(self):
super(open_close23090_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 15) \
.SetDisplay("Fast Period", "Fast MA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 60) \
.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(open_close23090_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(open_close23090_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 open_close23090_strategy()