CloseProfit V2 — это порт оригинальной утилиты MetaTrader, которая закрывает все позиции при достижении заданного уровня плавающей прибыли или убытка. В версии для StockSharp стратегия выполняет роль защитного модуля: на каждой завершённой свече она пересчитывает плавающий результат и, при выходе за установленные границы, снимает активные заявки и закрывает позиции. Самостоятельных входов стратегия не совершает — её задача ограничивается мониторингом и аварийным закрытием.
Периодичность контроля задаётся через подписку на свечи, поэтому компонент одинаково подходит для исторического тестирования и реальной торговли. CloseProfit V2 можно запускать вместе с другими стратегиями, которые используют тот же портфель: при срабатывании условий утилита отменит их заявки и принудительно закроет позиции.
Принцип работы
При старте стратегия сохраняет текущее значение портфеля как «базовую» величину капитала в момент отсутствия позиций и активирует выбранный поток свечей.
После закрытия каждой свечи фиксируется её цена закрытия и рассчитывается плавающая прибыль:
Если AllSymbols = false, контролируется только основной инструмент. Прибыль вычисляется по формуле Position * (lastClose - averagePrice), то есть учитывается исключительно нереализованный результат.
Если AllSymbols = true, текущая стоимость портфеля сравнивается с сохранённой базовой величиной, что позволяет оценить суммарный плавающий результат по всем инструментам стратегии.
Когда плавающая прибыль превышает ProfitClose или опускается ниже -LossClose, стратегия переводит флаг принудительного закрытия в активное состояние, отменяет заявки и отправляет рыночные приказы на закрытие каждого задействованного инструмента.
После обнуления позиций стратегия обновляет базовое значение капитала. Благодаря этому дальнейший мониторинг начинается уже с новой точки и не реагирует на ранее зафиксированную прибыль.
Реализация повторяет логику MQL‑версии: учитываются только открытые сделки, а история реализованной прибыли игнорируется. Дополнительно используется флаг _closeAllRequested, предотвращающий повторные запросы на закрытие в рамках одного сигнала.
Параметры
ProfitClose (по умолчанию 10) — порог плавающей прибыли в валюте счёта. При достижении этого значения стратегия закрывает все контролируемые позиции.
LossClose (по умолчанию 1000) — допустимый уровень плавающего убытка. При превышении по модулю указанного порога активируется принудительное закрытие.
AllSymbols (по умолчанию false) — если false, мониторится только основной Security; если true, стратегия суммирует плавающий результат по всем инструментам, находящимся под её управлением, и закрывает их одновременно.
CandleType (по умолчанию минутные свечи) — вид свечей, по которому происходит переоценка. Чем меньше таймфрейм, тем быстрее реакция, однако это увеличивает нагрузку при тестировании.
Практические рекомендации
Запускайте CloseProfit V2 вместе с основными торговыми алгоритмами, использующими тот же портфель. Как только сработают ограничения, стратегия отменит их ордера и закроет открытые позиции.
В высокоуровневом API StockSharp нет данных о комиссиях и свопах, поэтому плавающая прибыль рассчитывается только по ценам. Если учёт расходов критичен, следует увеличить пороговые значения.
Поскольку закрытие выполняется рыночными приказами, важно учитывать возможную просадку по ликвидности и заложить запас по уровням ProfitClose и LossClose.
Подписка на свечи обеспечивает детерминированные точки проверки при тестировании. В реальной торговле можно выбрать более мелкий таймфрейм для ускорения реакции.
В методе OnStarted вызывается StartProtection(), что сохраняет встроенные механизмы защиты StockSharp (переоткрытие соединений, контроль статуса стратегии и т. п.).
Отличия от исходного MQL-советника
Фильтр по «magic number» не нужен: в StockSharp ордера автоматически относятся к конкретной стратегии, поэтому AllSymbols применяется ко всем инструментам внутри одной стратегии.
В MetaTrader интерфейс строился на графических объектах с балансом, эквити и счётчиками заявок. В C#-версии используется логирование, что упрощает работу в безголовых сценариях.
Удалена отладочная логика, автоматически создававшая тестовые сделки в MQL. В StockSharp-версии оставлены только мониторинг и принудительное закрытие позиций.
Когда применять
CloseProfit V2 полезна в ситуациях, когда необходимо автоматически зафиксировать результат или ограничить убыток по совокупности позиций: при работе с проп-счетами, соблюдении внутренних регламентов риска, достижении дневных целей. Подбирайте таймфрейм свечей в соответствии с требуемой скоростью реакции.
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>
/// CloseProfit v2 strategy (simplified).
/// Uses EMA crossover for entries with profit/loss exit thresholds.
/// </summary>
public class CloseProfitV2Strategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastLength;
private readonly StrategyParam<int> _slowLength;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int FastLength
{
get => _fastLength.Value;
set => _fastLength.Value = value;
}
public int SlowLength
{
get => _slowLength.Value;
set => _slowLength.Value = value;
}
public CloseProfitV2Strategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Candles", "General");
_fastLength = Param(nameof(FastLength), 8)
.SetGreaterThanZero()
.SetDisplay("Fast EMA", "Fast EMA period", "Indicators");
_slowLength = Param(nameof(SlowLength), 21)
.SetGreaterThanZero()
.SetDisplay("Slow EMA", "Slow EMA period", "Indicators");
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var fastEma = new ExponentialMovingAverage { Length = FastLength };
var slowEma = new ExponentialMovingAverage { Length = SlowLength };
decimal prevFast = 0m;
decimal prevSlow = 0m;
var hasPrev = false;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(fastEma, slowEma, (ICandleMessage candle, decimal fastVal, decimal slowVal) =>
{
if (candle.State != CandleStates.Finished)
return;
if (!hasPrev)
{
prevFast = fastVal;
prevSlow = slowVal;
hasPrev = true;
return;
}
if (!IsFormedAndOnlineAndAllowTrading())
{
prevFast = fastVal;
prevSlow = slowVal;
return;
}
if (prevFast <= prevSlow && fastVal > slowVal && Position <= 0)
BuyMarket();
else if (prevFast >= prevSlow && fastVal < slowVal && Position >= 0)
SellMarket();
prevFast = fastVal;
prevSlow = slowVal;
})
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, fastEma);
DrawIndicator(area, slowEma);
DrawOwnTrades(area);
}
}
}
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 close_profit_v2_strategy(Strategy):
def __init__(self):
super(close_profit_v2_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Candles", "General")
self._fast_length = self.Param("FastLength", 8) \
.SetDisplay("Fast EMA", "Fast EMA period", "Indicators")
self._slow_length = self.Param("SlowLength", 21) \
.SetDisplay("Slow EMA", "Slow EMA period", "Indicators")
self._prev_fast = 0.0
self._prev_slow = 0.0
self._has_prev = False
@property
def CandleType(self):
return self._candle_type.Value
@property
def FastLength(self):
return self._fast_length.Value
@property
def SlowLength(self):
return self._slow_length.Value
def OnReseted(self):
super(close_profit_v2_strategy, self).OnReseted()
self._prev_fast = 0.0
self._prev_slow = 0.0
self._has_prev = False
def OnStarted2(self, time):
super(close_profit_v2_strategy, self).OnStarted2(time)
self._prev_fast = 0.0
self._prev_slow = 0.0
self._has_prev = False
fast_ema = ExponentialMovingAverage()
fast_ema.Length = self.FastLength
slow_ema = ExponentialMovingAverage()
slow_ema.Length = self.SlowLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(fast_ema, slow_ema, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, fast_ema)
self.DrawIndicator(area, slow_ema)
self.DrawOwnTrades(area)
def _on_process(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fv = float(fast_value)
sv = float(slow_value)
if not self._has_prev:
self._prev_fast = fv
self._prev_slow = sv
self._has_prev = True
return
if self._prev_fast <= self._prev_slow and fv > sv and self.Position <= 0:
self.BuyMarket()
elif self._prev_fast >= self._prev_slow and fv < sv and self.Position >= 0:
self.SellMarket()
self._prev_fast = fv
self._prev_slow = sv
def CreateClone(self):
return close_profit_v2_strategy()