Открыть на GitHub

Стратегия Breakdown Pending Stop

Общее описание

Стратегия воспроизводит эксперта MetaTrader «breakdown»: каждый день она рассчитывает диапазон предыдущей сессии, размещает отложенные стоп-заявки за границами этого диапазона и переносит защитный стоп по ступенчатому правилу, когда позиция начинает приносить прибыль.

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

  • Подготовка по дневным данным. После закрытия дневной свечи сохраняются максимум и минимум. В начале новой сессии все старые заявки снимаются и выставляются новые Buy Stop и Sell Stop с отступом Min Distance (ticks) от предыдущего максимума и минимума.
  • Переустановка заявок. При любом исполнении или начале нового дня оставшиеся заявки отменяются и мгновенно создаётся новая пара с теми же уровнями, что полностью соответствует поведению исходного MQL-советника.
  • Управление рисками. При открытии позиции рассчитываются стоп-лосс и тейк-профит на основе количества тиков. Алгоритм трейлинг-стопа передвигает защитный уровень только после того, как прибыль превысила сумму Trailing Stop (ticks) + Trailing Step (ticks).
  • Выход из позиции. Сделка закрывается при достижении стоп-лосса или тейк-профита. Если активируется трейлинг-стоп, стратегия закрывает позицию рыночной заявкой, что имитирует вызов PositionModify в оригинале.

Параметры

Параметр Описание
Working Candles Таймфрейм рабочих свечей для мониторинга цены и сопровождения позиции (по умолчанию 15 минут).
Stop Loss (ticks) Начальный стоп-лосс в тиках, преобразуется в абсолютную цену с учётом шага цены. Ноль отключает стоп.
Take Profit (ticks) Начальный тейк-профит в тиках, ноль отключает цель.
Trailing Stop (ticks) Дистанция трейлинг-стопа. При значении 0 трейлинг отключён.
Trailing Step (ticks) Дополнительное требуемое движение до переноса трейлинг-стопа.
Min Distance (ticks) Отступ от максимумов/минимумов предыдущего дня при выставлении стоп-заявок.
Order Volume Объём обеих отложенных заявок.

Рекомендации

  • Используйте стратегию на инструментах с надёжной дневной историей, иначе уровни предыдущего дня будут некорректны.
  • Предполагается фиксированный шаг цены. Если тик меняется динамически, скорректируйте входные параметры.
  • Модель управления капиталом из оригинального MQL (расчёт объёма от свободной маржи) не реализована — размер позиции задаётся параметром Order Volume.
  • Python-реализация пока отсутствует.
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>
/// Breakout strategy using Highest/Lowest channel.
/// Enters long when price breaks above the channel high, enters short when price breaks below the channel low.
/// </summary>
public class BreakdownPendingStopStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _period;

	private decimal? _prevHigh;
	private decimal? _prevLow;

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

	public int Period
	{
		get => _period.Value;
		set => _period.Value = value;
	}

	public BreakdownPendingStopStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe", "General");

		_period = Param(nameof(Period), 20)
			.SetGreaterThanZero()
			.SetDisplay("Period", "Channel lookback period", "Indicators");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevHigh = null;
		_prevLow = null;
	}

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

		_prevHigh = null;
		_prevLow = null;

		var highest = new Highest { Length = Period };
		var lowest = new Lowest { Length = Period };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(highest, lowest, ProcessCandle)
			.Start();

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

	private void ProcessCandle(ICandleMessage candle, decimal high, decimal low)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!IsFormedAndOnlineAndAllowTrading())
		{
			_prevHigh = high;
			_prevLow = low;
			return;
		}

		if (_prevHigh == null || _prevLow == null)
		{
			_prevHigh = high;
			_prevLow = low;
			return;
		}

		var close = candle.ClosePrice;

		if (close > _prevHigh.Value && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();
			BuyMarket();
		}
		else if (close < _prevLow.Value && Position >= 0)
		{
			if (Position > 0)
				SellMarket();
			SellMarket();
		}

		_prevHigh = high;
		_prevLow = low;
	}
}