Открыть на GitHub

Two Pending Orders 2

Обзор

Эта стратегия представляет собой портирование эксперта MetaTrader «Two pending orders 2» на платформу StockSharp. Алгоритм постоянно поддерживает две симметричные отложенные заявки вокруг текущей цены и управляет позицией первой сработавшей стороны с помощью настраиваемых стоп-лосса, тейк-профита и трейлинг-стопа. Конверсия выполнена на высокоуровневом API StockSharp и сохраняет ключевую логику оригинала, предоставляя все параметры в виде настраиваемых свойств стратегии.

Логика торговли

  1. Стратегия подписывается на выбранную серию свечей (по умолчанию дневные). Завершение свечи является моментом принятия нового торгового решения.
  2. Перед установкой свежих уровней отменяются истекшие и активные отложенные заявки, поэтому на рынке остаются только актуальные ордера.
  3. Если текущий спред не превышает допустимый максимум и число позиций/ордеров ниже заданного лимита, размещаются две симметричные заявки:
    • В режиме Stop (по умолчанию) выставляются buy stop выше рынка и sell stop ниже рынка.
    • В режиме Limit выставляются buy limit ниже рынка и sell limit выше рынка.
    • Флаг Reverse Levels меняет направления местами, что соответствует переключателю reverse в оригинальном советнике.
  4. Цены заявок смещаются относительно текущих bid/ask на величину параметра Pending Indent. Если новый уровень находится ближе к существующим позициям, чем значение Min Step, ордер не ставится.
  5. Для отложенных ордеров можно задать срок действия. По его истечении заявки автоматически отменяются.

Управление позициями

  • После срабатывания ордера стратегия отслеживает среднюю цену входа и объем по соответствующей стороне. Сделки противоположного направления сначала сокращают или закрывают текущую позицию и лишь затем открывают новую.
  • Длинные позиции закрываются при выполнении любого из условий:
    • Цена достигает уровня стоп-лосса ниже средней цены входа.
    • Цена достигает уровня тейк-профита выше средней цены входа.
    • Прибыль превышает порог активации трейлинг-стопа и последующее откатное движение касается перенесенного стопа (движение происходит ступенчато).
  • Короткие позиции используют зеркальные проверки с инвертированными сравнениями цен.
  • Если включен параметр 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();
	}
}