Стратегия представляет собой портирование MetaTrader 4 советника Pedroxxmod в экосистему StockSharp. Исходный робот ждёт,
когда цена отклонится от опорного уровня на заданное число пунктов, и открывает встречную позицию. При возврате цены на указанную
дистанцию он добавляет новые сделки в ту же сторону. В StockSharp сохранена эта логика, а параметры вынесены в типизированный
интерфейс Strategy для удобства оптимизации и бэктестинга.
Логика торговли
Подписаться на лучшую котировку Level1 и кэшировать текущие значения бид/аск.
При отсутствии позиций сохранить текущий аск в качестве опорной цены. Торговля разрешена только между StartHour и
EndHour, а также начиная с календарного года StartYear.
Если аск поднимается выше опорного уровня на Gap пунктов MetaTrader, отправляется рыночная продажа. Если опускается ниже на
Gap пунктов — отправляется рыночная покупка. Для сделок автоматически выставляются стоп и тейк через SetStopLoss и
SetTakeProfit с теми же расстояниями, что и в советнике.
После выбора направления стратегия ведёт FIFO-список виртуальных сделок, имитируя хеджирующие счета MT4. Пока число сделок в
корзине меньше MaxTrades, добавление в позицию происходит при возврате аска к последней цене входа внутри диапазона
ReEntryGap пунктов.
Управление объёмом может опираться на фиксированное значение Lots либо на правило money management floor(Equity / 20000)
с ограничением сверху MaxLots. Перед регистрацией ордеров объём нормализуется по VolumeStep, MinVolume и MaxVolume инструмента.
Во время простоя (вне торгового окна) опорные уровни сбрасываются, чтобы при следующем сеансе не возникли ложные сигналы.
Параметры
Имя
Описание
Lots
Фиксированный объём, если отключено динамическое управление.
StopLoss
Расстояние до защитного стопа в пунктах MetaTrader (0 отключает стоп).
TakeProfit
Расстояние до тейк-профита в пунктах (0 отключает тейк).
Gap
Отклонение от опорной цены, необходимое для открытия первой сделки.
MaxTrades
Максимальное число одновременно открытых сделок (размер корзины).
ReEntryGap
Возврат цены в пунктах, при котором добавляются новые сделки в корзину.
MoneyManagement
Включает формулу floor(Equity / 20000) для расчёта объёма.
MaxLots
Верхний предел для динамически рассчитанного объёма.
StartHour / EndHour
Временные границы торговли по серверному времени (включительно).
StartYear
Минимальный год, начиная с которого разрешено торговать.
Примечания
Стратегия использует только поток Level1 и не подписывается на свечи, поэтому реагирует на котировки так же оперативно, как
обработчик start() в MT4.
Стопы и тейки вычисляются стандартными методами Strategy, что требует корректных торговых параметров инструмента (PriceStep,
StepPrice, VolumeStep).
FIFO-учёт виртуальных сделок позволяет воспроизвести поведение хеджирующих счетов, несмотря на то, что StockSharp хранит
агрегированную позицию. Частичное закрытие и срабатывание защитных ордеров обрабатывается в OnPositionChanged.
Python-версия намеренно не создаётся во исполнение требований репозитория.
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using StockSharp.Algo;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// Pedro Mod mean reversion strategy using Bollinger Bands.
/// Buy when price touches the lower band, sell when price touches the upper band.
/// </summary>
public class PedroModStrategy : Strategy
{
private readonly StrategyParam<int> _bollingerPeriod;
private readonly StrategyParam<decimal> _bollingerWidth;
private readonly StrategyParam<DataType> _candleType;
public int BollingerPeriod { get => _bollingerPeriod.Value; set => _bollingerPeriod.Value = value; }
public decimal BollingerWidth { get => _bollingerWidth.Value; set => _bollingerWidth.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public PedroModStrategy()
{
_bollingerPeriod = Param(nameof(BollingerPeriod), 20)
.SetDisplay("Bollinger Period", "Bollinger Bands period", "Indicators");
_bollingerWidth = Param(nameof(BollingerWidth), 1.5m)
.SetDisplay("Bollinger Width", "Standard deviation multiplier", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var bb = new BollingerBands { Length = BollingerPeriod, Width = BollingerWidth };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(bb, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue bbValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!bbValue.IsFinal)
return;
var bb = (BollingerBandsValue)bbValue;
if (bb.UpBand is not decimal upper || bb.LowBand is not decimal lower || bb.MovingAverage is not decimal middle)
return;
// Buy when price touches lower band (mean reversion)
if (Position <= 0 && candle.ClosePrice <= lower)
{
if (Position < 0)
BuyMarket();
BuyMarket();
}
// Sell when price touches upper band
else if (Position >= 0 && candle.ClosePrice >= upper)
{
if (Position > 0)
SellMarket();
SellMarket();
}
// Exit at middle band
else if (Position > 0 && candle.ClosePrice >= middle)
{
SellMarket();
}
else if (Position < 0 && candle.ClosePrice <= middle)
{
BuyMarket();
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import BollingerBands
from StockSharp.Algo.Strategies import Strategy
class pedro_mod_strategy(Strategy):
"""Pedro Mod mean reversion strategy using Bollinger Bands.
Buy when price touches the lower band, sell when price touches the upper band.
Exit at the middle band."""
def __init__(self):
super(pedro_mod_strategy, self).__init__()
self._bollinger_period = self.Param("BollingerPeriod", 20) \
.SetDisplay("Bollinger Period", "Bollinger Bands period", "Indicators")
self._bollinger_width = self.Param("BollingerWidth", 1.5) \
.SetDisplay("Bollinger Width", "Standard deviation multiplier", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Candle timeframe", "General")
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def BollingerPeriod(self):
return self._bollinger_period.Value
@property
def BollingerWidth(self):
return self._bollinger_width.Value
def OnReseted(self):
super(pedro_mod_strategy, self).OnReseted()
def OnStarted2(self, time):
super(pedro_mod_strategy, self).OnStarted2(time)
bb = BollingerBands()
bb.Length = self.BollingerPeriod
bb.Width = self.BollingerWidth
subscription = self.SubscribeCandles(self.CandleType)
subscription.BindEx(bb, self._process_candle).Start()
def _process_candle(self, candle, bb_value):
if candle.State != CandleStates.Finished:
return
if not bb_value.IsFinal:
return
up_band = bb_value.UpBand if hasattr(bb_value, 'UpBand') else None
low_band = bb_value.LowBand if hasattr(bb_value, 'LowBand') else None
mid_band = bb_value.MovingAverage if hasattr(bb_value, 'MovingAverage') else None
if up_band is None or low_band is None or mid_band is None:
return
upper = float(up_band)
lower = float(low_band)
middle = float(mid_band)
close = float(candle.ClosePrice)
# Buy when price touches lower band (mean reversion)
if self.Position <= 0 and close <= lower:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
# Sell when price touches upper band
elif self.Position >= 0 and close >= upper:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
# Exit at middle band
elif self.Position > 0 and close >= middle:
self.SellMarket()
elif self.Position < 0 and close <= middle:
self.BuyMarket()
def CreateClone(self):
return pedro_mod_strategy()