Virtual Profit Close replica o comportamento do consultor especialista MetaTrader 4 Virtual_Profit_Close.mq4. A estratégia observa o
posição atual do título configurado e sai assim que uma meta de lucro virtual é atingida. Ao contrário de uma ordem de lucro regular,
o nível de saída é avaliado internamente para que nenhuma ordem de lucro seja deixada na carteira de pedidos. Um trailing stop configurável pode mover a saída
preço mais próximo do mercado à medida que a negociação se transforma em lucro. Ao executar no modo de teste, a estratégia pode abrir automaticamente posições de amostra
para demonstrar sua lógica.
Notas de conversão
Os eventos de tick são consumidos por meio de SubscribeTrades().Bind(ProcessTrade).Start() para imitar a rotina OnTick original.
MetaTrader "pontos" são convertidos em pips inspecionando Security.PriceStep e ajustando para símbolos de 3/5 dígitos.
O lucro virtual e os cálculos finais usam o lance atual para posições longas e o pedido para posições curtas, correspondendo ao MQL
implementação que dependia de preços Bid e Ask.
A lógica de trailing stop é ativada após o limite de lucro configurado e mantém o stop a uma distância fixa do mercado
preço, semelhante a chamar repetidamente OrderModify em MQL.
Um modo de demonstração substitui o auxiliar original do testador de estratégia (SendTest) abrindo ordens de mercado de acordo com o selecionado
direção e volume. Paradas de proteção opcionais são colocadas usando SetStopLoss.
Parâmetros
Parâmetro
Descrição
ProfitPips
Nível de lucro virtual expresso em MetaTrader pips. A estratégia fecha a posição quando o lucro ultrapassa esta distância.
UseTrailingStop
Ativa o comportamento de rastreamento quando definido como true.
TrailingOffsetPips
Distância mantida entre o preço atual e o trailing stop quando estiver ativo.
TrailingActivationPips
Lucro mínimo em pips exigido antes do trailing stop ser ativado.
EnableDemoMode
Abre automaticamente ordens de demonstração cada vez que a posição fica plana. Útil para backtests.
DemoOrderDirection
Direção dos pedidos de demonstração (Buy ou Sell).
DemoOrderVolume
Volume enviado para pedidos de demonstração.
DemoStopPips
Parada de proteção opcional para pedidos de demonstração, expressa em pips.
Comportamento
Quando a estratégia é iniciada, ela calcula o tamanho do pip e as distâncias para lucro, trailing e demo stops.
Cada tick recebido por meio de ProcessTrade avalia a posição atual:
As posições longas são fechadas quando o preço de compra entrega o lucro virtual configurado.
As posições curtas são fechadas quando o preço de venda percorre a mesma distância na direção oposta.
Se o trailing estiver habilitado e o limite de ativação for atingido, o trailing stop se move junto com o movimento favorável do preço. Uma vez
o mercado cruza o nível final, a estratégia envia uma ordem de mercado para sair.
O modo de demonstração pode abrir automaticamente uma nova posição sempre que a estratégia se torna plana, recriando o recurso exclusivo para testador do
especialista original.
Requisitos
A estratégia precisa de dados de mercado ao nível dos ticks para responder com precisão às mudanças de preços.
Apenas um símbolo deve ser atribuído à instância da estratégia. Vários símbolos simultâneos não são suportados, correspondendo ao original
Implementação MQL que monitorou o símbolo do gráfico atual.
namespace StockSharp.Samples.Strategies;
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;
/// <summary>
/// Virtual Profit Close strategy: EMA crossover with profit target management.
/// Enters on EMA crossover, closes when profit target is hit.
/// </summary>
public class VirtualProfitCloseStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private decimal _prevFast;
private decimal _prevSlow;
private bool _hasPrev;
private decimal _entryPrice;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
public VirtualProfitCloseStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
_fastPeriod = Param(nameof(FastPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Fast Period", "Fast EMA period", "Indicators");
_slowPeriod = Param(nameof(SlowPeriod), 50)
.SetGreaterThanZero()
.SetDisplay("Slow Period", "Slow EMA period", "Indicators");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevFast = 0m;
_prevSlow = 0m;
_hasPrev = false;
_entryPrice = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_hasPrev = false;
_entryPrice = 0;
var fast = new ExponentialMovingAverage { Length = FastPeriod };
var slow = new ExponentialMovingAverage { Length = SlowPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(fast, slow, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal fast, decimal slow)
{
if (candle.State != CandleStates.Finished) return;
var close = candle.ClosePrice;
if (_hasPrev)
{
if (_prevFast <= _prevSlow && fast > slow && Position <= 0)
{
BuyMarket();
_entryPrice = close;
}
else if (_prevFast >= _prevSlow && fast < slow && Position >= 0)
{
SellMarket();
_entryPrice = close;
}
}
_prevFast = fast;
_prevSlow = slow;
_hasPrev = true;
}
}
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 virtual_profit_close_strategy(Strategy):
def __init__(self):
super(virtual_profit_close_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 20) \
.SetDisplay("Fast Period", "Fast EMA period", "Indicators")
self._slow_period = self.Param("SlowPeriod", 50) \
.SetDisplay("Slow Period", "Slow EMA period", "Indicators")
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._has_prev = False
self._entry_price = 0.0
@property
def fast_period(self):
return self._fast_period.Value
@property
def slow_period(self):
return self._slow_period.Value
def OnReseted(self):
super(virtual_profit_close_strategy, self).OnReseted()
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._has_prev = False
self._entry_price = 0.0
def OnStarted2(self, time):
super(virtual_profit_close_strategy, self).OnStarted2(time)
self._fast = ExponentialMovingAverage()
self._fast.Length = self.fast_period
self._slow = ExponentialMovingAverage()
self._slow.Length = self.slow_period
self._has_prev = False
self._entry_price = 0.0
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(15)))
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
if not self._fast.IsFormed or not self._slow.IsFormed:
return
close = float(candle.ClosePrice)
fast_val = float(fast_value)
slow_val = float(slow_value)
if self._has_prev:
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
self.BuyMarket()
self._entry_price = close
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
self.SellMarket()
self._entry_price = close
self._prev_fast = fast_val
self._prev_slow = slow_val
self._has_prev = True
def CreateClone(self):
return virtual_profit_close_strategy()