Эта стратегия представляет собой портирование эксперта MetaTrader «Two pending orders 2» на платформу StockSharp. Алгоритм постоянно поддерживает две симметричные отложенные заявки вокруг текущей цены и управляет позицией первой сработавшей стороны с помощью настраиваемых стоп-лосса, тейк-профита и трейлинг-стопа. Конверсия выполнена на высокоуровневом API StockSharp и сохраняет ключевую логику оригинала, предоставляя все параметры в виде настраиваемых свойств стратегии.
Логика торговли
Стратегия подписывается на выбранную серию свечей (по умолчанию дневные). Завершение свечи является моментом принятия нового торгового решения.
Перед установкой свежих уровней отменяются истекшие и активные отложенные заявки, поэтому на рынке остаются только актуальные ордера.
Если текущий спред не превышает допустимый максимум и число позиций/ордеров ниже заданного лимита, размещаются две симметричные заявки:
В режиме Stop (по умолчанию) выставляются buy stop выше рынка и sell stop ниже рынка.
В режиме Limit выставляются buy limit ниже рынка и sell limit выше рынка.
Флаг Reverse Levels меняет направления местами, что соответствует переключателю reverse в оригинальном советнике.
Цены заявок смещаются относительно текущих bid/ask на величину параметра Pending Indent. Если новый уровень находится ближе к существующим позициям, чем значение Min Step, ордер не ставится.
Для отложенных ордеров можно задать срок действия. По его истечении заявки автоматически отменяются.
Управление позициями
После срабатывания ордера стратегия отслеживает среднюю цену входа и объем по соответствующей стороне. Сделки противоположного направления сначала сокращают или закрывают текущую позицию и лишь затем открывают новую.
Длинные позиции закрываются при выполнении любого из условий:
Цена достигает уровня стоп-лосса ниже средней цены входа.
Цена достигает уровня тейк-профита выше средней цены входа.
Прибыль превышает порог активации трейлинг-стопа и последующее откатное движение касается перенесенного стопа (движение происходит ступенчато).
Короткие позиции используют зеркальные проверки с инвертированными сравнениями цен.
Если включен параметр Only One Position, стратегия ждет полного закрытия текущей позиции перед постановкой новых заявок.
Параметры
Имя
Описание
StopLossPoints
Расстояние до защитного стоп-лосса в пунктах (0 — без стопа).
TakeProfitPoints
Расстояние до тейк-профита в пунктах (0 — без тейка).
MaxPositions
Максимальное количество одновременно активных позиций и ордеров.
MinStepPoints
Минимальный допуск между текущими позициями и ценами новых ордеров.
TrailingActivatePoints
Порог прибыли для активации трейлинг-стопа (0 — отключен).
TrailingStopPoints
Расстояние трейлинг-стопа после активации.
TrailingStepPoints
Минимальный шаг, на который переносится трейлинг-стоп.
TradeMode
Разрешенное направление торговли: Buy, Sell или BuySell.
PendingType
Тип отложенных заявок: Stop или Limit.
PendingExpirationMinutes
Срок действия ордеров в минутах (0 — без ограничения).
PendingIndentPoints
Отступ от текущего рынка при расчете цен отложенных ордеров.
PendingMaxSpreadPoints
Максимально допустимый спред для постановки ордеров (0 — без фильтра).
OnlyOnePosition
При значении true не позволяет держать более одной позиции одновременно.
ReverseLevels
Меняет местами направления ордеров, реализуя обратный режим оригинала.
CandleType
Таймфрейм, по закрытию свечи которого оцениваются сигналы (по умолчанию день).
Примечания
Все дистанции задаются в пунктах и автоматически переводятся в цену через размер шага котировки инструмента.
Для постановки и отмены заявок используются высокоуровневые методы StockSharp (BuyStop, SellStop, BuyLimit, SellLimit, CancelActiveOrders).
Логика трейлинг-стопа проверяется по завершенным свечам. Для более быстрого реагирования выберите меньший CandleType.
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Simplified from "Two pending orders 2" MetaTrader expert.
/// Places symmetric breakout levels around recent range and enters on breakout.
/// Uses high/low of N bars as breakout boundaries.
/// </summary>
public class TwoPendingOrders2Strategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _lookback;
private readonly Queue<decimal> _highs = new();
private readonly Queue<decimal> _lows = new();
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int Lookback
{
get => _lookback.Value;
set => _lookback.Value = value;
}
public TwoPendingOrders2Strategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for breakout detection", "General");
_lookback = Param(nameof(Lookback), 10)
.SetGreaterThanZero()
.SetDisplay("Lookback", "Number of bars for high/low range", "Indicators");
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_highs.Clear();
_lows.Clear();
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
if (_highs.Count < Lookback)
{
EnqueueCandle(candle);
return;
}
decimal highest = decimal.MinValue;
decimal lowest = decimal.MaxValue;
var highs = _highs.ToArray();
var lows = _lows.ToArray();
foreach (var h in highs)
if (h > highest) highest = h;
foreach (var l in lows)
if (l < lowest) lowest = l;
var close = candle.ClosePrice;
var volume = Volume;
if (volume <= 0)
volume = 1;
var range = highest - lowest;
var breakoutPadding = range * 0.05m;
// Breakout above range
if (close > highest + breakoutPadding)
{
if (Position <= 0)
BuyMarket(Position < 0 ? Math.Abs(Position) + volume : volume);
}
// Breakout below range
else if (close < lowest - breakoutPadding)
{
if (Position >= 0)
SellMarket(Position > 0 ? Math.Abs(Position) + volume : volume);
}
EnqueueCandle(candle);
}
private void EnqueueCandle(ICandleMessage candle)
{
_highs.Enqueue(candle.HighPrice);
_lows.Enqueue(candle.LowPrice);
if (_highs.Count > Lookback)
{
_highs.Dequeue();
_lows.Dequeue();
}
}
/// <inheritdoc />
protected override void OnReseted()
{
_highs.Clear();
_lows.Clear();
base.OnReseted();
}
}