GitHub で見る

Pedro Mod Strategy

Overview

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

  1. Subscribe to Level1 best bid/ask quotes and cache the most recent values.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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();
		}
	}
}