BadOrders Strategy — это точный перенос советника MetaTrader 4 BadOrders.mq4. Первоначальный скрипт задумывался как демонстрация неправильной работы с заявками и специально провоцирует ошибки брокера:
На каждом новом тике принудительно закрывается недавно открытая позиция по текущей цене Bid.
Сразу после этого выставляется заявка Buy Stop на расстоянии 100 пунктов выше Bid.
Затем эта же заявка мгновенно переносится на 100 пунктов ниже Bid, что нарушает минимально допустимую дистанцию и приводит к отказу во исполнении.
Перенос на StockSharp реализован исключительно средствами высокоуровневого API. Стратегия подписывается на поток лучших цен (Level 1) и повторяет оригинальный цикл «закрыть позицию — выставить стоп — переместить стоп в запрещённую зону» при каждом обновлении котировок.
Детали реализации
Источники данных: используется SubscribeLevel1(), поскольку MT4-версия работает по каждому тику, а не по закрытию свечи.
Управление заявками: закрытие позиций выполняется через ClosePosition(). Стоп-заявка создаётся методом BuyStop(), а её мгновенная «порча» реализована через ReRegisterOrder(), полностью имитируя логику исходного файла.
Нормализация цен: все уровни проходят через Security.ShrinkPrice(). Понятие Point из MetaTrader воспроизводится с помощью PriceStep, а при отсутствии данных используется запасное значение 0.0001.
Защита от дублирования: перед вызовом ClosePosition() проверяется, нет ли уже активной встречной заявки, чтобы не отправлять несколько одинаковых приказов на закрытие.
Параметры
Имя
Описание
Значение по умолчанию
DistancePoints
Расстояние в «пунктах» MetaTrader, добавляемое к текущему Bid при выставлении и изменении стоп-заявки.
100
Логика работы
При каждом обновлении Bid стратегия пытается закрыть открытую позицию.
После закрытия выставляется Buy Stop на цене Bid + DistancePoints * PointValue.
Немедленно эта заявка переносится на Bid - DistancePoints * PointValue, то есть в зону, запрещённую правилами площадки. Ошибка при регистрации ожидаема и полностью соответствует обучающей цели исходного скрипта.
Важно: пример создан исключительно для демонстрации некорректной работы с заявками и не предназначен для использования на реальном рынке.
using System;
using System.Collections.Generic;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Bad Orders strategy - ATR breakout with EMA filter.
/// Buys when price breaks above EMA + ATR threshold.
/// Sells when price breaks below EMA - ATR threshold.
/// </summary>
public class BadOrdersStrategy : Strategy
{
private readonly StrategyParam<int> _emaPeriod;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<decimal> _atrMultiplier;
private readonly StrategyParam<DataType> _candleType;
public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }
public int AtrPeriod { get => _atrPeriod.Value; set => _atrPeriod.Value = value; }
public decimal AtrMultiplier { get => _atrMultiplier.Value; set => _atrMultiplier.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public BadOrdersStrategy()
{
_emaPeriod = Param(nameof(EmaPeriod), 20)
.SetDisplay("EMA Period", "EMA lookback", "Indicators");
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetDisplay("ATR Period", "ATR lookback", "Indicators");
_atrMultiplier = Param(nameof(AtrMultiplier), 1.5m)
.SetDisplay("ATR Multiplier", "ATR breakout multiplier", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];
protected override void OnReseted() { base.OnReseted(); }
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var ema = new ExponentialMovingAverage { Length = EmaPeriod };
var atr = new AverageTrueRange { Length = AtrPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ema, atr, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal ema, decimal atr)
{
if (candle.State != CandleStates.Finished)
return;
var close = candle.ClosePrice;
var upper = ema + atr * AtrMultiplier;
var lower = ema - atr * AtrMultiplier;
if (close > upper && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
}
else if (close < lower && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
}
}
}