GitHub で見る

Zone Recovery Formula Strategy

Overview

The Zone Recovery Formula Strategy is a port of the MetaTrader 4 "Zone Recovery Formula" expert advisor. The algorithm follows a moving-average driven trend direction and then applies a zone recovery technique to mitigate adverse price moves. The core idea is to alternate long and short cycles with gradually increasing volume until price action exits the defined recovery zone, locking in profit even after several reversals.

How It Works

  1. Signal Detection – The strategy subscribes to timeframe candles (15 minutes by default) and tracks a fast and a slow simple moving average. A bullish crossover starts a long recovery cycle, while a bearish crossover starts a short cycle.
  2. Initial Order – When a new cycle starts the strategy opens a market position with the base volume multiplier. The take-profit and recovery distances are calculated from pip settings and the instrument tick size.
  3. Zone Recovery – If price moves against the open position by the configured recovery distance, the strategy reverses the direction and increases the order size using the original formula sequence (up to the maximum number of trades). This creates an alternating net exposure that aims to cover previous losses once price returns to the profit target.
  4. Profit Management – The algorithm monitors unrealized profit:
    • Monetary and percentage take-profit conditions can close all positions immediately.
    • Optional trailing management captures profits after a predefined gain and protects them with a trailing stop distance.
  5. Cycle Reset – When profit targets are achieved or trailing protection closes the position, the recovery cycle is reset and the strategy waits for the next moving-average signal.

Key Parameters

  • Use TP Money / TP Money – Enable and configure monetary take-profit.
  • Use TP % / TP Percent – Enable and configure percentage take-profit based on the portfolio balance.
  • Enable Trailing / Trailing TP / Trailing SL – Activate trailing profit capture and define the activation level together with the protective distance.
  • TP Pips / Zone Pips – Distances (in pips) that define the take-profit objective and the recovery trigger zone.
  • Base Volume / Max Trades – Initial order size and the number of recovery steps allowed in a cycle.
  • Fast MA / Slow MA – Moving averages that generate entry signals.
  • Profit Offset – Optional adjustment used in the original recovery volume formula.

Notes

  • The strategy uses the high-level StockSharp API with candle subscriptions and indicator binding.
  • Hedging positions are emulated by flipping the net position direction and scaling volume, which keeps the logic compatible with StockSharp's net position accounting.
  • Trailing and take-profit checks rely on unrealized profit calculated from the current position price. Adjust the monetary values to match the instrument's tick value.
  • Always test on a simulated environment before deploying to a live account.

Files

  • CS/ZoneRecoveryFormulaStrategy.cs – C# implementation of the strategy.
  • README.md – This documentation file in English.
  • README_ru.md – Documentation in Russian.
  • README_zh.md – Documentation in Chinese.
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 ZoneRecoveryFormulaStrategy : 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 ZoneRecoveryFormulaStrategy()
	{
		_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;
	}
}