Открыть на GitHub

Стратегия CloseProfit V2

Общее описание

CloseProfit V2 — это порт оригинальной утилиты MetaTrader, которая закрывает все позиции при достижении заданного уровня плавающей прибыли или убытка. В версии для StockSharp стратегия выполняет роль защитного модуля: на каждой завершённой свече она пересчитывает плавающий результат и, при выходе за установленные границы, снимает активные заявки и закрывает позиции. Самостоятельных входов стратегия не совершает — её задача ограничивается мониторингом и аварийным закрытием.

Периодичность контроля задаётся через подписку на свечи, поэтому компонент одинаково подходит для исторического тестирования и реальной торговли. CloseProfit V2 можно запускать вместе с другими стратегиями, которые используют тот же портфель: при срабатывании условий утилита отменит их заявки и принудительно закроет позиции.

Принцип работы

  1. При старте стратегия сохраняет текущее значение портфеля как «базовую» величину капитала в момент отсутствия позиций и активирует выбранный поток свечей.
  2. После закрытия каждой свечи фиксируется её цена закрытия и рассчитывается плавающая прибыль:
    • Если AllSymbols = false, контролируется только основной инструмент. Прибыль вычисляется по формуле Position * (lastClose - averagePrice), то есть учитывается исключительно нереализованный результат.
    • Если AllSymbols = true, текущая стоимость портфеля сравнивается с сохранённой базовой величиной, что позволяет оценить суммарный плавающий результат по всем инструментам стратегии.
  3. Когда плавающая прибыль превышает ProfitClose или опускается ниже -LossClose, стратегия переводит флаг принудительного закрытия в активное состояние, отменяет заявки и отправляет рыночные приказы на закрытие каждого задействованного инструмента.
  4. После обнуления позиций стратегия обновляет базовое значение капитала. Благодаря этому дальнейший мониторинг начинается уже с новой точки и не реагирует на ранее зафиксированную прибыль.

Реализация повторяет логику 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);
		}
	}
}