View on GitHub

Well Martin Strategy

Overview

The Well Martin strategy is a mean reversion system that combines Bollinger Bands and the Average Directional Index (ADX). It enters long positions when price breaks below the lower Bollinger Band during low trend strength, and enters short positions when price breaks above the upper Bollinger Band under the same conditions. Positions are closed when price reaches the opposite band or hits the configured take profit or stop loss levels.

Parameters

  • CandleType – candle series used for calculations.
  • BollingerPeriod – period for Bollinger Bands.
  • BollingerWidth – standard deviation multiplier for Bollinger Bands.
  • AdxPeriod – period for the ADX indicator.
  • AdxLevel – ADX threshold; trades are taken only when the ADX value is below this level.
  • Volume – trade volume for each entry.
  • TakeProfit – profit target in price units.
  • StopLoss – loss limit in price units.

Logic

  1. Subscribe to candle data and calculate Bollinger Bands and ADX.
  2. When there is no open position:
    • Buy if the close price is below the lower band and ADX is below the threshold.
    • Sell if the close price is above the upper band and ADX is below the threshold.
  3. Track the last executed trade side and allow entries only in the same direction or when no trades have been made.
  4. When in a long position:
    • Exit if price touches the upper band, reaches the take profit, or hits the stop loss.
  5. When in a short position:
    • Exit if price touches the lower band, reaches the take profit, or hits the stop loss.

Notes

This implementation uses a fixed trade volume. The original MQL version increased volume after a losing trade; this behaviour can be added later if required.

using System;
using System.Collections.Generic;

using Ecng.Common;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Well Martin mean reversion strategy using Bollinger Bands.
/// Buys at lower band, sells at upper band.
/// </summary>
public class WellMartinStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _bollingerPeriod;
	private readonly StrategyParam<decimal> _bollingerWidth;
	private readonly StrategyParam<decimal> _takeProfit;
	private readonly StrategyParam<decimal> _stopLoss;

	private decimal _entryPrice;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int BollingerPeriod { get => _bollingerPeriod.Value; set => _bollingerPeriod.Value = value; }
	public decimal BollingerWidth { get => _bollingerWidth.Value; set => _bollingerWidth.Value = value; }
	public decimal TakeProfit { get => _takeProfit.Value; set => _takeProfit.Value = value; }
	public decimal StopLoss { get => _stopLoss.Value; set => _stopLoss.Value = value; }

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

		_bollingerPeriod = Param(nameof(BollingerPeriod), 84)
			.SetDisplay("Bollinger Period", "Bollinger Bands period", "Indicators");

		_bollingerWidth = Param(nameof(BollingerWidth), 1.8m)
			.SetDisplay("Bollinger Width", "Bollinger Bands width", "Indicators");

		_takeProfit = Param(nameof(TakeProfit), 1200m)
			.SetDisplay("Take Profit", "Take profit in price units", "Risk");

		_stopLoss = Param(nameof(StopLoss), 1400m)
			.SetDisplay("Stop Loss", "Stop loss in price units", "Risk");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
		=> [(Security, CandleType)];

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_entryPrice = 0;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		var bb = new BollingerBands
		{
			Length = BollingerPeriod,
			Width = BollingerWidth
		};

		SubscribeCandles(CandleType)
			.BindEx(bb, ProcessCandle)
			.Start();
	}

	private void ProcessCandle(ICandleMessage candle, IIndicatorValue bbValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		var bb = (IBollingerBandsValue)bbValue;
		if (bb.UpBand is not decimal upper || bb.LowBand is not decimal lower)
			return;

		var close = candle.ClosePrice;

		// Exit management
		if (Position > 0)
		{
			var profit = close - _entryPrice;
			if (close >= upper || (TakeProfit > 0 && profit >= TakeProfit) || (StopLoss > 0 && -profit >= StopLoss))
			{
				SellMarket();
				return;
			}
		}
		else if (Position < 0)
		{
			var profit = _entryPrice - close;
			if (close <= lower || (TakeProfit > 0 && profit >= TakeProfit) || (StopLoss > 0 && -profit >= StopLoss))
			{
				BuyMarket();
				return;
			}
		}

		if (Position != 0)
			return;

		// Mean reversion: buy at lower band, sell at upper band
		if (close < lower)
		{
			BuyMarket();
			_entryPrice = close;
		}
		else if (close > upper)
		{
			SellMarket();
			_entryPrice = close;
		}
	}
}