在 GitHub 上查看

Cyberia Trader 自适应策略

概述

Cyberia Trader 自适应策略 是 MetaTrader 平台上经典 "CyberiaTrader" 智能交易系统的 C# 版本。策略在 StockSharp 中重构了原始的概率核心,并提供可选的技术指标过滤器。系统持续分析价格波动,评估潜在的反转概率,然后再由 EMA、MACD、CCI、ADX 或分形过滤器确认信号并执行交易。

概率引擎

策略的核心是受 MQL 版本启发的概率计算器。它使用自适应采样周期 (ValuePeriod),按固定间隔回看历史 K 线,并将每根 K 线划分为:

  • 卖出概率:当前 K 线为阳线且前一采样为阴线,暗示可能的反转做空机会。
  • 买入概率:当前 K 线为阴线且前一采样为阳线。
  • 未定义概率:其余所有情况。

对于每一类别,策略都会累积平均振幅、出现次数和成功次数,样本数量由 ValuePeriod × HistoryMultiplier 控制。自适应搜索会在 1MaxPeriod(默认 23)之间遍历采样周期,并保留成功率最高的值。内部统计量包括:

  • BuyPossibilitySellPossibilityUndefinedPossibility:当前 K 线对应的概率值。
  • BuyPossibilityMidSellPossibilityMid 等:用于原始决策树的移动平均。
  • PossibilityQualityPossibilitySuccessQuality:用于诊断与自适应搜索的质量指标。

当历史数据不足时,策略会等待直到概率引擎返回有效样本。

指标过滤器

原始 EA 提供一系列布尔开关来控制附加模块,移植版本保持了相同的设计:

  • EMA 过滤器:比较 MaPeriod EMA 在最近两根 K 线的斜率。
  • MACD 过滤器:检查 MACD 与信号线的相对位置(MacdFastMacdSlowMacdSignal)。
  • CCI 过滤器:使用 CciPeriod 和 ±100 阈值标记超买/超卖区域。
  • ADX 过滤器:基于 AdxPeriod 的 +DI 和 −DI 判断趋势方向。
  • 分形过滤器:利用 FractalDepth 窗口检测最近的摆动高/低点,并阻止逆向下单。
  • 反向检测器:当概率尖峰超过 ReversalIndex 倍平均值时,翻转当前的方向禁用标志。

所有过滤器都可以通过参数独立启用或关闭,行为与 MQL 外部变量一致。

交易流程

  1. 订阅参数 CandleType 指定的 K 线数据。
  2. 在每根完结 K 线更新概率统计,并在开启自适应模式时重新选择最佳采样周期。
  3. 应用可选指标过滤器以及 Cyberia 原始决策树,确定是否允许买入/卖出。
  4. 当判定为买入或卖出信号时,执行市价单,同时尊重全局的 BlockBuyBlockSell 开关。
  5. 如设置了 StopLossPointsTakeProfitPoints,启动绝对点数止损/止盈保护。
  6. 当决策变为 Unknown 且概率质量下降时提前平仓。

参数说明

参数 描述
CandleType 用于计算的 K 线类型。
AutoSelectPeriod 是否在 1..MaxPeriod 范围内自适应搜索最佳采样周期。
InitialPeriod 在禁用自适应时使用的固定采样周期。
MaxPeriod 自适应搜索允许的最大周期(默认 23)。
HistoryMultiplier 每个周期使用的样本倍数。
SpreadFilter 判定概率“有效”的最小价格变动。
EnableCyberiaLogic 是否启用原始概率决策树。
EnableMa / EnableMacd / EnableCci / EnableAdx / EnableFractals / EnableReversalDetector 启用对应过滤器。
MaPeriod EMA 过滤器长度。
MacdFastMacdSlowMacdSignal MACD 参数设置。
CciPeriod CCI 指标周期。
AdxPeriod ADX 指标周期。
FractalDepth 分形检测使用的窗口长度(建议为奇数且不小于 5)。
ReversalIndex 反向检测器的倍数阈值。
BlockBuyBlockSell 强制禁止开仓的方向。
TakeProfitPointsStopLossPoints 绝对止盈与止损距离。

注意事项

  • 自适应搜索需要足够的历史数据:至少 ValuePeriod × HistoryMultiplier + ValuePeriod 根 K 线。
  • 代码全部使用 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;
	}
}