This strategy is a StockSharp port of the Pedroxxmod MetaTrader 4 expert advisor. The original EA waits for the market to move a
few pips away from a reference price and then opens a contrarian position. Subsequent orders are averaged in the same direction
whenever the price retraces by a configurable distance. The StockSharp implementation keeps the behaviour intact while exposing
strongly typed parameters through the high-level Strategy API.
Trading logic
Subscribe to Level1 best bid/ask quotes and cache the most recent values.
When no trades are open, store the current ask price as the reference entry level. Trading is only allowed between
StartHour and EndHour, and from StartYear onward.
If the best ask rises by Gap MetaTrader pips above the reference, submit a market sell order. If it drops by Gap pips,
submit a market buy order. Protective stop-loss and take-profit levels are attached automatically by calling
SetStopLoss / SetTakeProfit with the same pip distances as the expert advisor.
Once a basket direction is established, the strategy keeps a FIFO list of the synthetic positions to emulate the hedging
style of MetaTrader. As long as the current basket size is below MaxTrades, averaging orders are added when the best ask
returns within ReEntryGap pips of the latest entry price.
Money management can either use the fixed Lots parameter or dynamically allocate volume according to the EA rule
floor(Equity / 20000), capped by MaxLots. All volumes are normalized against the security's volume step/min/max.
Out-of-hours updates reset the internal entry anchors to avoid spurious trades when the next session starts.
Parameters
Name
Description
Lots
Fixed order volume when money management is disabled.
StopLoss
Protective stop distance in MetaTrader pips. Set to 0 to disable the stop.
TakeProfit
Profit target distance in MetaTrader pips. Set to 0 to disable the target.
Gap
Distance in MetaTrader pips the ask must move away from the reference before opening the first trade.
MaxTrades
Maximum number of simultaneously open trades (basket size).
ReEntryGap
Distance in MetaTrader pips that triggers averaging orders in the basket direction.
MoneyManagement
Enables the dynamic volume rule floor(Equity / 20000) when set to true.
MaxLots
Upper bound for the dynamically calculated volume.
StartHour / EndHour
Trading window in exchange server time (inclusive).
StartYear
Calendar year from which trading is permitted. Earlier data is ignored.
Notes
The strategy only consumes Level1 data and does not request candles. It is therefore lightweight and reacts immediately to
quote changes, just like the MT4 start() tick handler.
Stops and targets rely on the helper methods from Strategy to translate MetaTrader pip distances into broker-specific
price levels. Ensure the connected venue exposes correct PriceStep, StepPrice, and VolumeStep values.
The synthetic basket counter allows the strategy to mimic hedging accounts even though StockSharp aggregates the position.
Partial fills and stop hits are handled via the OnPositionChanged callback that maintains the FIFO queues.
Python implementation is intentionally omitted according to the repository guidelines.
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()