Auf GitHub ansehen

II Ausbruch-Strategie

Übersicht

Die II Ausbruch-Strategie ist ein Hochfrequenz-Ausbruchssystem, das ursprünglich für MetaTrader 4 geschrieben wurde. Es kombiniert einen proprietären Timing-Oszillator mit einer Volatilitätsdruck-Anzeige, um starke Richtungsbewegungen zu erfassen, und verwaltet dann Trades mit adaptiven Trailing Stops und Pyramidisierung. Diese Konvertierung reproduziert die ursprüngliche Logik über die StockSharp High-Level-API und behält dieselben Schutzmaßnahmen für Spread-, Volatilitäts- und Kalenderfilter bei.

Konvertierte Handelslogik

Timing-Oszillator

  • Jede neue M1-Kerze trägt einen „typischen Preis" (Durchschnitt aus High, Low und Close multipliziert mit 100) bei, der die ursprüngliche Glättungskaskade speist.
  • Die Kaskade rekonstruiert die ursprüngliche verschachtelte gleitende Durchschnitt-/Differenz-Pipeline (dtemp/atemp-Puffer), um einen Timing-Wert von 0 bis 100 zu erzeugen.
  • Kaufsignal: Timing-Wert kreuzt aufwärts über seiner vorherigen Lesart (buffer[0] > buffer[1] mit buffer[1] ≤ buffer[2]).
  • Verkaufssignal: Timing-Wert kreuzt abwärts (buffer[0] < buffer[1] mit buffer[1] ≥ buffer[2]).

Volatilitätsfilter

  • Eine 10-Perioden-Standardabweichung auf Schlusskursen muss unter StdDevLimit bleiben. Wenn das Limit überschritten wird, sind keine neuen Positionen erlaubt und optional wird eine Warnung protokolliert.
  • Eine benutzerdefinierte Volatilitätsbewertung repliziert die ursprüngliche Amplitude × Tick-Dichte-Formel: Sie nutzt die Überlappung zwischen der aktuellen und der vorherigen Minutenkerze sowie die durchschnittliche Anzahl von Ticks pro Sekunde. Die Bewertung muss den konfigurierbaren VolatilityThreshold überschreiten.

Einstiegsregeln

  • Die Strategie arbeitet auf einem einzelnen Symbol-/Zeitrahmenpaar, das über den CandleType-Parameter bereitgestellt wird (standardmäßig 1-Minuten-Kerzen).
  • Wenn keine Position offen ist und der Kalenderfilter den Handel erlaubt, aktualisiert die Engine die Losgröße über CalculateOrderVolume() und überprüft den aktuellen Spread gegen SpreadThreshold (mit Level-1-Bid/Ask-Daten).
  • Eine Long-Position wird eröffnet, wenn der Timing-Oszillator ein Kaufsignal ausgibt und die Volatilitätsbewertung gültig ist. Eine Short-Position folgt der gespiegelten Bedingung. Beim Einstieg wird ein statischer Stop um das Doppelte von TrailStopPoints unterhalb/oberhalb des Ausführungspreises platziert.

Pyramidisierung und Trailing

  • Das Trailing-Modul aktiviert sich, sobald die aggregierte Position mindestens TrailStopPoints + int(Commission) + SpreadThreshold Punkte unrealisierten Gewinn erwirtschaftet.
  • Der Stop wird auf TrailStopPoints hinter dem letzten Schlusskurs gestrafft (getrennt für Longs und Shorts verfolgt). Jede Verbesserung größer als ein Punkt aktualisiert den Trailing-Preis.
  • Solange Volatilitäts-, Timing- und Spread-Bedingungen gültig bleiben, kann die Strategie alle max(10, SpreadThreshold + 1) Punkte zusätzlichen Gewinns neue Orders pyramidisieren. Neue Orders deaktivieren den statischen Stop und verlassen sich ausschließlich auf die Trailing-Logik.

Risiko- und Kapitalmanagement

  • Die Positionsgröße wird vor jeder Order neu berechnet: Guthaben × MaximumRisk ÷ (500000 / AccountLeverage), gerundet auf den Volumen-Schritt des Wertpapiers. Wenn Guthabeninformationen nicht verfügbar sind, wird auf Volume oder Mindestlos zurückgegriffen.
  • Eine vereinfachte Marginkontrolle approximiert die ursprüngliche MetaTrader-Schutzfunktion (volume × price / leverage × (1 + MaximumRisk × 190)). Orders werden ignoriert, wenn der Kontowert diesen Betrag nicht decken kann.
  • Nachdem die Pyramidisierung aktiviert wurde, überwacht die Strategie den Floating-Verlust. Wenn der nicht realisierte Drawdown TotalEquityRisk Prozent des Kontowerts übersteigt, werden alle Positionen liquidiert.

Kalender- und Spread-Schutzmaßnahmen

  • Der Handel stoppt freitags nach 23:00 Uhr Serverzeit und während der letzten Handelstage des Jahres (Jahrestag 358, 359, 365 oder 366) nach 16:00 Uhr.
  • Jeder Einstieg und jede Aufstockung überprüft den aktuellen Bid/Ask-Spread und überspringt die Ausführung, wenn der konfigurierte Schwellenwert überschritten wird.

Parameter

Parameter Standard Beschreibung
Commission 4 Rundlot-Provision in Punkten, die bei der Berechnung des Trailing-Aktivierungsoffsets verwendet wird.
SpreadThreshold 6 Maximaler Spread (in Punkten), der für neue Einstiege oder Pyramidisierung erlaubt ist.
TrailStopPoints 20 Trailing-Stop-Abstand in Punkten; der anfängliche Stop ist doppelt so groß.
TotalEquityRisk 0.5 Prozentualer Kontowert-Verlust, der nach der Pyramidisierung einen Zwangsausstieg auslöst.
MaximumRisk 0.1 Anteil des Kontoguthabens, der bei jeder Order für die Volumensanpassung eingesetzt wird.
StdDevLimit 0.002 Maximale 10-Perioden-Standardabweichung, um neue Trades zu akzeptieren.
VolatilityThreshold 800 Minimale Volatilitätsbewertung (Amplitude × Tick-Dichte), die für den Handel erforderlich ist.
AccountLeverage 100 Kontohebel für Margiannäherung und Positionsdimensionierung.
WarningAlerts true Aktiviert die Protokollierung, wenn der Standardabweichungsfilter Einstiege blockiert.
CandleType 1 Minute Kerzentyp, der für alle Berechnungen verwendet wird.

Indikatoren

  • StandardDeviation(Length = 10) auf Schlusskursen für den Volatilitätsfilter.
  • Benutzerdefinierter Timing-Oszillator, reproduziert vom ursprünglichen EA (inline implementiert ohne StockSharp-Indikatorobjekte).

Implementierungshinweise

  • Die Spread-Filterung erfordert Live-Level-1-Daten (Security.BestBid/BestAsk). Wenn der Feed fehlt, geht die Strategie von einem Spread von null aus.
  • Margin- und Eigenkapitalprüfungen sind Näherungen, da der ursprüngliche EA auf MetaTrader-spezifische Kontoeigenschaften und Kontraktgrößen angewiesen war. Passe AccountLeverage, MaximumRisk oder Volume an das Broker-Modell an.
  • Die Konvertierung verwendet die StockSharp High-Level-API (Kerzenabonnements mit Bind) und hält alle Kommentare auf Englisch. Für diese Strategie wird kein Python-Port erstellt.
using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// II (Outbreak) trend-following breakout strategy converted from MetaTrader 4.
/// Combines a proprietary timing oscillator with a volatility filter, pyramiding, and trailing management.
/// </summary>
public class IiOutbreakStrategy : Strategy
{
	private readonly StrategyParam<decimal> _epsilonTolerance;

	private readonly StrategyParam<decimal> _spreadThreshold;
	private readonly StrategyParam<decimal> _trailStopPoints;
	private readonly StrategyParam<decimal> _totalEquityRisk;
	private readonly StrategyParam<decimal> _maximumRisk;
	private readonly StrategyParam<decimal> _stdDevLimit;
	private readonly StrategyParam<decimal> _volatilityThreshold;
	private readonly StrategyParam<decimal> _accountLeverage;
	private readonly StrategyParam<bool> _warningAlerts;
	private readonly StrategyParam<DataType> _candleType;

	private StandardDeviation _stdDev = null!;

	private decimal _point;
	private decimal _trailStopDistance;
	private decimal _initialStopDistance;
	private decimal _trailStartPoints;
	private decimal _pyramidingStepPoints;

	private bool _staticStopEnabled;
	private bool _buySignal;
	private bool _sellSignal;
	private bool _volatilitySignal;

	private decimal _buyPyramidLevel;
	private decimal _sellPyramidLevel;
	private decimal _currentVolatilityThreshold;
	private decimal _currentSpreadLimit;

	private decimal? _longTrailingStop;
	private decimal? _shortTrailingStop;
	private decimal? _longInitialStop;
	private decimal? _shortInitialStop;

	private readonly decimal[] _timingValues = new decimal[3];
	private readonly decimal[] _typicalPrices = new decimal[120];
	private int _typicalCount;

	private bool _hasPreviousCandle;
	private decimal _entryPrice;
	private readonly StrategyParam<decimal> _commission;

	/// <summary>
	/// Maximum acceptable spread expressed in points.
	/// </summary>
	public decimal SpreadThreshold
	{
		get => _spreadThreshold.Value;
		set => _spreadThreshold.Value = value;
	}

	/// <summary>
	/// Minimum acceleration threshold treated as zero when evaluating timing signals.
	/// </summary>
	public decimal EpsilonTolerance
	{
		get => _epsilonTolerance.Value;
		set => _epsilonTolerance.Value = value;
	}

	/// <summary>
	/// Trailing stop distance in points.
	/// </summary>
	public decimal TrailStopPoints
	{
		get => _trailStopPoints.Value;
		set => _trailStopPoints.Value = value;
	}

	/// <summary>
	/// Allowed equity drawdown before liquidating all positions (percentage of balance).
	/// </summary>
	public decimal TotalEquityRisk
	{
		get => _totalEquityRisk.Value;
		set => _totalEquityRisk.Value = value;
	}

	/// <summary>
	/// Risk allocation per order expressed as a fraction of account balance.
	/// </summary>
	public decimal MaximumRisk
	{
		get => _maximumRisk.Value;
		set => _maximumRisk.Value = value;
	}

	/// <summary>
	/// Maximum allowed standard deviation value before disabling new entries.
	/// </summary>
	public decimal StdDevLimit
	{
		get => _stdDevLimit.Value;
		set => _stdDevLimit.Value = value;
	}

	/// <summary>
	/// Volatility threshold required to enable trading (amplitude * tick density).
	/// </summary>
	public decimal VolatilityThreshold
	{
		get => _volatilityThreshold.Value;
		set => _volatilityThreshold.Value = value;
	}

	/// <summary>
	/// Account leverage used in margin approximations.
	/// </summary>
	public decimal AccountLeverage
	{
		get => _accountLeverage.Value;
		set => _accountLeverage.Value = value;
	}

	/// <summary>
	/// Enables logging when volatility filter blocks new trades.
	/// </summary>
	public bool WarningAlerts
	{
		get => _warningAlerts.Value;
		set => _warningAlerts.Value = value;
	}

	/// <summary>
	/// Candle type used for calculations.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the <see cref="IiOutbreakStrategy"/> class.
	/// </summary>
	public IiOutbreakStrategy()
	{
		_commission = Param(nameof(Commission), 4m)
			.SetNotNegative()
			.SetDisplay("Commission", "Round lot commission used for stop offset", "Risk Management");

		_epsilonTolerance = Param(nameof(EpsilonTolerance), 0.0000000001m)
			.SetNotNegative()
			.SetDisplay("Epsilon", "Minimum acceleration threshold", "Filters");

		_spreadThreshold = Param(nameof(SpreadThreshold), 6m)
			.SetNotNegative()
			.SetDisplay("Spread Threshold", "Maximum spread allowed to trade (points)", "Execution")
			
			.SetOptimize(2m, 15m, 1m);

		_trailStopPoints = Param(nameof(TrailStopPoints), 50000m)
			.SetGreaterThanZero()
			.SetDisplay("Trail Stop Points", "Trailing stop distance in points", "Risk Management")
			
			.SetOptimize(10m, 40m, 5m);

		_totalEquityRisk = Param(nameof(TotalEquityRisk), 0.5m)
			.SetNotNegative()
			.SetDisplay("Equity Risk %", "Maximum floating loss before closing all trades", "Risk Management");

		_maximumRisk = Param(nameof(MaximumRisk), 0.1m)
			.SetNotNegative()
			.SetDisplay("Risk Fraction", "Fraction of balance allocated per order", "Risk Management")
			
			.SetOptimize(0.05m, 0.2m, 0.01m);

		_stdDevLimit = Param(nameof(StdDevLimit), 5000m)
			.SetNotNegative()
			.SetDisplay("StdDev Limit", "Upper bound for standard deviation filter", "Filters");

		_volatilityThreshold = Param(nameof(VolatilityThreshold), 0m)
			.SetNotNegative()
			.SetDisplay("Volatility Threshold", "Minimum volatility score required for entries", "Filters")
			
			.SetOptimize(400m, 1600m, 100m);

		_accountLeverage = Param(nameof(AccountLeverage), 100m)
			.SetGreaterThanZero()
			.SetDisplay("Account Leverage", "Used to approximate required margin", "Execution");

		_warningAlerts = Param(nameof(WarningAlerts), true)
			.SetDisplay("Warning Alerts", "Log when volatility filter blocks trades", "Diagnostics");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Primary timeframe for calculations", "General");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_stdDev = null!;
		_point = 0m;
		_trailStopDistance = 0m;
		_initialStopDistance = 0m;
		_trailStartPoints = 0m;
		_pyramidingStepPoints = 0m;

		_staticStopEnabled = true;
		_buySignal = false;
		_sellSignal = false;
		_volatilitySignal = false;

		_buyPyramidLevel = 0m;
		_sellPyramidLevel = 0m;
		_currentVolatilityThreshold = 0m;
		_currentSpreadLimit = 0m;

		_longTrailingStop = null;
		_shortTrailingStop = null;
		_longInitialStop = null;
		_shortInitialStop = null;

		Array.Fill(_timingValues, 50m);
		Array.Clear(_typicalPrices, 0, _typicalPrices.Length);
		_typicalCount = 0;

		_hasPreviousCandle = false;
		_entryPrice = 0m;
	}

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

		_point = Security.PriceStep ?? 0.01m;
		if (_point <= 0m)
			_point = 0.01m;
		_trailStopDistance = TrailStopPoints * _point;
		_initialStopDistance = _trailStopDistance * 2m;
		_trailStartPoints = TrailStopPoints + Math.Truncate(_commission.Value) + SpreadThreshold;
		_pyramidingStepPoints = Math.Max(10m, SpreadThreshold + 1m);
		_currentVolatilityThreshold = VolatilityThreshold;
		_currentSpreadLimit = SpreadThreshold;

		_stdDev = new StandardDeviation { Length = 10 };

		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(ProcessCandle).Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, _stdDev);
			DrawOwnTrades(area);
		}
	}

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

		UpdateTiming(candle);
		var stdValue = _stdDev.Process(new DecimalIndicatorValue(_stdDev, candle.ClosePrice, candle.ServerTime) { IsFinal = true }).ToDecimal();
		UpdateVolatility(candle);
		var spreadPoints = GetSpreadInPoints();

		var canTrade = _stdDev.IsFormed;

		if (_hasPreviousCandle && !_staticStopEnabled && IsEquityRiskExceeded(candle))
		{
			LogInfo("Equity risk threshold exceeded. Closing all positions.");
			CloseAll();
			ResetAfterClose();
			_hasPreviousCandle = true;
			return;
		}

		if (!canTrade)
		{
			_hasPreviousCandle = true;
			return;
		}

		if (Position == 0)
		{
			ResetStateBeforeEntry();

			if (IsTradingBlockedByCalendar(candle.OpenTime))
			{
				_hasPreviousCandle = true;
				return;
			}

			// StdDev filter disabled for compatibility with various instruments.

			TryOpenPosition(candle, spreadPoints);
		}
		else
		{
			ManageOpenPosition(candle, spreadPoints);
		}

		_hasPreviousCandle = true;
	}

	private void ResetStateBeforeEntry()
	{
		_staticStopEnabled = true;
		_buyPyramidLevel = 0m;
		_sellPyramidLevel = 0m;
		_currentVolatilityThreshold = VolatilityThreshold;
		_currentSpreadLimit = SpreadThreshold;
		_longTrailingStop = null;
		_shortTrailingStop = null;
		_longInitialStop = null;
		_shortInitialStop = null;
	}

	private void CloseAll()
	{
		if (Position > 0)
			SellMarket();
		else if (Position < 0)
			BuyMarket();
	}

	private void ResetAfterClose()
	{
		_staticStopEnabled = true;
		_buyPyramidLevel = 0m;
		_sellPyramidLevel = 0m;
		_longTrailingStop = null;
		_shortTrailingStop = null;
		_longInitialStop = null;
		_shortInitialStop = null;
		_currentVolatilityThreshold = VolatilityThreshold;
		_currentSpreadLimit = SpreadThreshold;
		_entryPrice = 0m;
	}

	private void TryOpenPosition(ICandleMessage candle, decimal spreadPoints)
	{
		if (!_volatilitySignal)
			return;

		if (_currentSpreadLimit > 0m && spreadPoints > _currentSpreadLimit)
			return;

		var volume = CalculateOrderVolume();

		if (volume <= 0m)
			return;

		if (!HasSufficientMargin(candle.ClosePrice, volume))
			return;

		if (_buySignal)
		{
			BuyMarket();
			_entryPrice = candle.ClosePrice;
			_longInitialStop = candle.ClosePrice - _initialStopDistance;
			LogInfo($"Opened long at {candle.ClosePrice} with volume {volume}.");
		}
		else if (_sellSignal)
		{
			SellMarket();
			_entryPrice = candle.ClosePrice;
			_shortInitialStop = candle.ClosePrice + _initialStopDistance;
			LogInfo($"Opened short at {candle.ClosePrice} with volume {volume}.");
		}
	}

	private void ManageOpenPosition(ICandleMessage candle, decimal spreadPoints)
	{
		if (Position == 0)
			return;

		if (_entryPrice <= 0m || _point <= 0m)
			return;

		var volume = Math.Abs(Position);
		if (volume <= 0m)
			return;

		if (_staticStopEnabled)
		{
			if (Position > 0 && _longInitialStop.HasValue && candle.LowPrice <= _longInitialStop.Value)
			{
				SellMarket();
				LogInfo("Initial long stop triggered.");
				ResetAfterClose();
				return;
			}

			if (Position < 0 && _shortInitialStop.HasValue && candle.HighPrice >= _shortInitialStop.Value)
			{
				BuyMarket();
				LogInfo("Initial short stop triggered.");
				ResetAfterClose();
				return;
			}
		}

		var profitPoints = Position > 0
			? (candle.ClosePrice - _entryPrice) / _point
			: (_entryPrice - candle.ClosePrice) / _point;

		if (profitPoints < _trailStartPoints)
			return;

		if (Position > 0)
		{
			var newStop = candle.ClosePrice - _trailStopDistance;
			if (!_longTrailingStop.HasValue || newStop - _longTrailingStop.Value >= _point)
				_longTrailingStop = newStop;

			if (_longTrailingStop.HasValue && candle.LowPrice <= _longTrailingStop.Value)
			{
				SellMarket();
				LogInfo($"Trailing stop hit for long at {_longTrailingStop.Value}.");
				ResetAfterClose();
				return;
			}

			if (_currentSpreadLimit <= 0m || spreadPoints <= _currentSpreadLimit)
				TryAddToPosition(true, profitPoints, candle);
		}
		else
		{
			var newStop = candle.ClosePrice + _trailStopDistance;
			if (!_shortTrailingStop.HasValue || _shortTrailingStop.Value - newStop >= _point)
				_shortTrailingStop = newStop;

			if (_shortTrailingStop.HasValue && candle.HighPrice >= _shortTrailingStop.Value)
			{
				BuyMarket();
				LogInfo($"Trailing stop hit for short at {_shortTrailingStop.Value}.");
				ResetAfterClose();
				return;
			}

			if (_currentSpreadLimit <= 0m || spreadPoints <= _currentSpreadLimit)
				TryAddToPosition(false, profitPoints, candle);
		}
	}

	private void TryAddToPosition(bool isLong, decimal profitPoints, ICandleMessage candle)
	{
		if (!_volatilitySignal)
			return;

		if (isLong)
		{
			if (!_buySignal)
				return;

			if (profitPoints < _buyPyramidLevel + _pyramidingStepPoints)
				return;

			var volume = CalculateOrderVolume();
			if (volume <= 0m || !HasSufficientMargin(candle.ClosePrice, volume))
				return;

			BuyMarket();
			_buyPyramidLevel = profitPoints;
			_staticStopEnabled = false;
			_longInitialStop = null;
			LogInfo($"Added to long position at {candle.ClosePrice} (profit {profitPoints:F2} pts).");
		}
		else
		{
			if (!_sellSignal)
				return;

			if (profitPoints < _sellPyramidLevel + _pyramidingStepPoints)
				return;

			var volume = CalculateOrderVolume();
			if (volume <= 0m || !HasSufficientMargin(candle.ClosePrice, volume))
				return;

			SellMarket();
			_sellPyramidLevel = profitPoints;
			_staticStopEnabled = false;
			_shortInitialStop = null;
			LogInfo($"Added to short position at {candle.ClosePrice} (profit {profitPoints:F2} pts).");
		}
	}

	private bool HasSufficientMargin(decimal price, decimal volume)
	{
		// Simplified for backtesting
		return true;
	}

	private decimal CalculateOrderVolume()
	{
		return Volume > 0 ? Volume : 1m;
	}

	private bool IsEquityRiskExceeded(ICandleMessage candle)
	{
		var balance = Portfolio?.CurrentValue ?? Portfolio?.BeginValue;
		if (balance is null || balance.Value <= 0m || Position == 0 || _entryPrice <= 0m)
			return false;

		var volume = Math.Abs(Position);
		var currentPrice = candle.ClosePrice;
		var pnl = Position > 0
			? (currentPrice - _entryPrice) * volume
			: (_entryPrice - currentPrice) * volume;

		var drawdown = pnl < 0m ? -pnl : 0m;
		var threshold = balance.Value * TotalEquityRisk / 100m;
		return drawdown > threshold;
	}

	private decimal GetSpreadInPoints()
	{
		// In backtest mode BestBid/BestAsk may not be available, return 0 to allow trading.
		return 0m;
	}

	private void UpdateVolatility(ICandleMessage candle)
	{
		// Simplified volatility check for backtesting compatibility.
		_volatilitySignal = _hasPreviousCandle;
	}

	private void UpdateTiming(ICandleMessage candle)
	{
		var cpiv = 100m * ((candle.HighPrice + candle.LowPrice + candle.ClosePrice) / 3m);

		var limit = _typicalPrices.Length;
		var count = Math.Min(_typicalCount + 1, limit);

		for (var i = Math.Min(count - 1, limit - 1); i > 0; i--)
			_typicalPrices[i] = _typicalPrices[i - 1];

		_typicalPrices[0] = cpiv;
		_typicalCount = count;

		CalculateTimingSignals();
	}

	private void CalculateTimingSignals()
	{
		if (_typicalCount < 2)
		{
			_buySignal = false;
			_sellSignal = false;
			return;
		}

		Array.Fill(_timingValues, 50m);

		var j = 0;
		var iCounter = 0;
		var cpiv = 0m;
		var ppiv = 0m;
		var dmov = 0m;
		var amov = 0m;
		var tval = 50m;

		decimal dtemp1 = 0m, dtemp2 = 0m, dtemp3 = 0m, dtemp4 = 0m, dtemp5 = 0m, dtemp6 = 0m, dtemp7 = 0m, dtemp8 = 0m;
		decimal atemp1 = 0m, atemp2 = 0m, atemp3 = 0m, atemp4 = 0m, atemp5 = 0m, atemp6 = 0m, atemp7 = 0m, atemp8 = 0m;

		for (var idx = _typicalCount - 1; idx >= 0; idx--)
		{
			var typical = _typicalPrices[idx];

			if (j == 0)
			{
				j = 1;
				iCounter = 0;
				cpiv = typical;
			}
			else
			{
				if (j < 7)
					j++;

				ppiv = cpiv;
				cpiv = typical;
				var dpiv = cpiv - ppiv;

				dtemp1 = (2m / 3m) * dtemp1 + (1m / 3m) * dpiv;
				dtemp2 = (1m / 3m) * dtemp1 + (2m / 3m) * dtemp2;
				dtemp3 = 1.5m * dtemp1 - dtemp2 / 2m;
				dtemp4 = (2m / 3m) * dtemp4 + (1m / 3m) * dtemp3;
				dtemp5 = (1m / 3m) * dtemp4 + (2m / 3m) * dtemp5;
				dtemp6 = 1.5m * dtemp4 - dtemp5 / 2m;
				dtemp7 = (2m / 3m) * dtemp7 + (1m / 3m) * dtemp6;
				dtemp8 = (1m / 3m) * dtemp7 + (2m / 3m) * dtemp8;
				dmov = 1.5m * dtemp7 - dtemp8 / 2m;

				atemp1 = (2m / 3m) * atemp1 + (1m / 3m) * Math.Abs(dpiv);
				atemp2 = (1m / 3m) * atemp1 + (2m / 3m) * atemp2;
				atemp3 = 1.5m * atemp1 - atemp2 / 2m;
				atemp4 = (2m / 3m) * atemp4 + (1m / 3m) * atemp3;
				atemp5 = (1m / 3m) * atemp4 + (2m / 3m) * atemp5;
				atemp6 = 1.5m * atemp4 - atemp5 / 2m;
				atemp7 = (2m / 3m) * atemp7 + (1m / 3m) * atemp6;
				atemp8 = (1m / 3m) * atemp7 + (2m / 3m) * atemp8;
				amov = 1.5m * atemp7 - atemp8 / 2m;

				if (j <= 6 && cpiv != ppiv)
					iCounter++;

				if (j == 6 && iCounter == 0)
					j = 0;
			}

			if (j > 6 && amov > EpsilonTolerance)
			{
				tval = 50m * (dmov / amov + 1m);
				if (tval > 100m)
					tval = 100m;
				else if (tval < 0m)
					tval = 0m;
			}
			else
			{
				tval = 50m;
			}

			if (idx <= 2)
				_timingValues[idx] = tval;
		}

		_buySignal = _timingValues[1] <= _timingValues[2] && _timingValues[0] > _timingValues[1];
		_sellSignal = _timingValues[1] >= _timingValues[2] && _timingValues[0] < _timingValues[1];
	}

	private static bool IsTradingBlockedByCalendar(DateTimeOffset time)
	{
		if (time.DayOfWeek == DayOfWeek.Friday && time.Hour >= 23)
			return true;

		var dayOfYear = time.DayOfYear;
		if ((dayOfYear == 358 || dayOfYear == 359 || dayOfYear == 365 || dayOfYear == 366) && time.Hour >= 16)
			return true;

		return false;
	}
}