GitHub で見る

II ブレイクアウト戦略

概要

II ブレイクアウト戦略は、元々 MetaTrader 4 向けに書かれた高頻度ブレイクアウトシステムです。独自のタイミングオシレーターとボラティリティ圧力ゲージを組み合わせて強い方向性の動きにエントリーし、適応型トレーリングストップとピラミッド取引を使って取引を管理します。このコンバージョンは StockSharp のハイレベル API 上で元のロジックを再現し、スプレッド、ボラティリティ、カレンダーフィルターのための同じ保護機能を維持します。

変換されたトレーディングロジック

タイミングオシレーター

  • 新しい M1 ローソク足ごとに「典型価格」(high、low、close の平均に 100 を掛けたもの)が生成され、レガシーの平滑化カスケードに供給されます。
  • カスケードは元のネストされた移動平均/差分パイプライン(dtemp/atemp バッファ)を再構築し、0 から 100 のタイミング値を生成します。
  • 買いシグナル:タイミング値が前回の読み値を上回る方向に交差する(buffer[0] > buffer[1] かつ buffer[1] ≤ buffer[2])。
  • 売りシグナル:タイミング値が下方向に交差する(buffer[0] < buffer[1] かつ buffer[1] ≥ buffer[2])。

ボラティリティフィルター

  • 終値の 10 期間標準偏差が StdDevLimit を下回る必要があります。制限を超えると新規ポジションは許可されず、オプションで警告がログに記録されます。
  • カスタムボラティリティスコアが元の振幅 × ティック密度の式を再現します:現在と前の分足ローソク足の重なりと1秒あたりの平均ティック数を使用します。スコアは設定可能な VolatilityThreshold を超える必要があります。

エントリールール

  • 戦略は CandleType パラメーターで提供される単一のシンボル/時間軸ペアで動作します(デフォルトは1分足)。
  • ポジションが開いておらずカレンダーフィルターが取引を許可している場合、エンジンは CalculateOrderVolume() でロットサイズを更新し、レベル1の bid/ask データを使用して現在のスプレッドを SpreadThreshold と照合します。
  • タイミングオシレーターが買いシグナルを出し、ボラティリティスコアが有効な場合にロングポジションが開かれます。ショートポジションは逆の条件に従います。エントリー時に、執行価格の下/上に TrailStopPoints の 2 倍の距離に静的ストップが置かれます。

ピラミッド取引とトレーリング

  • トレーリングモジュールは、集計ポジションが少なくとも TrailStopPoints + int(Commission) + SpreadThreshold ポイントの未実現利益を獲得したら起動します。
  • ストップは最新の終値の後ろ TrailStopPoints に引き締められます(ロングとショートで別々に追跡)。1ポイントを超える改善があればトレーリング価格が更新されます。
  • ボラティリティ、タイミング、スプレッドの条件が有効である限り、戦略は追加利益の max(10, SpreadThreshold + 1) ポイントごとに新しい注文をピラミッドできます。新しい注文は静的ストップを無効にし、純粋にトレーリングロジックに依存します。

リスクと資金管理

  • ポジションサイズは各注文の前に再計算されます:残高 × MaximumRisk ÷ (500000 / AccountLeverage) を銘柄のボリュームステップに丸めたもの。残高情報が利用できない場合は Volume または最小ロットにフォールバックします。
  • 簡略化されたマージンチェックが元の MetaTrader の保護を近似します(volume × price / leverage × (1 + MaximumRisk × 190))。口座価値がその金額を賄えない場合、注文は無視されます。
  • ピラミッド取引が有効になった後、戦略は浮動損失を監視します。未実現ドローダウンが口座価値の TotalEquityRisk パーセントを超えると、すべてのポジションが清算されます。

カレンダーとスプレッドの保護

  • 取引は金曜日のサーバー時間 23:00 以降、および年末の最後の取引日(年の日数 358、359、365 または 366)の 16:00 以降に停止します。
  • 各エントリーと追加注文は現在の bid/ask スプレッドを確認し、設定されたしきい値を超えた場合は執行をスキップします。

パラメーター

パラメーター デフォルト 説明
Commission 4 トレーリング起動オフセットの計算に使用するラウンドロット手数料(ポイント)。
SpreadThreshold 6 新規エントリーまたはピラミッド取引に許可される最大スプレッド(ポイント)。
TrailStopPoints 20 トレーリングストップの距離(ポイント);初期ストップはこの値の2倍。
TotalEquityRisk 0.5 ピラミッド取引後に強制決済をトリガーする口座資産の損失パーセント。
MaximumRisk 0.1 ボリュームをサイジングする際に各注文に充当する口座残高の割合。
StdDevLimit 0.002 新規取引を受け入れるための最大 10 期間標準偏差。
VolatilityThreshold 800 取引に必要な最小ボラティリティスコア(振幅 × ティック密度)。
AccountLeverage 100 マージン近似とポジションサイジングに使用する口座レバレッジ。
WarningAlerts true 標準偏差フィルターがエントリーをブロックする際のログ記録を有効にします。
CandleType 1分 すべての計算に使用するローソク足タイプ。

インジケーター

  • 終値に対する StandardDeviation(Length = 10) — ボラティリティフィルター用。
  • 元の EA から再現したカスタムタイミングオシレーター(StockSharp インジケーターオブジェクトを使わずインラインで実装)。

実装上の注意

  • スプレッドフィルタリングにはライブレベル1データ(Security.BestBid/BestAsk)が必要です。フィードがない場合、戦略はスプレッドゼロと仮定します。
  • 元の EA が MetaTrader 固有の口座プロパティとコントラクトサイズに依存していたため、マージンと資産チェックは近似値です。ブローカーモデルに合わせて AccountLeverageMaximumRisk、または Volume を調整してください。
  • コンバージョンは StockSharp のハイレベル API(Bind を使ったローソク足サブスクリプション)を使用し、すべてのコメントを英語で維持します。この戦略には Python ポートは生成されません。
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;
	}
}