GitHub で見る

サイベリア トレーダー アダプティブ ストラテジー

概要

Cyberia Trader Adaptive Strategy は、古典的な MetaTrader「CyberiaTrader」エキスパート アドバイザーの C# ポートです。の この戦略は、元の確率駆動型コアを StockSharp で再構築し、オプションの技術フィルターで強化します。 価格変動を継続的に分析して反転の確率を測定し、オプションで EMA でシグナルを確認します。 注文を送信する前に、MACD、CCI、ADX、またはフラクタル フィルターを使用します。

確率エンジン

この戦略の中心となるのは、MQL バージョンからインスピレーションを得た確率計算ツールです。適応サンプリング周期を使用します (ValuePeriod) と固定ステップで過去のバーを検査し、各バーを次のように分類します。

  • 売り確率 – 弱気バーの後に強気バーが続く (潜在的なフェードチャンス)。
  • 買いの確率 – 強気のバーに続く弱気のバー。
  • 未定義の確率 – 他のすべてのバー構成。

各クラスについて、戦略は ValuePeriod × HistoryMultiplier にわたる平均振幅、ヒット率、成功率の統計を蓄積します。 サンプル。適応検索は、1 から MaxPeriod までの期間をスキャンし (デフォルトは 23)、最高の結果を生成する期間を保持します。 成功率。これらの統計は次のように内部的に公開されます。

  • BuyPossibilitySellPossibilityUndefinedPossibility – 現在のバー分類値。
  • BuyPossibilityMidSellPossibilityMid、... – 元のデシジョン ツリーで使用される移動平均。
  • PossibilityQualityPossibilitySuccessQuality – 診断と自動期間選択に使用される品質比率。

利用可能な履歴が不十分な場合、戦略は確率エンジンが有効なサンプル セットを報告するまで待機します。

インジケーターフィルター

オリジナルの EA では、追加のインジケーターベースのモジュールを有効または無効にすることができました。ポートでも同じ考え方が維持されています。

  • EMA フィルター – 最後の 2 つの完成したローソク足間の EMA (MaPeriod) の傾きを比較します。
  • MACD フィルター – MACD とその信号線 (MacdFastMacdSlowMacdSignal) の間の関係をチェックします。
  • CCI フィルターCciPeriod および ±100 のしきい値を使用して買われすぎ/売られすぎの状況にフラグを立てます。
  • ADX フィルター – +DI および -DI コンポーネント (AdxPeriod) を検査して、支配的な方向を優先します。
  • フラクタル フィルター – 構成可能な FractalDepth ウィンドウを使用して最新のスイングを検出し、それに対する注文をブロックします。
  • 反転検出器 – 確率スパイクが平均の ReversalIndex 倍を超えたときに方向フラグを切り替えます。

各モジュールはパラメータを介して切り替えることができ、元のブール型 extern 入力の動作を反映します。

取引ロジック

  1. 設定されたキャンドル シリーズ (CandleType) をサブスクライブします。
  2. 確率統計を再構築し、必要に応じて、完成したローソク足ごとに最適なサンプリング期間を再選択します。
  3. オプションのインジケーター フィルターとサイベリア デシジョン ツリーを適用して、買い/売りの指示を有効または無効にします。
  4. グローバルな BlockBuy および BlockSell スイッチを尊重して、売買の決定がアクティブなときに取引を実行します。
  5. StopLossPoints または TakeProfitPoints が指定されている場合は、オプションで絶対ストップロスまたはテイクプロフィット保護を適用します。
  6. 決定が Unknown になり、確率の質が悪化した場合は、ポジションを早期にクローズします。

パラメーター

名前 説明
CandleType 計算に使用されるローソク足シリーズ。
AutoSelectPeriod MaxPeriod にわたる適応検索を有効にして、最適なサンプリング ウィンドウを見つけます。
InitialPeriod 自動選択が無効になっている場合のフォールバック確率期間。
MaxPeriod 適応検索中に考慮される最大期間 (デフォルトは EA と同様に 23)。
HistoryMultiplier 統計で使用される期間ごとのサンプル数 (デフォルトは 5)。
SpreadFilter 確率を「成功」として扱うために必要な最小移動 (価格単位)。
EnableCyberiaLogic 確率平均を比較する元の決定木を切り替えます。
EnableMa, EnableMacd, EnableCci, EnableAdx, EnableFractals, EnableReversalDetector 個別のフィルターを有効にします。
MaPeriod 移動平均フィルターの長さは EMA です。
MacdFast, MacdSlow, MacdSignal MACD 構成。
CciPeriod 商品チャネルインデックスの長さ。
AdxPeriod 方向インデックスの平均長。
FractalDepth 最新のフラクタル スイングを検出するために分析された奇数のローソク足。
ReversalIndex 反転検出器によって使用される乗算器。
BlockBuy, BlockSell 指定された方向での取引の開始を停止するハードスイッチ。
TakeProfitPoints, StopLossPoints オプションの絶対テイクプロフィット距離とストップロス距離。

注意事項

  • 適応期間検索には、ValuePeriod × HistoryMultiplier + ValuePeriod バー分の十分な履歴が必要です。
  • すべてのコメントは英語で書き直され、ロジックはインジケーター バインディングを備えた高レベルの StockSharp API を維持しています。
  • 確率メトリクスは内部フィールドですが、ログを通じて、またはさらなる診断が必要な場合は戦略を拡張することによって公開されます。

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;

using StockSharp.Algo;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Adaptive port of the CyberiaTrader expert advisor that reconstructs its probability based decision tree.
/// Combines the original statistical core with optional indicator based filters.
/// </summary>
public class CyberiaTraderAdaptiveStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<bool> _autoSelectPeriod;
	private readonly StrategyParam<int> _initialPeriod;
	private readonly StrategyParam<int> _maxPeriod;
	private readonly StrategyParam<int> _historyMultiplier;
	private readonly StrategyParam<decimal> _spreadFilter;
	private readonly StrategyParam<bool> _enableCyberiaLogic;
	private readonly StrategyParam<bool> _enableMa;
	private readonly StrategyParam<bool> _enableMacd;
	private readonly StrategyParam<bool> _enableCci;
	private readonly StrategyParam<bool> _enableAdx;
	private readonly StrategyParam<bool> _enableFractals;
	private readonly StrategyParam<bool> _enableReversalDetector;
	private readonly StrategyParam<int> _maPeriod;
	private readonly StrategyParam<int> _macdFast;
	private readonly StrategyParam<int> _macdSlow;
	private readonly StrategyParam<int> _macdSignal;
	private readonly StrategyParam<int> _cciPeriod;
	private readonly StrategyParam<int> _adxPeriod;
	private readonly StrategyParam<int> _fractalDepth;
	private readonly StrategyParam<decimal> _reversalIndex;
	private readonly StrategyParam<bool> _blockBuy;
	private readonly StrategyParam<bool> _blockSell;
	private readonly StrategyParam<decimal> _takeProfitPoints;
	private readonly StrategyParam<decimal> _stopLossPoints;

	private ExponentialMovingAverage _ema = null!;
	private MovingAverageConvergenceDivergenceSignal _macd = null!;
	private CommodityChannelIndex _cci = null!;
	private AverageDirectionalIndex _adx = null!;

	private readonly List<CandleSnapshot> _history = new();

	private int _currentValuePeriod;
	private int _previousValuePeriod;
	private int _currentValuesPeriodCount;
	private decimal _lastSuitablePeriodQuality;

	private decimal? _previousEmaValue;
	private decimal? _lastEmaValue;
	private decimal? _lastMacdValue;
	private decimal? _lastMacdSignal;
	private decimal? _lastCciValue;
	private decimal? _lastPlusDi;
	private decimal? _lastMinusDi;
	private FractalDirections _fractalDirection = FractalDirections.None;

	private bool _disableBuy;
	private bool _disableSell;
	private bool _blockBuyFlag;
	private bool _blockSellFlag;

	private DecisionTypes _currentDecision = DecisionTypes.Unknown;
	private int _candlesSinceLastTrade;
	private decimal _buyPossibility;
	private decimal _sellPossibility;
	private decimal _undefinedPossibility;
	private decimal _decisionValue;
	private decimal _previousDecisionValue;

	private decimal _buyPossibilityMid;
	private decimal _sellPossibilityMid;
	private decimal _undefinedPossibilityMid;
	private decimal _buySucPossibilityMid;
	private decimal _sellSucPossibilityMid;
	private decimal _undefinedSucPossibilityMid;

	private decimal _buyPossibilityQuality;
	private decimal _sellPossibilityQuality;
	private decimal _undefinedPossibilityQuality;
	private decimal _buySucPossibilityQuality;
	private decimal _sellSucPossibilityQuality;
	private decimal _undefinedSucPossibilityQuality;
	private decimal _possibilityQuality;
	private decimal _possibilitySuccessQuality;

	/// <summary>
	/// Creates a new instance of the adaptive CyberiaTrader strategy.
	/// </summary>
	public CyberiaTraderAdaptiveStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
		.SetDisplay("Candle Type", "Candle series used for calculations", "General");

		_autoSelectPeriod = Param(nameof(AutoSelectPeriod), true)
		.SetDisplay("Auto Period", "Automatically scan for the best probability window", "General");

		_initialPeriod = Param(nameof(InitialPeriod), 5)
		.SetGreaterThanZero()
		.SetDisplay("Initial Period", "Fallback period for probability sampling", "General");

		_maxPeriod = Param(nameof(MaxPeriod), 23)
		.SetGreaterThanZero()
		.SetDisplay("Max Period", "Upper bound for adaptive period search", "General");

		_historyMultiplier = Param(nameof(HistoryMultiplier), 5)
		.SetGreaterThanZero()
		.SetDisplay("History Multiplier", "Number of samples per period used for statistics", "General");

		_spreadFilter = Param(nameof(SpreadFilter), 0m)
		.SetDisplay("Spread Filter", "Minimum move treated as actionable", "General");

		_enableCyberiaLogic = Param(nameof(EnableCyberiaLogic), true)
		.SetDisplay("Enable Cyberia Logic", "Use original probability based decision rules", "Logic");

		_enableMa = Param(nameof(EnableMa), false)
		.SetDisplay("Enable EMA", "Use EMA slope filter", "Logic");

		_enableMacd = Param(nameof(EnableMacd), false)
		.SetDisplay("Enable MACD", "Use MACD trend filter", "Logic");

		_enableCci = Param(nameof(EnableCci), false)
		.SetDisplay("Enable CCI", "Use CCI overbought/oversold filter", "Logic");

		_enableAdx = Param(nameof(EnableAdx), false)
		.SetDisplay("Enable ADX", "Use ADX directional filter", "Logic");

		_enableFractals = Param(nameof(EnableFractals), false)
		.SetDisplay("Enable Fractals", "Block trades opposite to the latest fractal", "Logic");

		_enableReversalDetector = Param(nameof(EnableReversalDetector), false)
		.SetDisplay("Enable Reversal Detector", "Toggle direction when probabilities spike", "Logic");

		_maPeriod = Param(nameof(MaPeriod), 23)
		.SetGreaterThanZero()
		.SetDisplay("EMA Period", "Length of the EMA used by the filter", "Indicators");

		_macdFast = Param(nameof(MacdFast), 12)
		.SetGreaterThanZero()
		.SetDisplay("MACD Fast", "Fast EMA length for MACD", "Indicators");

		_macdSlow = Param(nameof(MacdSlow), 26)
		.SetGreaterThanZero()
		.SetDisplay("MACD Slow", "Slow EMA length for MACD", "Indicators");

		_macdSignal = Param(nameof(MacdSignal), 9)
		.SetGreaterThanZero()
		.SetDisplay("MACD Signal", "Signal EMA length for MACD", "Indicators");

		_cciPeriod = Param(nameof(CciPeriod), 13)
		.SetGreaterThanZero()
		.SetDisplay("CCI Period", "Commodity Channel Index length", "Indicators");

		_adxPeriod = Param(nameof(AdxPeriod), 14)
		.SetGreaterThanZero()
		.SetDisplay("ADX Period", "Average Directional Index length", "Indicators");

		_fractalDepth = Param(nameof(FractalDepth), 5)
		.SetGreaterThanZero()
		.SetDisplay("Fractal Depth", "Number of candles used to detect fractals", "Indicators");

		_reversalIndex = Param(nameof(ReversalIndex), 3m)
		.SetDisplay("Reversal Index", "Multiplier for spike based reversal detection", "Logic");

		_blockBuy = Param(nameof(BlockBuy), false)
		.SetDisplay("Block Buy", "Prevent buy orders regardless of signals", "Risk");

		_blockSell = Param(nameof(BlockSell), false)
		.SetDisplay("Block Sell", "Prevent sell orders regardless of signals", "Risk");

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 0m)
		.SetDisplay("Take Profit", "Absolute take profit distance", "Risk");

		_stopLossPoints = Param(nameof(StopLossPoints), 0m)
		.SetDisplay("Stop Loss", "Absolute stop loss distance", "Risk");

		Volume = 1;
	}

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

	/// <summary>
	/// Enable adaptive period selection.
	/// </summary>
	public bool AutoSelectPeriod
	{
		get => _autoSelectPeriod.Value;
		set => _autoSelectPeriod.Value = value;
	}

	/// <summary>
	/// Fallback probability period when auto selection is disabled.
	/// </summary>
	public int InitialPeriod
	{
		get => _initialPeriod.Value;
		set => _initialPeriod.Value = value;
	}

	/// <summary>
	/// Maximum period evaluated during adaptive search.
	/// </summary>
	public int MaxPeriod
	{
		get => _maxPeriod.Value;
		set => _maxPeriod.Value = value;
	}

	/// <summary>
	/// Number of historical samples analysed per period.
	/// </summary>
	public int HistoryMultiplier
	{
		get => _historyMultiplier.Value;
		set => _historyMultiplier.Value = value;
	}

	/// <summary>
	/// Minimum move required to consider a probability successful.
	/// </summary>
	public decimal SpreadFilter
	{
		get => _spreadFilter.Value;
		set => _spreadFilter.Value = value;
	}

	/// <summary>
	/// Toggle the original Cyberia logic module.
	/// </summary>
	public bool EnableCyberiaLogic
	{
		get => _enableCyberiaLogic.Value;
		set => _enableCyberiaLogic.Value = value;
	}

	/// <summary>
	/// Toggle EMA filter.
	/// </summary>
	public bool EnableMa
	{
		get => _enableMa.Value;
		set => _enableMa.Value = value;
	}

	/// <summary>
	/// Toggle MACD filter.
	/// </summary>
	public bool EnableMacd
	{
		get => _enableMacd.Value;
		set => _enableMacd.Value = value;
	}

	/// <summary>
	/// Toggle CCI filter.
	/// </summary>
	public bool EnableCci
	{
		get => _enableCci.Value;
		set => _enableCci.Value = value;
	}

	/// <summary>
	/// Toggle ADX filter.
	/// </summary>
	public bool EnableAdx
	{
		get => _enableAdx.Value;
		set => _enableAdx.Value = value;
	}

	/// <summary>
	/// Toggle fractal filter.
	/// </summary>
	public bool EnableFractals
	{
		get => _enableFractals.Value;
		set => _enableFractals.Value = value;
	}

	/// <summary>
	/// Toggle probability spike based reversal detector.
	/// </summary>
	public bool EnableReversalDetector
	{
		get => _enableReversalDetector.Value;
		set => _enableReversalDetector.Value = value;
	}

	/// <summary>
	/// EMA period used in the moving average filter.
	/// </summary>
	public int MaPeriod
	{
		get => _maPeriod.Value;
		set => _maPeriod.Value = value;
	}

	/// <summary>
	/// Fast MACD period.
	/// </summary>
	public int MacdFast
	{
		get => _macdFast.Value;
		set => _macdFast.Value = value;
	}

	/// <summary>
	/// Slow MACD period.
	/// </summary>
	public int MacdSlow
	{
		get => _macdSlow.Value;
		set => _macdSlow.Value = value;
	}

	/// <summary>
	/// MACD signal period.
	/// </summary>
	public int MacdSignal
	{
		get => _macdSignal.Value;
		set => _macdSignal.Value = value;
	}

	/// <summary>
	/// CCI period.
	/// </summary>
	public int CciPeriod
	{
		get => _cciPeriod.Value;
		set => _cciPeriod.Value = value;
	}

	/// <summary>
	/// ADX period.
	/// </summary>
	public int AdxPeriod
	{
		get => _adxPeriod.Value;
		set => _adxPeriod.Value = value;
	}

	/// <summary>
	/// Depth used to confirm fractal swings.
	/// </summary>
	public int FractalDepth
	{
		get => _fractalDepth.Value;
		set => _fractalDepth.Value = value;
	}

	/// <summary>
	/// Multiplier for reversal detection.
	/// </summary>
	public decimal ReversalIndex
	{
		get => _reversalIndex.Value;
		set => _reversalIndex.Value = value;
	}

	/// <summary>
	/// Hard block for buy orders.
	/// </summary>
	public bool BlockBuy
	{
		get => _blockBuy.Value;
		set => _blockBuy.Value = value;
	}

	/// <summary>
	/// Hard block for sell orders.
	/// </summary>
	public bool BlockSell
	{
		get => _blockSell.Value;
		set => _blockSell.Value = value;
	}

	/// <summary>
	/// Take profit distance in absolute points.
	/// </summary>
	public decimal TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	/// <summary>
	/// Stop loss distance in absolute points.
	/// </summary>
	public decimal StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

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

	protected override void OnReseted()
	{
		base.OnReseted();
		_ema = default;
		_macd = default;
		_cci = default;
		_adx = default;
		_history.Clear();
		_currentValuePeriod = 0;
		_previousValuePeriod = 0;
		_currentValuesPeriodCount = 0;
		_lastSuitablePeriodQuality = 0;
		_previousEmaValue = null;
		_lastEmaValue = null;
		_lastMacdValue = null;
		_lastMacdSignal = null;
		_lastCciValue = null;
		_lastPlusDi = null;
		_lastMinusDi = null;
		_fractalDirection = default;
		_disableBuy = false;
		_disableSell = false;
		_blockBuyFlag = false;
		_blockSellFlag = false;
		_currentDecision = default;
		_candlesSinceLastTrade = 0;
		_buyPossibility = 0;
		_sellPossibility = 0;
		_undefinedPossibility = 0;
		_decisionValue = 0;
		_previousDecisionValue = 0;
		_buyPossibilityMid = 0;
		_sellPossibilityMid = 0;
		_undefinedPossibilityMid = 0;
		_buySucPossibilityMid = 0;
		_sellSucPossibilityMid = 0;
		_undefinedSucPossibilityMid = 0;
		_buyPossibilityQuality = 0;
		_sellPossibilityQuality = 0;
		_undefinedPossibilityQuality = 0;
		_buySucPossibilityQuality = 0;
		_sellSucPossibilityQuality = 0;
		_undefinedSucPossibilityQuality = 0;
		_possibilityQuality = 0;
		_possibilitySuccessQuality = 0;
	}

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

		_currentValuePeriod = Math.Max(1, InitialPeriod);
		_previousValuePeriod = _currentValuePeriod;
		_currentValuesPeriodCount = Math.Max(1, _currentValuePeriod * HistoryMultiplier);

		_ema = new EMA { Length = MaPeriod };
		_macd = new MovingAverageConvergenceDivergenceSignal
		{
			Macd =
			{
				ShortMa = { Length = MacdFast },
				LongMa = { Length = MacdSlow },
			},
			SignalMa = { Length = MacdSignal }
		};
		_cci = new CommodityChannelIndex { Length = CciPeriod };
		_adx = new AverageDirectionalIndex { Length = AdxPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription
		.BindEx(_ema, _macd, _cci, _adx, ProcessCandle)
		.Start();

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

			var indicatorArea = CreateChartArea();
			if (indicatorArea != null)
			{
				DrawIndicator(indicatorArea, _macd);
				DrawIndicator(indicatorArea, _cci);
				DrawIndicator(indicatorArea, _adx);
			}
		}

		Unit takeProfit = TakeProfitPoints > 0m ? new Unit(TakeProfitPoints, UnitTypes.Absolute) : null;
		Unit stopLoss = StopLossPoints > 0m ? new Unit(StopLossPoints, UnitTypes.Absolute) : null;

		if (takeProfit != null || stopLoss != null)
		{
			StartProtection(takeProfit, stopLoss);
		}

		base.OnStarted2(time);
	}

	private void ProcessCandle(ICandleMessage candle, IIndicatorValue emaValue, IIndicatorValue macdValue, IIndicatorValue cciValue, IIndicatorValue adxValue)
	{
		// Ignore updates for unfinished candles.
		if (candle.State != CandleStates.Finished)
		return;

		// Ensure every indicator reports a final value before using it.
		if (!emaValue.IsFinal || !macdValue.IsFinal || !cciValue.IsFinal || !adxValue.IsFinal)
		return;

		var ema = emaValue.ToDecimal();
		var macdTyped = (MovingAverageConvergenceDivergenceSignalValue)macdValue;
		var cci = cciValue.ToDecimal();
		var adxTyped = (AverageDirectionalIndexValue)adxValue;

		_previousEmaValue = _lastEmaValue;
		_lastEmaValue = ema;
		_lastMacdValue = macdTyped.Macd;
		_lastMacdSignal = macdTyped.Signal;
		_lastCciValue = cci;
		_lastPlusDi = adxTyped.Dx.Plus;
		_lastMinusDi = adxTyped.Dx.Minus;

		// Store the latest bar snapshot for probability calculations.
		AddCandle(candle);
		UpdateFractalState();

		_candlesSinceLastTrade++;

		// Skip trading until the probability model is ready.
		if (!UpdateAdaptivePeriod())
		return;

		CalculateDirection();
		ExecuteTradingLogic();
	}

	private void CalculateDirection()
	{
		// Reset direction flags before applying the filter chain.
		_disableBuy = false;
		_disableSell = false;
		_blockBuyFlag = BlockBuy;
		_blockSellFlag = BlockSell;

		if (EnableCyberiaLogic)
		ApplyCyberiaLogic();

		if (EnableMacd)
		ApplyMacdFilter();

		if (EnableMa)
		ApplyMaFilter();

		if (EnableCci)
		ApplyCciFilter();

		if (EnableAdx)
		ApplyAdxFilter();

		if (EnableFractals)
		ApplyFractalFilter();

		if (EnableReversalDetector)
		ApplyReversalDetector();
	}

	private void ExecuteTradingLogic()
	{
		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		// Enforce a minimum holding period to prevent rapid order churn.
		var minHold = Math.Max(MaxPeriod, _currentValuePeriod) * HistoryMultiplier;
		if (_candlesSinceLastTrade < minHold)
			return;

		// Combine internal logic and user defined blocks.
		var allowBuy = !_disableBuy && !_blockBuyFlag;
		var allowSell = !_disableSell && !_blockSellFlag;

		if (_currentDecision == DecisionTypes.Buy && allowBuy && Position <= 0)
		{
			_candlesSinceLastTrade = 0;
			if (Position < 0)
				BuyMarket(Math.Abs(Position) + Volume);
			else
				BuyMarket(Volume);
		}
		else if (_currentDecision == DecisionTypes.Sell && allowSell && Position >= 0)
		{
			_candlesSinceLastTrade = 0;
			if (Position > 0)
				SellMarket(Position + Volume);
			else
				SellMarket(Volume);
		}
		else if (_currentDecision == DecisionTypes.Unknown)
		{
			if (_possibilityQuality < 0.5m)
			{
				_candlesSinceLastTrade = 0;
				ClosePosition();
			}
		}
	}

	private void ClosePosition()
	{
		if (Position > 0)
		{
			SellMarket(Position);
		}
		else if (Position < 0)
		{
			BuyMarket(Math.Abs(Position));
		}
	}

	private void ApplyCyberiaLogic()
	{
		var leftScore = _sellPossibilityMid * _sellPossibilityQuality;
		var rightScore = _buyPossibilityMid * _buyPossibilityQuality;
		var leftSuccess = _sellSucPossibilityMid * _sellSucPossibilityQuality;
		var rightSuccess = _buySucPossibilityMid * _buySucPossibilityQuality;

		if (_currentValuePeriod > _previousValuePeriod)
		{
			if (leftScore > rightScore)
			{
				_disableSell = false;
				_disableBuy = true;

				if (leftSuccess > rightSuccess)
				_disableSell = true;
			}
			else if (leftScore < rightScore)
			{
				_disableSell = true;
				_disableBuy = false;

				if (leftSuccess < rightSuccess)
				_disableBuy = true;
			}
		}
		else if (_currentValuePeriod < _previousValuePeriod)
		{
			_disableSell = true;
			_disableBuy = true;
		}

		if (leftScore == rightScore)
		{
			_disableSell = true;
			_disableBuy = true;
		}

		if (_sellPossibilityMid > 0m && _sellSucPossibilityMid > 0m &&
		_sellPossibility > _sellSucPossibilityMid * 2m)
		{
			_disableSell = true;
		}

		if (_buyPossibilityMid > 0m && _buySucPossibilityMid > 0m &&
		_buyPossibility > _buySucPossibilityMid * 2m)
		{
			_disableBuy = true;
		}
	}

	private void ApplyMacdFilter()
	{
		if (_lastMacdValue is not decimal macd || _lastMacdSignal is not decimal signal)
		return;

		if (macd > signal)
		{
			_disableSell = true;
		}
		else if (macd < signal)
		{
			_disableBuy = true;
		}
	}

	private void ApplyMaFilter()
	{
		if (_previousEmaValue is not decimal prev || _lastEmaValue is not decimal current)
		return;

		if (current > prev)
		{
			_disableSell = true;
		}
		else if (current < prev)
		{
			_disableBuy = true;
		}
	}

	private void ApplyCciFilter()
	{
		if (_lastCciValue is not decimal cci)
		return;

		if (cci < -100m)
		{
			_disableSell = true;
		}
		else if (cci > 100m)
		{
			_disableBuy = true;
		}
	}

	private void ApplyAdxFilter()
	{
		if (_lastPlusDi is not decimal plus || _lastMinusDi is not decimal minus)
		return;

		if (plus > minus)
		{
			_disableSell = true;
		}
		else if (minus > plus)
		{
			_disableBuy = true;
		}
	}

	private void ApplyFractalFilter()
	{
		if (_fractalDirection == FractalDirections.Up)
		{
			_blockBuyFlag = true;
			_blockSellFlag = false;
		}
		else if (_fractalDirection == FractalDirections.Down)
		{
			_blockSellFlag = true;
			_blockBuyFlag = false;
		}
	}

	private void ApplyReversalDetector()
	{
		var trigger = false;

		if (_buyPossibility != 0m && _buyPossibilityMid != 0m &&
		_buyPossibility > _buyPossibilityMid * ReversalIndex)
		{
			trigger = true;
		}

		if (_sellPossibility != 0m && _sellPossibilityMid != 0m &&
		_sellPossibility > _sellPossibilityMid * ReversalIndex)
		{
			trigger = true;
		}

		if (!trigger)
		return;

		_disableSell = !_disableSell;
		_disableBuy = !_disableBuy;
	}

	private void AddCandle(ICandleMessage candle)
	{
		var snapshot = new CandleSnapshot(candle.OpenPrice, candle.HighPrice, candle.LowPrice, candle.ClosePrice);
		_history.Add(snapshot);

		var maxHistory = Math.Max(MaxPeriod, _currentValuePeriod) * (HistoryMultiplier + 2) + 2;
		while (_history.Count > maxHistory)
		{
			_history.RemoveAt(0);
		}
	}

	private void UpdateFractalState()
	{
		var depth = Math.Max(5, FractalDepth);
		if (depth % 2 == 0)
		depth += 1;

		if (_history.Count < depth)
		return;

		var start = _history.Count - depth;
		var middle = start + depth / 2;
		var center = _history[middle];

		var isUpper = true;
		var isLower = true;

		for (var i = start; i < start + depth; i++)
		{
			if (i == middle)
			continue;

			var sample = _history[i];
			if (sample.High >= center.High)
			isUpper = false;
			if (sample.Low <= center.Low)
			isLower = false;
		}

		if (isUpper)
		{
			_fractalDirection = FractalDirections.Up;
		}
		else if (isLower)
		{
			_fractalDirection = FractalDirections.Down;
		}
	}

	private bool UpdateAdaptivePeriod()
	{
		// Evaluate possible sampling windows and keep the most reliable one.
		var basePeriod = Math.Max(1, InitialPeriod);
		var maxPeriod = AutoSelectPeriod ? Math.Max(1, MaxPeriod) : basePeriod;

		PossibilityStats? bestStats = null;
		var bestQuality = decimal.MinValue;
		var selectedPeriod = basePeriod;

		for (var period = 1; period <= maxPeriod; period++)
		{
			if (!AutoSelectPeriod && period != basePeriod)
			continue;

			var stats = CalculateStatistics(period);
			if (!stats.IsValid)
			continue;

			if (!AutoSelectPeriod)
			{
				bestStats = stats;
				bestQuality = stats.PossibilitySuccessQuality;
				selectedPeriod = period;
				break;
			}

			if (stats.PossibilitySuccessQuality > bestQuality)
			{
				bestQuality = stats.PossibilitySuccessQuality;
				selectedPeriod = period;
				bestStats = stats;
			}
		}

		if (bestStats == null)
		return false;

		_previousValuePeriod = _currentValuePeriod;
		_currentValuePeriod = selectedPeriod;
		_currentValuesPeriodCount = Math.Max(1, _currentValuePeriod * HistoryMultiplier);
		_lastSuitablePeriodQuality = bestQuality;

		ApplyStatistics(bestStats.Value);
		return true;
	}

	private PossibilityStats CalculateStatistics(int period)
	{
		// Compute averages and hit rates for the specified sampling period.
		var modelingBars = Math.Max(1, period * HistoryMultiplier);
		var required = period * (modelingBars + 1);
		if (_history.Count < required)
		return PossibilityStats.Invalid;

		var spread = SpreadFilter;

		decimal buySum = 0m;
		decimal sellSum = 0m;
		decimal undefinedSum = 0m;
		decimal buySuccessSum = 0m;
		decimal sellSuccessSum = 0m;
		decimal undefinedSuccessSum = 0m;

		var buyCount = 0;
		var sellCount = 0;
		var undefinedCount = 0;
		var buySuccessCount = 0;
		var sellSuccessCount = 0;
		var undefinedSuccessCount = 0;

		var buyQuality = 0m;
		var sellQuality = 0m;
		var undefinedQuality = 0m;
		var buySuccessQuality = 0m;
		var sellSuccessQuality = 0m;
		var undefinedSuccessQuality = 0m;

		DecisionTypes currentDecision = DecisionTypes.Unknown;
		decimal currentBuy = 0m;
		decimal currentSell = 0m;
		decimal currentUndefined = 0m;
		decimal currentDecisionValue = 0m;
		decimal previousDecisionValue = 0m;

		var shifts = Math.Min(modelingBars, (_history.Count / period) - 1);

		for (var i = 0; i <= shifts; i++)
		{
			var result = CalculatePossibility(period, i);
			if (i == 0)
			{
				currentDecision = result.Decision;
				currentBuy = result.BuyPossibility;
				currentSell = result.SellPossibility;
				currentUndefined = result.UndefinedPossibility;
				currentDecisionValue = result.DecisionValue;
				previousDecisionValue = result.PreviousDecisionValue;
			}

			if (result.Decision == DecisionTypes.Buy)
			buyQuality += 1m;
			else if (result.Decision == DecisionTypes.Sell)
			sellQuality += 1m;
			else
			undefinedQuality += 1m;

			if (result.BuyPossibility > spread)
			{
				buySuccessQuality += 1m;
				buySuccessSum += result.BuyPossibility;
				buySuccessCount += 1;
			}

			if (result.SellPossibility > spread)
			{
				sellSuccessQuality += 1m;
				sellSuccessSum += result.SellPossibility;
				sellSuccessCount += 1;
			}

			if (result.UndefinedPossibility > spread)
			{
				undefinedSuccessQuality += 1m;
				undefinedSuccessSum += result.UndefinedPossibility;
				undefinedSuccessCount += 1;
			}

			buySum += result.BuyPossibility;
			sellSum += result.SellPossibility;
			undefinedSum += result.UndefinedPossibility;

			buyCount += 1;
			sellCount += 1;
			undefinedCount += 1;
		}

		var totalQuality = buyQuality + sellQuality + undefinedQuality;
		var totalSuccessQuality = buySuccessQuality + sellSuccessQuality + undefinedSuccessQuality;

		var stats = new PossibilityStats
		(
		currentDecision,
		currentBuy,
		currentSell,
		currentUndefined,
		currentDecisionValue,
		previousDecisionValue,
		buyCount > 0 ? buySum / buyCount : 0m,
		sellCount > 0 ? sellSum / sellCount : 0m,
		undefinedCount > 0 ? undefinedSum / undefinedCount : 0m,
		buySuccessCount > 0 ? buySuccessSum / buySuccessCount : 0m,
		sellSuccessCount > 0 ? sellSuccessSum / sellSuccessCount : 0m,
		undefinedSuccessCount > 0 ? undefinedSuccessSum / undefinedSuccessCount : 0m,
		buyQuality,
		sellQuality,
		undefinedQuality,
		buySuccessQuality,
		sellSuccessQuality,
		undefinedSuccessQuality,
		totalQuality > 0m ? (sellQuality + buyQuality) / totalQuality : 0m,
		totalSuccessQuality > 0m ? (sellSuccessQuality + buySuccessQuality) / totalSuccessQuality : 0m,
		true
		);

		return stats;
	}

	private void ApplyStatistics(PossibilityStats stats)
	{
		_currentDecision = stats.Decision;
		_buyPossibility = stats.BuyPossibility;
		_sellPossibility = stats.SellPossibility;
		_undefinedPossibility = stats.UndefinedPossibility;
		_decisionValue = stats.DecisionValue;
		_previousDecisionValue = stats.PreviousDecisionValue;
		_buyPossibilityMid = stats.BuyPossibilityMid;
		_sellPossibilityMid = stats.SellPossibilityMid;
		_undefinedPossibilityMid = stats.UndefinedPossibilityMid;
		_buySucPossibilityMid = stats.BuySucPossibilityMid;
		_sellSucPossibilityMid = stats.SellSucPossibilityMid;
		_undefinedSucPossibilityMid = stats.UndefinedSucPossibilityMid;
		_buyPossibilityQuality = stats.BuyPossibilityQuality;
		_sellPossibilityQuality = stats.SellPossibilityQuality;
		_undefinedPossibilityQuality = stats.UndefinedPossibilityQuality;
		_buySucPossibilityQuality = stats.BuySucPossibilityQuality;
		_sellSucPossibilityQuality = stats.SellSucPossibilityQuality;
		_undefinedSucPossibilityQuality = stats.UndefinedSucPossibilityQuality;
		_possibilityQuality = stats.PossibilityQuality;
		_possibilitySuccessQuality = stats.PossibilitySuccessQuality;
	}

	private PossibilityResult CalculatePossibility(int period, int shift)
	{
		var currentIndex = period * shift;
		var previousIndex = period * (shift + 1);

		var current = GetCandle(currentIndex);
		var previous = GetCandle(previousIndex);

		var decisionValue = current.Close - current.Open;
		var previousDecisionValue = previous.Close - previous.Open;

		decimal buyPossibility = 0m;
		decimal sellPossibility = 0m;
		decimal undefinedPossibility = 0m;
		var decision = DecisionTypes.Unknown;

		if (decisionValue > 0m)
		{
			if (previousDecisionValue < 0m)
			{
				decision = DecisionTypes.Sell;
				sellPossibility = decisionValue;
			}
			else
			{
				decision = DecisionTypes.Unknown;
				undefinedPossibility = decisionValue;
			}
		}
		else if (decisionValue < 0m)
		{
			if (previousDecisionValue > 0m)
			{
				decision = DecisionTypes.Buy;
				buyPossibility = -decisionValue;
			}
			else
			{
				decision = DecisionTypes.Unknown;
				undefinedPossibility = -decisionValue;
			}
		}

		return new PossibilityResult(decision, buyPossibility, sellPossibility, undefinedPossibility, decisionValue, previousDecisionValue);
	}

	private CandleSnapshot GetCandle(int shift)
	{
		var index = _history.Count - 1 - shift;
		if (index < 0)
		return default;

		return _history[index];
	}

	private readonly record struct CandleSnapshot(decimal Open, decimal High, decimal Low, decimal Close);

	private enum DecisionTypes
	{
		Sell,
		Buy,
		Unknown,
	}

	private enum FractalDirections
	{
		None,
		Up,
		Down,
	}

	private readonly record struct PossibilityResult(DecisionTypes Decision, decimal BuyPossibility, decimal SellPossibility, decimal UndefinedPossibility, decimal DecisionValue, decimal PreviousDecisionValue);

	private readonly record struct PossibilityStats(
	DecisionTypes Decision,
	decimal BuyPossibility,
	decimal SellPossibility,
	decimal UndefinedPossibility,
	decimal DecisionValue,
	decimal PreviousDecisionValue,
	decimal BuyPossibilityMid,
	decimal SellPossibilityMid,
	decimal UndefinedPossibilityMid,
	decimal BuySucPossibilityMid,
	decimal SellSucPossibilityMid,
	decimal UndefinedSucPossibilityMid,
	decimal BuyPossibilityQuality,
	decimal SellPossibilityQuality,
	decimal UndefinedPossibilityQuality,
	decimal BuySucPossibilityQuality,
	decimal SellSucPossibilityQuality,
	decimal UndefinedSucPossibilityQuality,
	decimal PossibilityQuality,
	decimal PossibilitySuccessQuality,
	bool HasValue)
	{
		public static PossibilityStats Invalid { get; } = new PossibilityStats(DecisionTypes.Unknown, 0m, 0m, 0m, 0m, 0m, 0m, 0m, 0m, 0m, 0m, 0m, 0m, 0m, 0m, 0m, 0m, 0m, 0m, 0m, false);

		public bool IsValid => HasValue;
	}
}