Ver no GitHub

Dynamic Averaging Strategy

Overview

Dynamic Averaging is a direct port of the MetaTrader 5 expert advisor "Dynamic averaging.mq5" (id 23319). The strategy combines a fast stochastic oscillator with a volatility filter based on standard deviation. Trades are only allowed while the market volatility remains below its rolling average, forcing entries to occur during consolidations where stochastic reversals are more reliable.

Parameters

  • TradeVolume – order size for every new entry. It is automatically doubled after a losing sequence and reset after a profitable one.
  • MinimumProfit – floating profit (in account currency) that closes all open positions once exceeded.
  • SlidingWindowDays – number of calendar days used to average the standard deviation values and build the volatility baseline.
  • StochasticKPeriod – number of bars for the %K calculation.
  • StochasticDPeriod – smoothing length for the %D line.
  • StochasticSlowPeriod – final slowing period for the stochastic oscillator.
  • StdDevPeriod – lookback period for the standard deviation indicator.
  • CandleType – source candles for calculations (defaults to 15-minute time frame).

Trading Rules

  1. The strategy operates on finished candles only. At the close of each bar the stochastic and volatility filters are updated via SubscribeCandles().BindEx.
  2. Calculate the market volatility using StandardDeviation(StdDevPeriod) and compare it with the average volatility computed by SimpleMovingAverage over the last SlidingWindowDays worth of bars.
  3. If the current standard deviation is above the rolling average, the bar is skipped.
  4. When volatility is muted:
    • Enter long if %K is below 25 and the slope of the previous two %K values is positive (last value minus the value two bars ago).
    • Enter short if %K is above 75 and the slope of the previous two %K values is negative.
  5. Positions are reversed by sending enough volume to flatten the opposite side plus the new TradeVolume exposure.
  6. Whenever the floating PnL of the open position exceeds MinimumProfit, the strategy immediately exits the market.

Position Sizing and Recovery

  • The initial order size equals TradeVolume.
  • After the position is closed, the realized PnL change is inspected.
    • A loss doubles the next trade size (martingale step) to replicate the original EA behaviour.
    • A profit or breakeven resets the size back to the base TradeVolume.

Implementation Details

  • Candles, stochastic and standard deviation values are processed through the high-level API with BindEx, avoiding manual buffer management.
  • The sliding volatility window converts calendar days into bar counts by using the candle time frame if available.
  • Floating profit control relies on the current candle close and PositionAvgPrice, matching the MQL implementation that sums open-position profit only.
  • All code comments are written in English; no Python version is provided per task requirements.
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 DynamicAveragingStrategy : 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 DynamicAveragingStrategy()
	{
		_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;
	}
}