Открыть на GitHub

Стратегия Grid Rebalance

Стратегия Grid Rebalance — это высокоуровневая версия советника Mission Automate «Grid» на платформе StockSharp. Стратегия чередует длинные и короткие циклы сетки и всегда поддерживает лестницу лимитных ордеров в направлении текущего цикла. Когда суммарная позиция достигает общего тейк-профита, цикл завершается, все отложенные заявки снимаются, и следующий цикл стартует в противоположную сторону.

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

  1. Старт цикла. При отсутствии позиций и отложенных ордеров стратегия открывает рыночную позицию в направлении FirstTradeSide объёмом StartVolume лотов.
  2. Построение сетки. После каждого исполнения ордера в активном направлении выставляется новый лимитный ордер на расстоянии GridStepPoints (переводится в цену через PriceStep инструмента). Объём нового ордера равен объёму последнего исполненного умноженному на LotMultiplier.
  3. Общий тейк-профит. После каждого пополнения позиции пересчитывается средняя взвешенная цена входа. Тейк-профит для всей корзины устанавливается на среднюю цену плюс/минус TargetPoints (также переводится через PriceStep). Для моделирования срабатывания серверного тейк-профита анализируются максимумы и минимумы свечей.
  4. Завершение цикла. Когда цена достигает уровня тейк-профита, стратегия закрывает всю позицию рыночным ордером, снимает оставшиеся лимитные заявки, запоминает направление завершившегося цикла и инвертирует его для следующего запуска.

Параметры

  • FirstTradeSide — направление первого цикла (Buy или Sell). После каждого завершённого цикла направление автоматически меняется на противоположное.
  • StartVolume — объём начального рыночного ордера в каждом цикле.
  • LotMultiplier — множитель, который применяется к объёму последнего исполненного ордера при подготовке следующего уровня сетки. Значения больше единицы формируют мартингейл-последовательность.
  • GridStepPoints — расстояние между уровнями сетки в пунктах. Стратегия умножает его на Security.PriceStep, чтобы получить абсолютную ценовую разницу.
  • TargetPoints — расстояние тейк-профита от средней цены входа, также измеряется в пунктах.
  • CandleType — тип свечей, по которым отслеживаются экстремумы для фиксации прибыли.

Управление рисками и особенности

  • Явный стоп-лосс отсутствует: при движении рынка против позиции сетка продолжает наращивать объём.
  • В каждый момент активен только один отложенный ордер; после его исполнения следующий уровень выставляется немедленно.
  • Новый цикл не запускается, пока открыта позиция, существуют активные заявки или инструмент не предоставил корректный PriceStep.
  • Все расчёты выполняются внутри стратегии без обращения к глобальным коллекциям и буферам индикаторов, что соответствует требованиям проекта.
  • При завершении цикла все отложенные ордера снимаются, что исключает появление «забытых» заявок от предыдущих циклов.

Примечания

  • Все параметры в пунктах конвертируются в цены с помощью Security.PriceStep. Пока шаг цены не задан, стратегия ждёт поступления информации от площадки.
  • Реализация использует только высокоуровневый API (SubscribeCandles, Bind, BuyMarket, SellMarket, BuyLimit, SellLimit).
  • Python-версия намеренно не создавалась в рамках этой задачи.
using System;

using Ecng.Common;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Grid Rebalance strategy: RSI + EMA crossover-based.
/// Buys when close crosses above EMA with RSI confirmation.
/// Sells when close crosses below EMA with RSI confirmation.
/// </summary>
public class GridRebalanceStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<int> _emaPeriod;

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

	public int RsiPeriod
	{
		get => _rsiPeriod.Value;
		set => _rsiPeriod.Value = value;
	}

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

	public GridRebalanceStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");

		_rsiPeriod = Param(nameof(RsiPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("RSI Period", "RSI period", "Indicators");

		_emaPeriod = Param(nameof(EmaPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("EMA Period", "EMA period", "Indicators");
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		var rsi = new RelativeStrengthIndex { Length = RsiPeriod };
		var ema = new ExponentialMovingAverage { Length = EmaPeriod };

		decimal? prevClose = null;
		decimal? prevEma = null;

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(rsi, ema, (candle, rsiVal, emaVal) =>
			{
				if (candle.State != CandleStates.Finished)
					return;

				if (!IsFormedAndOnlineAndAllowTrading())
					return;

				var close = candle.ClosePrice;

				if (prevClose.HasValue && prevEma.HasValue)
				{
					var crossUp = prevClose.Value <= prevEma.Value && close > emaVal;
					var crossDown = prevClose.Value >= prevEma.Value && close < emaVal;

					if (crossUp && rsiVal < 55m && Position <= 0)
						BuyMarket();
					else if (crossDown && rsiVal > 45m && Position >= 0)
						SellMarket();
				}

				prevClose = close;
				prevEma = emaVal;
			})
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, ema);
			DrawOwnTrades(area);
		}
	}
}