Открыть на GitHub

Утилита Basket Close

Обзор

Стратегия «Утилита Basket Close» повторяет работу советника MetaTrader «Basket Close 2». Она постоянно отслеживает плавающий финансовый результат всех открытых позиций в портфеле и при достижении заданной цели по прибыли или лимита по убытку отправляет рыночные заявки, чтобы полностью закрыть все позиции по каждому инструменту. Дополнительно можно автоматически открывать тестовую сделку, если портфель пуст, что удобно при тестировании защиты капитала.

Параметры

Название Описание
LossMode Выбор метода контроля убытка: по проценту или по денежной сумме.
LossPercentage Порог просадки в процентах (модуль значения), при достижении которого закрываются все позиции, если выбран режим Percentage.
LossCurrency Плавающий убыток в валюте счёта, который запускает закрытие при режиме Currency.
ProfitMode Выбор метода фиксации прибыли: по проценту или по денежной сумме.
ProfitPercentage Процент прибыли, при достижении которого закрываются все позиции в режиме Percentage.
ProfitCurrency Плавающая прибыль в валюте счёта, при достижении которой закрываются все позиции в режиме Currency.
CandleType Таймфрейм, по закрытию которого выполняются периодические проверки PnL.
EnableTestOrders При включении автоматически отправляет рыночную покупку, когда открытых позиций нет.
TestOrderVolume Объём ордера, используемый тестовой сделкой.

Логика работы

  1. Подписаться на выбранный таймфрейм и выполнять проверку только после закрытия свечи, как это делал исходный советник.
  2. Рассчитать суммарный плавающий PnL всех открытых позиций. Если портфель предоставляет агрегированное значение, используется оно, иначе значения позиций суммируются вручную.
  3. Рассчитать процентное изменение относительно баланса счёта, зафиксированного в момент запуска стратегии.
  4. Запустить защиту от убытка, когда плавающий результат достигает отрицательного порога. Запустить фиксацию прибыли, когда плавающий PnL или процент прибыли достигает заданной цели.
  5. После срабатывания условий продолжать отправлять рыночные заявки, пока каждая позиция в портфеле (включая дочерние стратегии) не будет полностью закрыта.
  6. Если включены тестовые ордера, после обнуления позиций снова отправить тестовую сделку, чтобы повторно проверить работу защиты.

Примечания

  • В оригинальном советнике информация выводилась на график, в StockSharp ключевые показатели фиксируются через LogInfo.
  • Комиссии и свопы, которые в MQL добавлялись вручную, уже входят в плавающий PnL портфеля или отдельных позиций.
  • Пороговые значения по проценту используют баланс, сохранённый на старте. При значительном изменении капитала корректируйте параметры.
  • Тестовый ордер выставляется повторно каждый раз, когда все позиции были закрыты защитными условиями.
namespace StockSharp.Samples.Strategies;

using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;

/// <summary>
/// Basket Close strategy: EMA trend following with profit/loss close thresholds.
/// Enters on EMA direction, closes when accumulated P&L hits target or stop.
/// </summary>
public class BasketCloseStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _emaPeriod;

	private decimal _entryPrice;
	private bool _wasBullish;
	private bool _hasPrevSignal;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }

	public BasketCloseStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
		_emaPeriod = Param(nameof(EmaPeriod), 50)
			.SetGreaterThanZero()
			.SetDisplay("EMA Period", "EMA period", "Indicators");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_entryPrice = 0m;
		_wasBullish = false;
		_hasPrevSignal = false;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_entryPrice = 0;
		_hasPrevSignal = false;
		var ema = new ExponentialMovingAverage { Length = EmaPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(ema, ProcessCandle).Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal emaValue)
	{
		if (candle.State != CandleStates.Finished) return;

		var close = candle.ClosePrice;
		var isBullish = close > emaValue;

		if (_hasPrevSignal && isBullish != _wasBullish)
		{
			if (isBullish && Position <= 0)
			{
				BuyMarket();
				_entryPrice = close;
			}
			else if (!isBullish && Position >= 0)
			{
				SellMarket();
				_entryPrice = close;
			}
		}

		_wasBullish = isBullish;
		_hasPrevSignal = true;
	}
}