Стратегия Sample Check Pending Order постоянно поддерживает в стакане ровно один стоп-ордер на покупку и один стоп-ордер на продажу. Исходный советник MetaTrader 5 от Tungman проверял допустимость указанного лота у брокера, контролировал достаточность свободной маржи в обоих направлениях и затем размещал новые отложенные ордера прямо по текущим BID/ASK с истечением через сутки. В этой реализации те же действия перенесены на StockSharp с использованием высокоуровневого API и данных уровня 1.
Логика торговли
Обработка рыночных данных
Стратегия подписывается на поток Level 1 и кеширует последние значения лучшего BID и лучшего ASK.
Пока обе цены недоступны или IsFormedAndOnlineAndAllowTrading() возвращает false, торговые решения приостанавливаются.
Проверка объёма
Каждый тик запускает проверку параметра OrderVolume относительно Security.MinVolume, Security.MaxVolume и Security.VolumeStep.
Объём должен входить в допустимый диапазон и быть кратным шагу лота. При нарушении выводится сообщение в лог, а новые ордера не отправляются.
Предварительная оценка маржи
Перед постановкой ордеров рассчитывается требуемая маржа для длинной и короткой позиции указанного размера. Используются текущие BID/ASK, множитель инструмента и заданное пользователем значение AccountLeverage.
Если фактическая или стартовая стоимость портфеля недостаточна хотя бы для одного направления, стратегия пропускает тик, что соответствует поведению функции CheckMoneyForTrade.
Размещение отложенных ордеров
При отсутствии активного buy-stop стратегия выставляет его по текущему ASK (после округления к ближайшему шагу цены). Аналогично sell-stop размещается по текущему BID.
Для каждого ордера запоминается срок действия (ExpirationMinutes, по умолчанию сутки). При последующих тиках, если срок истёк, ордер отменяется и ячейка освобождается под новую заявку.
Управление рисками
Метод StartProtection автоматически добавляет стоп-лосс и тейк-профит на расстояниях StopLossPoints и TakeProfitPoints. После срабатывания stop-ордера защитные заявки выставляются движком StockSharp, как и в оригинальной версии MT5.
В результате получается минималистичная стратегия пробоя, которая «зажимает» рынок между двумя стопами. Как только один из них активируется, противоположный остаётся в силе, а недостающий ордер будет создан при следующем обновлении котировок.
Параметры
Параметр
Описание
OrderVolume
Объём заявки. Должен соответствовать ограничениям брокера и шагу лота.
StopLossPoints
Расстояние в пунктах до защитного стоп-лосса после открытия позиции.
TakeProfitPoints
Расстояние в пунктах до тейк-профита, который ставится после входа.
ExpirationMinutes
Время жизни каждой отложенной заявки. По истечении ордер отменяется.
AccountLeverage
Оценка кредитного плеча, используемая при расчёте требуемой маржи.
Все расстояния переводятся в абсолютные цены через Security.PriceStep. Если шаг цены или множитель инструмента отсутствуют, применяется запасное значение 1, чтобы избежать неопределённостей. Логи фиксируют нестандартные ситуации для облегчения диагностики.
Особенности реализации
Жизненный цикл ордеров – В полях стратегии хранятся последние объекты Order, возвращённые BuyStop и SellStop. Вспомогательные методы обнуляют ссылки после перехода ордера в состояние Done, Canceled или Failed, предотвращая ложное распознавание неактивных заявок.
Контроль срока действия – Поскольку не все площадки поддерживают серверное истечение стоп-ордеров, время жизни отслеживается на стороне стратегии. При превышении лимита вызывается CancelOrder.
Оценка маржи – Требования по марже оцениваются через доступный капитал портфеля и введённое плечо. Такой подход близок к OrderCalcMargin и не зависит от специфики биржи.
Высокоуровневый API – Весь функционал реализован средствами SubscribeLevel1, BuyStop, SellStop и StartProtection, что соответствует методическим рекомендациям и упрощает код.
Документация намеренно насыщена деталями, чтобы трейдер мог легко сопоставить конверсию с оригинальным советником и адаптировать параметры под своего брокера.
namespace StockSharp.Samples.Strategies;
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;
/// <summary>
/// Sample Check Pending Order strategy: CCI trend following.
/// Buys when CCI crosses above zero, sells when CCI crosses below zero.
/// </summary>
public class SampleCheckPendingOrderStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _period;
private readonly StrategyParam<decimal> _cciLevel;
private readonly StrategyParam<int> _signalCooldownCandles;
private decimal _prevCci;
private int _candlesSinceTrade;
private bool _hasPrev;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int Period { get => _period.Value; set => _period.Value = value; }
public decimal CciLevel { get => _cciLevel.Value; set => _cciLevel.Value = value; }
public int SignalCooldownCandles { get => _signalCooldownCandles.Value; set => _signalCooldownCandles.Value = value; }
public SampleCheckPendingOrderStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
_period = Param(nameof(Period), 30)
.SetGreaterThanZero()
.SetDisplay("Period", "CCI period", "Indicators");
_cciLevel = Param(nameof(CciLevel), 100m)
.SetDisplay("CCI Level", "CCI crossover threshold", "Signals");
_signalCooldownCandles = Param(nameof(SignalCooldownCandles), 4)
.SetGreaterThanZero()
.SetDisplay("Signal Cooldown", "Bars to wait between trades", "Trading");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevCci = 0;
_candlesSinceTrade = SignalCooldownCandles;
_hasPrev = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevCci = 0;
_candlesSinceTrade = SignalCooldownCandles;
_hasPrev = false;
var cci = new CommodityChannelIndex { Length = Period };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(cci, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal cciValue)
{
if (candle.State != CandleStates.Finished) return;
if (_candlesSinceTrade < SignalCooldownCandles)
_candlesSinceTrade++;
if (_hasPrev)
{
if (_prevCci <= -CciLevel && cciValue > -CciLevel && Position <= 0 && _candlesSinceTrade >= SignalCooldownCandles)
{
BuyMarket();
_candlesSinceTrade = 0;
}
else if (_prevCci >= CciLevel && cciValue < CciLevel && Position >= 0 && _candlesSinceTrade >= SignalCooldownCandles)
{
SellMarket();
_candlesSinceTrade = 0;
}
}
_prevCci = cciValue;
_hasPrev = true;
}
}