Overall Statistics
Total Orders
0
Average Win
0%
Average Loss
0%
Compounding Annual Return
0%
Drawdown
0%
Expectancy
0
Start Equity
1000000.00
End Equity
1000000
Net Profit
0%
Sharpe Ratio
0
Sortino Ratio
0
Probabilistic Sharpe Ratio
0%
Loss Rate
0%
Win Rate
0%
Profit-Loss Ratio
0
Alpha
0
Beta
0
Annual Standard Deviation
0
Annual Variance
0
Information Ratio
-1.037
Tracking Error
0.137
Treynor Ratio
0
Total Fees
$0.00
Estimated Strategy Capacity
$0
Lowest Capacity Asset
Portfolio Turnover
0%
#region imports
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Globalization;
using System.Linq;
using System.Text;
using Newtonsoft.Json;
using QuantConnect;
using QuantConnect.Algorithm;
using QuantConnect.Algorithm.Framework;
using QuantConnect.Algorithm.Framework.Alphas;
using QuantConnect.Algorithm.Framework.Execution;
using QuantConnect.Algorithm.Framework.Portfolio;
using QuantConnect.Algorithm.Framework.Portfolio.SignalExports;
using QuantConnect.Algorithm.Framework.Risk;
using QuantConnect.Algorithm.Framework.Selection;
using QuantConnect.Algorithm.Selection;
using QuantConnect.Api;
using QuantConnect.Benchmarks;
using QuantConnect.Brokerages;
using QuantConnect.Commands;
using QuantConnect.Configuration;
using QuantConnect.Data;
using QuantConnect.Data.Auxiliary;
using QuantConnect.Data.Consolidators;
using QuantConnect.Data.Custom;
using QuantConnect.Data.Custom.IconicTypes;
using QuantConnect.Data.Fundamental;
using QuantConnect.Data.Market;
using QuantConnect.Data.Shortable;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.DataSource;
using QuantConnect.Indicators;
using QuantConnect.Interfaces;
using QuantConnect.Notifications;
using QuantConnect.Orders;
using QuantConnect.Orders.Fees;
using QuantConnect.Orders.Fills;
using QuantConnect.Orders.OptionExercise;
using QuantConnect.Orders.Slippage;
using QuantConnect.Orders.TimeInForces;
using QuantConnect.Parameters;
using QuantConnect.Python;
using QuantConnect.Scheduling;
using QuantConnect.Securities;
using QuantConnect.Securities.Crypto;
using QuantConnect.Securities.CryptoFuture;
using QuantConnect.Securities.Equity;
using QuantConnect.Securities.Forex;
using QuantConnect.Securities.Future;
using QuantConnect.Securities.IndexOption;
using QuantConnect.Securities.Interfaces;
using QuantConnect.Securities.Option;
using QuantConnect.Securities.Positions;
using QuantConnect.Securities.Volatility;
using QuantConnect.Statistics;
using QuantConnect.Storage;
using QuantConnect.Util;
using Calendar = QuantConnect.Data.Consolidators.Calendar;
using QCAlgorithmFramework = QuantConnect.Algorithm.QCAlgorithm;
using QCAlgorithmFrameworkBridge = QuantConnect.Algorithm.QCAlgorithm;
#endregion

namespace QuantConnect.Algorithm.CSharp
{

    public class CryptoExampleStrategyPublic : QCAlgorithm
    {
        public static string ModelParamsFileName = "baseline_model_params.json";
        public static string ThresholdArrayFileName = "baseline_threshold_array.json";

        public class ModelParams
        {
            [JsonProperty("feature_cols")]
            public string[] FeatureCols { get; set; }

            [JsonProperty("coefficients")]
            public decimal[] Coefficients { get; set; }

            [JsonProperty("intercept")]
            public decimal Intercept { get; set; }

            [JsonProperty("center")]
            public decimal[] Center { get; set; }

            [JsonProperty("scale")]
            public decimal[] Scale { get; set; }
        }

        // Ring buffer for prediction history
        private class RingBuffer<T>
        {
            private T[] _buffer;
            private DateTime[] _timestamps;
            private int _size;
            private int _currentIndex;
            private int _count;

            public RingBuffer(int size)
            {
                _size = size;
                _buffer = new T[size];
                _timestamps = new DateTime[size];
                _currentIndex = 0;
                _count = 0;
            }

            public void Add(DateTime timestamp, T item)
            {
                _timestamps[_currentIndex] = timestamp;
                _buffer[_currentIndex] = item;
                _currentIndex = (_currentIndex + 1) % _size;
                if (_count < _size)
                    _count++;
            }

            public int Count => _count;

            public T GetByIndex(int index)
            {
                if (index < 0 || index >= _count)
                    throw new IndexOutOfRangeException();

                int actualIndex = (_currentIndex - _count + index + _size) % _size;
                return _buffer[actualIndex];
            }

            public DateTime GetTimestampByIndex(int index)
            {
                if (index < 0 || index >= _count)
                    throw new IndexOutOfRangeException();

                int actualIndex = (_currentIndex - _count + index + _size) % _size;
                return _timestamps[actualIndex];
            }

            public T GetLatest()
            {
                if (_count == 0)
                    throw new InvalidOperationException("Buffer is empty");

                int index = (_currentIndex - 1 + _size) % _size;
                return _buffer[index];
            }

            public DateTime GetLatestTimestamp()
            {
                if (_count == 0)
                    throw new InvalidOperationException("Buffer is empty");

                int index = (_currentIndex - 1 + _size) % _size;
                return _timestamps[index];
            }

            public List<T> GetItems()
            {
                List<T> result = new List<T>(_count);
                for (int i = 0; i < _count; i++)
                {
                    int index = (_currentIndex - _count + i + _size) % _size;
                    result.Add(_buffer[index]);
                }
                return result;
            }

            public List<KeyValuePair<DateTime, T>> GetAllWithTimestamps()
            {
                List<KeyValuePair<DateTime, T>> result = new List<KeyValuePair<DateTime, T>>(
                    _count
                );
                for (int i = 0; i < _count; i++)
                {
                    int index = (_currentIndex - _count + i + _size) % _size;
                    result.Add(new KeyValuePair<DateTime, T>(_timestamps[index], _buffer[index]));
                }
                return result;
            }

            public List<T> GetItemsInTimeRange(DateTime startTime, DateTime endTime)
            {
                List<T> result = new List<T>();
                for (int i = 0; i < _count; i++)
                {
                    int index = (_currentIndex - _count + i + _size) % _size;
                    if (_timestamps[index] >= startTime && _timestamps[index] <= endTime)
                    {
                        result.Add(_buffer[index]);
                    }
                }
                return result;
            }
        }

        // Prediction record to track accuracy
        private class PredictionRecord
        {
            public decimal Probability { get; set; }
            public decimal EntryPrice { get; set; }
            public bool? IsCorrect { get; set; } // null means not yet determined
        }

        private enum ModelState
        {
            Normal,
            Reversed,
            NotReliable,
        }

        private Symbol _btcusdt;
        private ModelParams _modelParams;
        private decimal[] _thresholdArr;
        private bool _modelLoaded = false;

        private decimal _positionSize = 0.98m;
        private decimal _leverage = 1.0m;

        private decimal _enterPositionThreshold = 0.02m;
        private decimal _exitPositionThreshold = 0.73m;

        private decimal _takeProfitTarget = 0.005m;
        private decimal _stopLossLevel = 5m;

        // Min accuracy threshold for normal operation
        private decimal _normalThreshold = 0.46m; // TODO
        // Max accuracy threshold for reversed operation
        private decimal _reversedThreshold = 0.41m; // TODO
        // Min number of predictions needed to evaluate accuracy
        private int _minPredictionsForAccuracy = 30; // half in 30, half in 60, // TODO

        private ModelState _currentModelState = ModelState.Normal;

        private DateTime _positionEntryTime;
        private bool _inLongPosition = false;
        private bool _inShortPosition = false;
        private decimal _entryPrice = 0m;
        private int _positionHoldingWindow = 10;
        private int _earlyProfitMinHoldingTime = 1;

        private RingBuffer<decimal> _predictionHistory;
        private RingBuffer<decimal> _priceHistory;
        private RingBuffer<PredictionRecord> _predictionRecords; // Records for accuracy tracking
        private int _maxPredictionHistory => 60 + _positionHoldingWindow + _earlyProfitMinHoldingTime; // TODO

        private List<TradeRecord> _tradeRecords = new List<TradeRecord>();

        private class TradeRecord
        {
            public DateTime EntryTime { get; set; }
            public DateTime ExitTime { get; set; }
            public decimal EntryPrice { get; set; }
            public decimal ExitPrice { get; set; }
            public string Direction { get; set; }
            public decimal PnL { get; set; }
            public string ExitReason { get; set; }
            public ModelState ModelStateAtEntry { get; set; }
            public decimal OriginalPrediction { get; set; }
            public decimal AdjustedPrediction { get; set; }
        }

        public override void Initialize()
        {
            // SetStartDate(2023, 1, 1);
            SetStartDate(2023, 7, 1);
            // SetStartDate(2023, 10, 14);
            // SetStartDate(2024, 8, 1);
            // SetEndDate(2024, 6, 1);
            // SetEndDate(2024, 9, 1);
            // SetEndDate(2023, 10, 30);
            SetEndDate(DateTime.Now);

            // SetAccountCurrency("USDT", 1_000_000);
            SetAccountCurrency("USD", 1_000_000);
            // SetCash("USDT", 0);
            SetBrokerageModel(new DefaultBrokerageModel());
            // SetBrokerageModel(new BinanceBrokerageModel(AccountType.Margin));
            SetTimeZone(TimeZones.Utc);

            // We use 2x leverage for quantconnect live paper trading for the high sharpe ratio
            if (LiveMode)
            {
                _positionSize = 0.98m;
                _leverage = 2.0m;
            }

            var security = AddCrypto(
                "BTCUSDT",
                Resolution.Minute,
                LiveMode ? null: Market.Binance,
                fillForward: true,
                leverage: _leverage
            );
            // security.SetFeeModel(new ConstantFeeModel(0.0m));
            _btcusdt = security.Symbol;
            _predictionHistory = new RingBuffer<decimal>(_maxPredictionHistory);
            _priceHistory = new RingBuffer<decimal>(_maxPredictionHistory + _positionHoldingWindow + _earlyProfitMinHoldingTime);
            _predictionRecords = new RingBuffer<PredictionRecord>(_maxPredictionHistory);

            // Reload model every 00:00 UTC
            // Schedule.On(
            //     DateRules.EveryDay("BTCUSDT"),
            //     TimeRules.At(new TimeSpan(00, 00, 00)),
            //     LoadModelParameters
            // );
            // Reset state machine at start of each day
            // Schedule.On(
            //     DateRules.EveryDay("BTCUSDT"),
            //     TimeRules.At(00, 00, 01), // Just after midnight
            //     ResetStateMachine
            // );
            // Liquidate at the start of each day
            // Schedule.On(
            //     DateRules.EveryDay("BTCUSDT"),
            //     TimeRules.At(00, 00, 05), // Just after midnight
            //     CheckAndLiquidateForNonTestDays
            // );

            // Schedule evaluation of past predictions
            Schedule.On(
                DateRules.EveryDay("BTCUSDT"),
                TimeRules.Every(TimeSpan.FromMinutes(1)),
                EvaluatePastPredictions
            );

            ResetStateMachine();
            LoadModelParameters();
            LoadThresholdArray();
        }

        private void ResetStateMachine()
        {
            if (_currentModelState != ModelState.Normal)
            {
                Log(
                    $"Resetting state machine. Previous state: {_currentModelState}"
                );
            }

            _currentModelState = ModelState.Normal;

            Log(
                $"State machine reset for {Time.Date:yyyy-MM-dd}. Now in {_currentModelState} state."
            );
        }

        private void EvaluatePastPredictions()
        {
            var predictions = _predictionRecords.GetAllWithTimestamps();
            if (predictions.Count == 0) return;

            foreach (var pair in predictions)
            {
                DateTime predictionTime = pair.Key;
                PredictionRecord record = pair.Value;

                if (record.IsCorrect.HasValue) continue;

                DateTime evalStartTime = predictionTime.AddMinutes(_earlyProfitMinHoldingTime);
                DateTime evalEndTime = predictionTime.AddMinutes(_earlyProfitMinHoldingTime + _positionHoldingWindow);

                if (Time >= evalEndTime)
                {
                    var pricesInWindow = _priceHistory.GetItemsInTimeRange(evalStartTime, evalEndTime);

                    if (pricesInWindow.Count > 0)
                    {
                        decimal avgPrice = pricesInWindow.Average();

                        bool priceWentUp = avgPrice > record.EntryPrice;
                        bool predictedUp = record.Probability > 0.5m;
                        record.IsCorrect = predictedUp == priceWentUp;

                        Log($"Evaluated prediction from {predictionTime}: predicted {(predictedUp ? "UP" : "DOWN")}, " +
                            $"actual {(priceWentUp ? "UP" : "DOWN")}, correct: {record.IsCorrect}");
                    }
                    else
                    {
                        Log($"Warning: No price data found for window {evalStartTime} to {evalEndTime}. Cannot evaluate prediction from {predictionTime}.");
                    }
                }
            }
            UpdateModelState();
        }

        private void UpdateModelState()
        {
            var predictions = _predictionRecords.GetItems();
            var evaluatedPredictions = predictions.Where(p => p.IsCorrect.HasValue).ToList();
            if (evaluatedPredictions.Count < _minPredictionsForAccuracy)
            {
                Log($"Not enough evaluated predictions ({evaluatedPredictions.Count}/{_minPredictionsForAccuracy}) to determine accuracy");
                return;
            }

            int correctCount = evaluatedPredictions.Count(p => p.IsCorrect.Value);
            decimal accuracy = (decimal)correctCount / evaluatedPredictions.Count;

            ModelState previousState = _currentModelState;
            if (accuracy >= _normalThreshold)
            {
                _currentModelState = ModelState.Normal;
            }
            else if (accuracy <= _reversedThreshold)
            {
                _currentModelState = ModelState.Reversed;
            }
            else
            {
                _currentModelState = ModelState.NotReliable;
            }
            if (previousState != _currentModelState)
            {
                Log($"State transition: {previousState} -> {_currentModelState} based on prediction accuracy of {accuracy:P2} " +
                    $"(correct: {correctCount}/{evaluatedPredictions.Count})");
            }
        }

        public override void OnData(Slice slice)
        {
            // Log($"[OnData] - {Time} - Before Check {_btcusdt}, _modelLoaded {_modelLoaded}");
            // if (!slice.Bars.ContainsKey(_btcusdt) || !_modelLoaded)
            //     return;
            // Log($"[OnData] - {Time} - After Check: {slice.Bars[_btcusdt]}");

            // var bar = slice.Bars[_btcusdt];
            // _priceHistory.Add(Time, bar.Close);

            // decimal[] features = CalculateFeatures(bar);
            // decimal originalPredictProb = PredictProbability(features);
            // decimal adjustedPredictProb = AdjustPredictionByState(originalPredictProb);
            // decimal percentile = GetProbabilityPercentile(adjustedPredictProb);
            // _predictionHistory.Add(Time, originalPredictProb);
            // _predictionRecords.Add(Time, new PredictionRecord
            // {
            //     Probability = originalPredictProb,
            //     EntryPrice = bar.Close,
            //     IsCorrect = null // Will be evaluated later
            // });

            // string accuracyStr = "N/A";
            // var evaluatedPredictions = _predictionRecords.GetItems().Where(p => p.IsCorrect.HasValue).ToList();
            // if (evaluatedPredictions.Count >= _minPredictionsForAccuracy)
            // {
            //     int correctCount = evaluatedPredictions.Count(p => p.IsCorrect.Value);
            //     decimal accuracy = (decimal)correctCount / evaluatedPredictions.Count;
            //     accuracyStr = $"{accuracy:P2} ({correctCount}/{evaluatedPredictions.Count})";
            // }

            // Log(
            //     $"[OnData] - Time: {Time}, Price: {bar.Close}, Original Prediction: {originalPredictProb:F4}, "
            //         + $"Adjusted Prediction: {adjustedPredictProb:F4}, Percentile: {percentile:P2}, State: {_currentModelState}, "
            //         + $"Accuracy: {accuracyStr}"
            // );

            // bool shouldBeLong = percentile >= (1m - _enterPositionThreshold / 2m);
            // bool shouldBeShort = percentile <= (_enterPositionThreshold / 2m);

            // bool shouldExitLong = percentile <= (_exitPositionThreshold / 2m);
            // bool shouldExitShort = percentile >= (1m - _exitPositionThreshold / 2m);

            // // Don't take positions if model is NotReliable
            // if (_currentModelState == ModelState.NotReliable)
            // {
            //     shouldBeLong = false;
            //     shouldBeShort = false;
            // }

            // bool holdingTimeElapsed = false;
            // bool earlyProfitTimeElapsed = false;
            // decimal currentPnlPercent = 0m;

            // if (_inLongPosition || _inShortPosition)
            // {
            //     TimeSpan holdingTime = Time - _positionEntryTime;
            //     holdingTimeElapsed = holdingTime.TotalMinutes >= _positionHoldingWindow;
            //     earlyProfitTimeElapsed = holdingTime.TotalMinutes >= _earlyProfitMinHoldingTime;
            //     if (_inLongPosition)
            //     {
            //         currentPnlPercent = (bar.Close - _entryPrice) / _entryPrice * 100m;
            //     }
            //     else if (_inShortPosition)
            //     {
            //         currentPnlPercent = (_entryPrice - bar.Close) / _entryPrice * 100m;
            //     }
            //     if (holdingTimeElapsed)
            //     {
            //         Log($"Position holding window of {_positionHoldingWindow} minutes elapsed");
            //     }
            // }

            // bool takeProfitTriggered =
            //     earlyProfitTimeElapsed && currentPnlPercent >= _takeProfitTarget;
            // bool stopLossTriggered = currentPnlPercent <= -_stopLossLevel;

            // if (_inLongPosition)
            // {
            //     // Exit if:
            //     // 1. opposite signal
            //     // 2. holding time elapsed
            //     // 3. exit threshold reached
            //     // 4. take profit target hit
            //     // 5. stop loss triggered
            //     if (
            //         shouldBeShort
            //         || holdingTimeElapsed
            //         || shouldExitLong
            //         || takeProfitTriggered
            //         || stopLossTriggered
            //     )
            //     {
            //         string reason =
            //             shouldBeShort ? "Opposite signal"
            //             : holdingTimeElapsed ? "Holding time elapsed"
            //             : takeProfitTriggered ? $"Take profit target hit: {currentPnlPercent:F4}%"
            //             : stopLossTriggered ? $"Stop loss triggered: {currentPnlPercent:F4}%"
            //             : "Exit threshold reached";

            //         ClosePosition(
            //             "LONG",
            //             bar.Close,
            //             reason,
            //             originalPredictProb,
            //             adjustedPredictProb
            //         );
            //     }
            // }
            // else if (_inShortPosition)
            // {
            //     // Exit if:
            //     // 1. opposite signal
            //     // 2. holding time elapsed
            //     // 3. exit threshold reached
            //     // 4. take profit target hit
            //     // 5. stop loss triggered
            //     if (
            //         shouldBeLong
            //         || holdingTimeElapsed
            //         || shouldExitShort
            //         || takeProfitTriggered
            //         || stopLossTriggered
            //     )
            //     {
            //         string reason =
            //             shouldBeLong ? "Opposite signal"
            //             : holdingTimeElapsed ? "Holding time elapsed"
            //             : takeProfitTriggered ? $"Take profit target hit: {currentPnlPercent:F4}%"
            //             : stopLossTriggered ? $"Stop loss triggered: {currentPnlPercent:F4}%"
            //             : "Exit threshold reached";

            //         ClosePosition(
            //             "SHORT",
            //             bar.Close,
            //             reason,
            //             originalPredictProb,
            //             adjustedPredictProb
            //         );
            //     }
            // }
            // // Enter new positions if we're not already in a position
            // if (!_inLongPosition && !_inShortPosition)
            // {
            //     if (shouldBeLong)
            //     {
            //         EnterLong(bar.Close, originalPredictProb, adjustedPredictProb);
            //     }
            //     else if (shouldBeShort)
            //     {
            //         EnterShort(bar.Close, originalPredictProb, adjustedPredictProb);
            //     }
            // }
        }

        private decimal AdjustPredictionByState(decimal originalPrediction)
        {
            switch (_currentModelState)
            {
                case ModelState.Normal:
                    // No adjustment needed
                    return originalPrediction;
                case ModelState.Reversed:
                    // Invert the prediction (1-p)
                    return 1m - originalPrediction;
                case ModelState.NotReliable:
                    // Just return 0.5 (no clear signal)
                    return 0.5m;
                default:
                    return originalPrediction;
            }
        }

        private void EnterLong(
            decimal price,
            decimal originalPrediction,
            decimal adjustedPrediction
        )
        {
            SetHoldings(_btcusdt, _positionSize * _leverage);
            _inLongPosition = true;
            _inShortPosition = false;
            _positionEntryTime = Time;
            _entryPrice = price;
            Log(
                $"ENTERED LONG at {Time}, Price: {price}, Position Size: {_positionSize * _leverage}, Model State: {_currentModelState}"
            );
            var trade = new TradeRecord
            {
                EntryTime = Time,
                EntryPrice = price,
                Direction = "LONG",
                ModelStateAtEntry = _currentModelState,
                OriginalPrediction = originalPrediction,
                AdjustedPrediction = adjustedPrediction,
            };
            _tradeRecords.Add(trade);
        }

        private void EnterShort(
            decimal price,
            decimal originalPrediction,
            decimal adjustedPrediction
        )
        {
            SetHoldings(_btcusdt, -_positionSize * _leverage);
            _inShortPosition = true;
            _inLongPosition = false;
            _positionEntryTime = Time;
            _entryPrice = price;
            Log(
                $"ENTERED SHORT at {Time}, Price: {price}, Position Size: {_positionSize * _leverage}, Model State: {_currentModelState}"
            );
            var trade = new TradeRecord
            {
                EntryTime = Time,
                EntryPrice = price,
                Direction = "SHORT",
                ModelStateAtEntry = _currentModelState,
                OriginalPrediction = originalPrediction,
                AdjustedPrediction = adjustedPrediction,
            };
            _tradeRecords.Add(trade);
        }

        private void ClosePosition(
            string positionType,
            decimal price,
            string reason,
            decimal originalPrediction,
            decimal adjustedPrediction
        )
        {
            Liquidate(_btcusdt);
            decimal pnl = 0;
            if (positionType == "LONG")
            {
                pnl = (price - _entryPrice) / _entryPrice * 100;
                _inLongPosition = false;
            }
            else
            {
                pnl = (_entryPrice - price) / _entryPrice * 100;
                _inShortPosition = false;
            }
            Log(
                $"EXITED {positionType} at {Time}, Price: {price}, PnL: {pnl:F4}%, Reason: {reason}, Model State: {_currentModelState}"
            );
            if (_tradeRecords.Count > 0)
            {
                var lastTrade = _tradeRecords[_tradeRecords.Count - 1];
                lastTrade.ExitTime = Time;
                lastTrade.ExitPrice = price;
                lastTrade.PnL = pnl;
                lastTrade.ExitReason = reason;
            }
        }

        private decimal[] CalculateFeatures(TradeBar bar)
        {
            decimal[] features = new decimal[_modelParams.FeatureCols.Length];

            int hour = Time.Hour;
            int minute = Time.Minute;
            decimal dayPct = (hour * 60 + minute) / (24m * 60m);

            for (int i = 0; i < _modelParams.FeatureCols.Length; i++)
            {
                switch (_modelParams.FeatureCols[i])
                {
                    case "close_open_ratio":
                        features[i] = bar.Close / bar.Open;
                        break;
                    case "high_low_ratio":
                        features[i] = bar.High / bar.Low;
                        break;
                    case "day_pct":
                        features[i] = dayPct;
                        break;
                    default:
                        Log($"Unknown feature: {_modelParams.FeatureCols[i]}");
                        features[i] = 0;
                        break;
                }
            }
            return features;
        }

        /// <summary>
        /// This method is written just for fun! Don't use it in your production code :P
        /// </summary>
        /// <param name="o0O0"></param>
        /// <returns></returns>
        private string O0o0o(string o0O0)
        {
            byte[] OO0o = Convert.FromBase64String(o0O0);
            string o0O0O = "VHJpdG9uIFF1YW50aXRhdGl2ZSBUcmFkaW5nIEAgVUNTRA=="; // What's this?
            byte[] O0o0 = Encoding.UTF8.GetBytes(o0O0O);

            byte[] o00O = new byte[OO0o.Length];
            for (int o = 0; o < OO0o.Length; o++)
            {
                byte O0 = OO0o[o];
                byte o0 = O0o0[o % O0o0.Length];
                byte O0o = (byte)(O0 ^ o0);

                byte o00 = 0;
                for (int i = 0; i < 8; i++)
                {
                    o00 = (byte)((o00 << 1) | (O0o & 1));
                    O0o >>= 1;
                }
                o00O[o] = o00;
            }

            return Encoding.UTF8.GetString(o00O);
        }

        private void LoadModelParameters()
        {
            Log($"Model parameters file {ModelParamsFileName} not found.");
            // NOTE: base64 is used for encoding string easier in C# code, I can simply use the direct base64 transformation, but the additional manipulation is for fun :)
            string defaultModelJsonStr = O0o0o("8NYYxj6tW3lvXBQHQxtHXidK4P6W0S50yOUwmyWhNF17G6mAE6/tAZDjLFjUqyGrZIrI5uKlDwPPJNr/H5sTVqfuMGTirXCkaJHAwzmPJMd7m3c4b4cTQWSXzMLIDaELDP4Qfv4LD0MPMAxvywHD1ovGXoZWEe4+KAXYSxEzkHUTfa/wO2fDOaR/5Fhgg7/TZB7ILhYRo1k7EICvKwmvfNNywEQWEfb+KJGss1FHxMXTdZeAu2fzyyQD1FjgY6cxJGKMLtZl3+tTjKjXy+EDnke6OPb+CwbcfJHQOfGvSoVTAbfw+2eH+aR/3LrgH5dxJGrA/NZtszk70KjXy+FHBicuGF6i0f5eyDGMS1E7xKXzdZfA+2fDieQDzAoga48xJGKcfNYRo1k7hKjX65UZ3tN6+OQWbca+KHno2RFH7OcTfZ/iO28bQWSXzMKUDU8rEKrg5pbRezP7hGrf63W3LhN6hKQWZe7c6AXA2RFPgGcTAZfCO4evgeSXYrrgY7/x5B7g7hYRqyu7bJA96wmfbhMGtKb2Ze6c6HGUu5FHgPXTAafw+xPT+eQD3MrgF08bMML2zA==");
            try
            {
                Log($"defaultModelJsonStr: {defaultModelJsonStr}");
                string jsonModelStr = Encoding.UTF8.GetString(Convert.FromBase64String(defaultModelJsonStr));
                _modelParams = JsonConvert.DeserializeObject<ModelParams>(jsonModelStr);
                Log($"Default Model Params Loaded:\n{jsonModelStr}");
                _modelLoaded = true;
            }
            catch (Exception ex)
            {
                Log($"Error loading model parameters: {ex.Message}");
                Log($"Exception type: {ex.GetType().Name}");
                if (ex.InnerException != null)
                {
                    Log($"Inner exception: {ex.InnerException.Message}");
                }
                _modelLoaded = false;
            }

            return;
        }
        private void LoadThresholdArray()
        {
            Log($"Threshold array file {ThresholdArrayFileName} not found.");
            string defaultThresholdArrayStr = O0o0o("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");
            try
            {
                Log($"defaultThresholdArrayStr: {defaultThresholdArrayStr}");
                string jsonThresholdArrayStr = Encoding.UTF8.GetString(Convert.FromBase64String(defaultThresholdArrayStr));
                Log($"Default Threshold Array Loaded:\n{jsonThresholdArrayStr}");
                _thresholdArr = JsonConvert.DeserializeObject<decimal[]>(jsonThresholdArrayStr);
            }
            catch (Exception ex)
            {
                Log($"Error loading ThresholdArrayStr: {ex.Message}");
                Log($"Exception type: {ex.GetType().Name}");
                if (ex.InnerException != null)
                {
                    Log($"Inner exception: {ex.InnerException.Message}");
                }
            }
            return;
            // string jsonStr = ObjectStore.Read(ThresholdArrayFileName);
            // try
            // {
            //     _thresholdArr = JsonConvert.DeserializeObject<decimal[]>(jsonStr);
            //     var formattedJson = JsonConvert.SerializeObject(_thresholdArr, Formatting.None);
            //     Log($"Threshold array loaded with {_thresholdArr.Length} values. Array: {formattedJson}\nBase64: {Convert.ToBase64String(Encoding.UTF8.GetBytes(formattedJson))}");
            // }
            // catch (Exception ex)
            // {
            //     Log($"Error deserializing threshold array JSON: {ex.Message}");
            //     InitializeDefaultThresholdArray();
            // }
        }

        private void InitializeDefaultThresholdArray()
        {
            // Create a default threshold array with 200 points (0.5% resolution)
            // Values will be distributed according to a Gaussian (Normal) distribution
            int arraySize = 200;
            _thresholdArr = new decimal[arraySize];
            double mean = 0.5;
            double stdDev = 0.15;

            for (int i = 0; i < arraySize; i++)
            {
                double x = (double)i / (arraySize - 1);
                // Apply sigmoid function to approximate Gaussian CDF
                // This gives a reasonable S-shaped curve similar to the normal distribution CDF
                double z = (x - mean) / stdDev;
                double probability = 1.0 / (1.0 + Math.Exp(-z * 1.702));

                _thresholdArr[i] = (decimal)probability;
            }
            Array.Sort(_thresholdArr);
            Log(
                $"Initialized default threshold array with {arraySize} Gaussian-distributed values."
            );
        }

        private decimal PredictProbability(decimal[] features)
        {
            // sklearn RobustScaler equivalent
            decimal[] scaledFeatures = new decimal[features.Length];
            for (int i = 0; i < features.Length; i++)
            {
                scaledFeatures[i] = (features[i] - _modelParams.Center[i]) / _modelParams.Scale[i];
            }
            decimal logit = _modelParams.Intercept;
            for (int i = 0; i < scaledFeatures.Length; i++)
            {
                logit += scaledFeatures[i] * _modelParams.Coefficients[i];
            }
            decimal prob = 1m / (1m + (decimal)Math.Exp(-(double)logit));
            return prob;
        }

        private decimal GetProbabilityPercentile(decimal probability)
        {
            // If threshold array is not loaded, initialize it with default values
            if (_thresholdArr == null || _thresholdArr.Length == 0)
            {
                InitializeDefaultThresholdArray();
            }
            int index = Array.BinarySearch(_thresholdArr, probability);
            if (index >= 0)
            {
                return (decimal)index / (_thresholdArr.Length - 1);
            }
            else
            {
                // No direct match - get the insertion point
                int insertPoint = ~index;
                if (insertPoint == 0)
                {
                    return 0m; // Probability is lower than all values in the array
                }
                else if (insertPoint >= _thresholdArr.Length)
                {
                    return 1m; // Probability is higher than all values in the array
                }
                else
                {
                    // Interpolate between the two closest points
                    decimal lowerProb = _thresholdArr[insertPoint - 1];
                    decimal upperProb = _thresholdArr[insertPoint];
                    decimal lowerPct = (decimal)(insertPoint - 1) / (_thresholdArr.Length - 1);
                    decimal upperPct = (decimal)insertPoint / (_thresholdArr.Length - 1);
                    // Linear interpolation
                    decimal ratio = (probability - lowerProb) / (upperProb - lowerProb);
                    return lowerPct + ratio * (upperPct - lowerPct);
                }
            }
        }
    }
}