View on GitHub

Martingail Expert Strategy

Overview

Martingail Expert is a trend-following martingale strategy that relies on the Stochastic Oscillator to time new sequences of trades. Once the indicator generates a direction, the strategy starts a ladder of market orders and manages the exposure using a dynamic profit target and a geometric position sizing scheme.

Trading Logic

  • Calculate a Stochastic Oscillator on the configured candle series. The most recent final values of %K and %D are cached for decision making.
  • Start a new long sequence when %K (previous) > %D (previous) and %D (previous) is above the BuyLevel threshold.
  • Start a new short sequence when %K (previous) < %D (previous) and %D (previous) is below the SellLevel threshold.
  • After entering a sequence, every favorable price move equal to ProfitFactor × openOrders pips adds a new position with the base volume.
  • Every adverse move of StepPoints pips multiplies the last filled volume by Multiplier and sends an averaging order in the same direction.

Exit Rules

  • Close the entire position as soon as the last fill price reaches a dynamic profit target at ProfitFactor × openOrders pips in the favorable direction.
  • Reset the martingale state whenever the aggregated position size returns to zero.

Risk Management

The martingale progression increases exposure quickly when price moves against the position. Adjust Multiplier, StepPoints, and ProfitFactor carefully to match the account size and instrument volatility.

Parameters

Name Description
Volume Base market order volume used for the first trade and every favorable add-on.
Multiplier Factor applied to the last executed volume when averaging during adverse moves.
StepPoints Distance in points that triggers a martingale averaging order.
ProfitFactor Profit target per open order expressed in points. The actual distance is ProfitFactor × number_of_orders.
KPeriod Lookback length for the %K line.
DPeriod Smoothing length for the %D line.
Slowing Additional smoothing applied to %K before comparing with %D.
BuyLevel Minimum %D value required to allow a new long sequence.
SellLevel Maximum %D value required to allow a new short sequence.
CandleType Candle series used for calculations (default: 5-minute timeframe).

Notes

  • Works best on liquid FX pairs where pip size and volume step allow granular scaling.
  • Requires sufficient margin to withstand several martingale steps.
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>
/// Martingale strategy driven by stochastic oscillator crossovers.
/// Buy when K crosses above D, sell when K crosses below D.
/// Doubles down on adverse moves with martingale averaging.
/// </summary>
public class MartingailExpertSequenceStrategy : Strategy
{
	private readonly StrategyParam<int> _kPeriod;
	private readonly StrategyParam<int> _dPeriod;
	private readonly StrategyParam<decimal> _stepPoints;
	private readonly StrategyParam<decimal> _takeProfitPoints;
	private readonly StrategyParam<DataType> _candleType;

	private decimal? _prevK;
	private decimal? _prevD;
	private decimal _entryPrice;

	public int KPeriod { get => _kPeriod.Value; set => _kPeriod.Value = value; }
	public int DPeriod { get => _dPeriod.Value; set => _dPeriod.Value = value; }
	public decimal StepPoints { get => _stepPoints.Value; set => _stepPoints.Value = value; }
	public decimal TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public MartingailExpertSequenceStrategy()
	{
		_kPeriod = Param(nameof(KPeriod), 14)
			.SetDisplay("K Period", "Stochastic K period", "Indicators");

		_dPeriod = Param(nameof(DPeriod), 3)
			.SetDisplay("D Period", "Stochastic D period", "Indicators");

		_stepPoints = Param(nameof(StepPoints), 500m)
			.SetDisplay("Step Points", "Distance for martingale averaging", "Risk");

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 200m)
			.SetDisplay("Take Profit", "Take profit in price steps", "Risk");

		_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();

		_prevK = null;
		_prevD = null;
		_entryPrice = 0m;
	}

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

		_prevK = null;
		_prevD = null;

		var stoch = new StochasticOscillator
		{
			K = { Length = KPeriod },
			D = { Length = DPeriod }
		};

		var subscription = SubscribeCandles(CandleType);
		subscription
			.BindEx(stoch, ProcessCandle)
			.Start();
	}

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

		if (!stochValue.IsFinal)
			return;

		if (stochValue is not StochasticOscillatorValue stoch)
			return;

		if (stoch.K is not decimal k || stoch.D is not decimal d)
			return;

		var step = Security?.PriceStep ?? 1m;
		if (step <= 0) step = 1m;

		// Check take profit
		if (Position > 0 && candle.ClosePrice >= _entryPrice + TakeProfitPoints * step)
		{
			SellMarket();
			_prevK = k;
			_prevD = d;
			return;
		}
		else if (Position < 0 && candle.ClosePrice <= _entryPrice - TakeProfitPoints * step)
		{
			BuyMarket();
			_prevK = k;
			_prevD = d;
			return;
		}

		if (_prevK is not decimal prevK || _prevD is not decimal prevD)
		{
			_prevK = k;
			_prevD = d;
			return;
		}

		// Stochastic crossover signals
		var bullCross = prevK <= prevD && k > d;
		var bearCross = prevK >= prevD && k < d;

		if (Position == 0)
		{
			if (bullCross)
			{
				BuyMarket();
				_entryPrice = candle.ClosePrice;
			}
			else if (bearCross)
			{
				SellMarket();
				_entryPrice = candle.ClosePrice;
			}
		}
		else if (Position > 0 && bearCross)
		{
			SellMarket(); // Close long
			SellMarket(); // Open short
			_entryPrice = candle.ClosePrice;
		}
		else if (Position < 0 && bullCross)
		{
			BuyMarket(); // Close short
			BuyMarket(); // Open long
			_entryPrice = candle.ClosePrice;
		}

		_prevK = k;
		_prevD = d;
	}
}