Virtual Profit Close — это перенос эксперта MetaTrader 4 Virtual_Profit_Close.mq4 на платформу StockSharp. Стратегия следит за
текущей позицией по выбранному инструменту и закрывает её, как только достигается заданная виртуальная прибыль. В отличие от обычного
тейк-профита, уровень выхода хранится внутри стратегии и не выставляется в виде заявки. Дополнительно доступен трейлинг-стоп, который
подтягивает точку выхода по мере роста прибыли. Для тестов предусмотрен демонстрационный режим автоматического открытия сделок.
Особенности портирования
Подписка на тиковые сделки (SubscribeTrades().Bind(ProcessTrade).Start()) обеспечивает поведение, аналогичное обработчику OnTick.
Пересчёт «поинтов» MetaTrader выполнен через Security.PriceStep с поправкой на трёх- и пятизнаковые инструменты.
Для расчёта прибыли используются цены Bid (для лонгов) и Ask (для шортов), как и в оригинальном скрипте.
Трейлинг-стоп активируется после достижения заданного порога и удерживает стоп на фиксированном расстоянии от рынка, имитируя
последовательные вызовы OrderModify.
Демонстрационный режим заменяет функцию SendTest: стратегия открывает рыночные сделки выбранного направления и при необходимости
ставит защитный стоп через SetStopLoss.
Параметры
Параметр
Описание
ProfitPips
Виртуальный тейк-профит в пипсах MetaTrader. При достижении позиция закрывается.
UseTrailingStop
Включает или отключает трейлинг-стоп.
TrailingOffsetPips
Расстояние между ценой и трейлинг-стопом после его активации.
TrailingActivationPips
Минимальная прибыль в пипсах для запуска трейлинга.
EnableDemoMode
Автоматически открывает демонстрационные сделки при отсутствии позиции.
DemoOrderDirection
Направление демо-сделок (Buy или Sell).
DemoOrderVolume
Объём демонстрационной сделки.
DemoStopPips
Необязательный защитный стоп для демо-режима (в пипсах).
Логика работы
При старте вычисляются размер пипса и все необходимые расстояния (прибыль, трейлинг, демо-стоп).
Каждый тик обрабатывается в методе ProcessTrade:
Лонг закрывается, когда цена Bid приносит требуемую прибыль.
Шорт закрывается, когда цена Ask проходит заданное расстояние.
Если трейлинг включён и условие активации выполнено, стоп подтягивается вслед за ценой. При возврате цены за уровень стопа позиция
закрывается рыночной заявкой.
В демо-режиме стратегия автоматически открывает новую сделку всякий раз, когда позиция становится нулевой, что повторяет поведение
оригинального эксперта в тестере.
Требования
Для корректной работы нужна тиковая лента сделок.
Стратегия рассчитана на один инструмент и не управляет несколькими символами одновременно.
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()