VirtualProfitLossTrailStrategy reproduce dentro de StockSharp el comportamiento del asesor experto de MetaTrader "Virtual Profit Loss Trail". La estrategia nunca abre posiciones nuevas por sí misma. En su lugar, supervisa continuamente la posición actual del valor seleccionado y aplica lógica de protección:
Una distancia configurable de take-profit expresada en pips.
Una distancia configurable de stop-loss expresada en pips.
Un trailing stop virtual que se activa después de alcanzar una ganancia mínima y se desliza con el mercado solo cuando el precio avanza por el paso trailing especificado.
Como los niveles de protección son virtuales, no se envían órdenes stop o limit reales a la bolsa. La estrategia monitoriza actualizaciones de mejor bid/ask y cierra la posición abierta con una orden de mercado cuando se toca cualquiera de los niveles virtuales.
Parámetros
Parámetro
Descripción
Take-profit (pips)
Distancia entre el precio de entrada y el objetivo de ganancia. Establecer en 0 para desactivar la salida take-profit.
Stop-loss (pips)
Distancia entre el precio de entrada y el stop de protección. Establecer en 0 para desactivar la salida stop-loss.
Trailing stop (pips)
Distancia usada para calcular el trailing stop. Cuando se establece en 0, la lógica trailing se desactiva por completo.
Trailing step (pips)
Ganancia adicional que debe obtenerse antes de desplazar más el trailing stop. Usar 0 para mover el trail cada vez que se marca un nuevo máximo/mínimo.
Trailing activation (pips)
Ganancia mínima que debe bloquearse antes de que el trailing stop se active. Cuando se establece en 0, trailing empieza inmediatamente después de entrar en la posición.
Todas las distancias se miden en unidades de pip. La estrategia deriva automáticamente el tamaño de pip desde el paso de precio del valor: para símbolos con tres o cinco decimales, un pip se define como diez pasos de precio; de lo contrario, como un paso.
Lógica
Suscripción a datos de mercado - la estrategia se suscribe a datos Level1 para recibir actualizaciones del mejor bid y mejor ask. Solo se procesan actualizaciones finalizadas, asegurando que el algoritmo funcione tanto en tiempo real como durante reproducciones históricas.
Gestión de posición larga - cuando la posición neta es larga, la estrategia calcula los niveles virtuales de stop-loss, take-profit y trailing stop a partir del precio medio de entrada. Si el mejor bid toca el stop-loss o take-profit, la posición se cierra inmediatamente. Cuando se alcanza la ganancia de activación, el trailing stop sigue el precio hacia arriba. El stop solo avanza cuando se cumple el requisito de paso trailing.
Gestión de posición corta - la misma lógica se aplica simétricamente usando el mejor ask para salidas de posiciones cortas.
Comportamiento de reinicio - cuando la posición se cierra por completo, las referencias internas de trailing se reinician para evitar señales accidentales de reentrada.
Consejos de uso
Adjunte la estrategia a un conector y valor que ya tenga una posición abierta o que vaya a recibir órdenes de otras estrategias o trading manual. El gestor controlará el tamaño agregado de la posición.
Asegúrese de que haya datos Level1 disponibles; sin valores actuales bid/ask, los niveles virtuales no pueden evaluarse.
La estrategia puede combinarse con cualquier estrategia generadora de entradas ejecutando ambas bajo la misma cartera y valor. Solo una instancia debe gestionar la lógica de protección para evitar conflictos.
Diferencias con el experto MQL
La versión StockSharp trabaja con posiciones agregadas en lugar de tickets de órdenes individuales. Calcula automáticamente el precio medio de entrada proporcionado por la plataforma.
El dibujo de líneas visuales y alertas sonoras del experto original se sustituyen por logging dentro de StockSharp. Las acciones de protección son visibles en el diario de la estrategia.
Se conserva la misma configuración basada en pips, incluido el umbral de activación trailing y el paso trailing incremental.
Archivos
CS/VirtualProfitLossTrailStrategy.cs - implementación C# de la estrategia.
README.md - esta documentación.
README_zh.md - traducción al chino simplificado.
README_ru.md - traducción al ruso.
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 VirtualProfitLossTrailStrategy : 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 VirtualProfitLossTrailStrategy()
{
_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 virtual_profit_loss_trail_strategy(Strategy):
def __init__(self):
super(virtual_profit_loss_trail_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(virtual_profit_loss_trail_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(virtual_profit_loss_trail_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 virtual_profit_loss_trail_strategy()