Ver en GitHub

Hedging Martingale Strategy

Overview

This strategy is a StockSharp port of the MetaTrader expert advisor "Hedging Martingale" (folder MQL/23693). It keeps a balanced hedge by opening both a long and a short position on every new bar and then applies a martingale averaging scheme. When price moves adversely by a configurable pip distance, the strategy adds a new position on the losing side with an increased volume while keeping the opposite hedge in place. Floating profit is managed using money- and percent-based targets together with an optional trailing lock.

Trading Logic

  • Initial hedge: whenever the strategy is flat and a new candle closes, it simultaneously buys and sells using the same base volume.
  • Martingale steps: if price moves against one side by Pip Step pips, an additional order is opened on that side. The volume is multiplied by Volume Multiplier, emulating the progressive lot sizing from the MQL version. The opposite side remains open to maintain the hedge.
  • Per-trade take profit: every open entry has an individual take-profit distance defined by Take Profit (pips). When the market moves in favor of a leg by that distance, the leg is reduced by issuing an offsetting order.
  • Basket exits: the entire set of positions can be closed when floating profit reaches a money target, a percentage of the starting equity, or after a trailing lock gives back more than the allowed retracement. These behaviours replicate Take_Profit_In_Money, Take_Profit_In_percent, and TRAIL_PROFIT_IN_MONEY2 from the original expert.
  • Trade limits: the Max Trades parameter restricts how many martingale steps can be active. If Close On Max is enabled, the basket is liquidated once the limit is exceeded.

Parameters

Name Description
Candle Type Timeframe that drives the logic. Each finished candle can trigger new hedging actions.
Use Money TP / Money Take Profit Enable and define the floating profit (in currency units) that closes all positions.
Use Percent TP / Percent Take Profit Close the basket when the floating profit reaches a percentage of the starting portfolio value.
Enable Trailing / Trailing Start / Trailing Step Activate the money-based trailing lock for the basket and configure the trigger level together with the permitted profit give-back.
Take Profit (pips) Distance in pips for per-leg take-profit exits.
Pip Step Adverse price movement (in pips) required before adding another martingale order.
Base Volume Initial volume for both the buy and sell legs.
Volume Multiplier Multiplier applied to the largest position volume when adding martingale entries.
Max Trades Maximum number of simultaneously open entries (across both directions).
Close On Max Whether to liquidate all positions once the maximum trade count is breached.

Notes

  • The strategy uses BuyMarket and SellMarket for all order placements, mirroring the market execution model of the source expert.
  • Volume values are normalized to the instrument's lot step to avoid rejected orders.
  • When the strategy becomes flat, the trailing lock is reset so that new baskets start with a clean profit reference.

Files

  • CS/HedgingMartingaleStrategy.cs – implementation of the converted strategy (C#).
  • README.md – this documentation (English).
  • README_zh.md – Chinese translation.
  • README_ru.md – Russian translation.
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;

public class HedgingMartingaleStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public HedgingMartingaleStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}