Стратегия воспроизводит эксперта 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;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import Highest, Lowest
from StockSharp.Algo.Strategies import Strategy
class breakdown_pending_stop_strategy(Strategy):
def __init__(self):
super(breakdown_pending_stop_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._period = self.Param("Period", 20) \
.SetDisplay("Period", "Channel lookback period", "Indicators")
self._prev_high = None
self._prev_low = None
@property
def CandleType(self):
return self._candle_type.Value
@property
def Period(self):
return self._period.Value
def OnReseted(self):
super(breakdown_pending_stop_strategy, self).OnReseted()
self._prev_high = None
self._prev_low = None
def OnStarted2(self, time):
super(breakdown_pending_stop_strategy, self).OnStarted2(time)
self._prev_high = None
self._prev_low = None
highest = Highest()
highest.Length = self.Period
lowest = Lowest()
lowest.Length = self.Period
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(highest, lowest, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, highest)
self.DrawIndicator(area, lowest)
self.DrawOwnTrades(area)
def _on_process(self, candle, high_value, low_value):
if candle.State != CandleStates.Finished:
return
hv = float(high_value)
lv = float(low_value)
if self._prev_high is None or self._prev_low is None:
self._prev_high = hv
self._prev_low = lv
return
close = float(candle.ClosePrice)
if close > self._prev_high and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif close < self._prev_low and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._prev_high = hv
self._prev_low = lv
def CreateClone(self):
return breakdown_pending_stop_strategy()