Crypto Currency Software Monitor

  • 1.3K Views
  • Last Post 4 days ago
Chris posted this 06 March 2025

My Friends,

I want to share some of my work on Crypto, and Software tracking changes in the Market. I am using AI as Prediction Engines, Data Engines, to download data and so on.

Disclaimer:

I am not a Financial Expert, what I share with you is just an experiment, so please take from it what you will! Crypto is risky, you can very easily loose your money! Any of my comments must not be accepted as advice, it is not, and no responsibility will be accepted. You must accept the risks and your own activities are your own responsibility.

 

Datasets

There are very few Datasets available for Crypto! Its a real shame, because with the right Dataset, one could do a lot more to make accurate predictions! I have put together a Dataset: Here

  • Size: 270Mb
  • Resolutions: 5, 15, 30, 60, 120, 240, D, 3D, W
  • Cutoff Date: Thursday 6th March 2025
  • Download: Here

 

// Get the Data and read in as a Json String:
string jsonString = File.ReadAllText("Path to your data");

// Deserialize the latest price data:
List<LatestPrice> dataset = JsonSerializer.Deserialize<List<LatestPrice>>(jsonString);

    /// <summary>
    /// Represents a cryptocurrency price update with status and candlestick data.
    /// </summary>
    public class LatestPrice
    {
        /// <summary>
        /// The status of the price update (e.g., "ok" indicates successful data).
        /// </summary>
        [JsonPropertyName("s")]
        public string Status { get; set; }

        /// <summary>
        /// The current or closing price of the cryptocurrency.
        /// </summary>
        [JsonPropertyName("price")]
        public double Price { get; set; }

        /// <summary>
        /// The opening price of the cryptocurrency for the period.
        /// </summary>
        [JsonPropertyName("open")]
        public double OpenPrice { get; set; }

        /// <summary>
        /// The lowest price of the cryptocurrency during the period.
        /// </summary>
        [JsonPropertyName("low")]
        public double LowPrice { get; set; }

        /// <summary>
        /// The highest price of the cryptocurrency during the period.
        /// </summary>
        [JsonPropertyName("high")]
        public double HighPrice { get; set; }

        /// <summary>
        /// The highest price of the cryptocurrency during the period.
        /// </summary>
        public int Timestamp { get; internal set; }
    }

 

Typically, one data record would look like:

  {
    "s": "ok",
    "price": 145567.4031468,
    "open": 145123.87091991,
    "low": 140956.87897272,
    "high": 148669.89984942,
    "Timestamp": 1741254545
  }

 

I think most would agree, the OHLCV Data, Open, High, Low, Close and Volume Data, is the best data to look at. However, this data does not include the most important data, the Tick Data or the 10 Second Data!

 

The One Tick Data

The Tick Data or some refer to the 10 Second Data is the data we see in the Charts, every update, where the line on the chart moves with every Update, each Candle displayed is made up of X number of Ticks, which are Market updates for the timeframe given:

 

Above, the the Chart, we are on the One Hour time frame, which is the 60 minutes shown. If one watches the Chart, we have a Candle for every One Hour shown. However, the Red Line, shown at: 4.068133, is on a different Time Frame, typically, it is 10 Seconds, and moves up or Down according to market value, every 10 Seconds. So, you ask: "Why is this important?"

This data is important, if one wants to make predictions on Market movement, one can evaluate this 10 second data to see big shifts in the market very early on in the game!

For Example:

Each Candle shown is for One Hour, and this is how a Candle works:

 

Where:

  • Bullish Candle = Increase in Market Value.
  • Bearish Candle = Decrease in Market Value.

 

This means, each Candle might be made up of:

[
  {
    "s": "ok",
    "price": 145567.4031468,
    "open": 145123.87091991,
    "low": 140956.87897272,
    "high": 148669.89984942,
    "Timestamp": 1741254545
  },
  {
    "s": "ok",
    "price": 145520.46327524,
    "open": 145123.87091991,
    "low": 140956.87897272,
    "high": 148669.89984942,
    "Timestamp": 1741254614
  },
  {
    "s": "ok",
    "price": 145564.42804449,
    "open": 145123.87091991,
    "low": 140956.87897272,
    "high": 148669.89984942,
    "Timestamp": 1741254654
  },
  {
    "s": "ok",
    "price": 145617.9267137,
    "open": 145123.87091991,
    "low": 140956.87897272,
    "high": 148669.89984942,
    "Timestamp": 1741254690
  },
  {
    "s": "ok",
    "price": 145578.68046191,
    "open": 145123.87091991,
    "low": 140956.87897272,
    "high": 148669.89984942,
    "Timestamp": 1741254727
  },
  {
    "s": "ok",
    "price": 145637.58190353,
    "open": 145123.87091991,
    "low": 140956.87897272,
    "high": 148669.89984942,
    "Timestamp": 1741254761
  },
  {
    "s": "ok",
    "price": 145618.64815215,
    "open": 145123.87091991,
    "low": 140956.87897272,
    "high": 148669.89984942,
    "Timestamp": 1741254797
  },
  {
    "s": "ok",
    "price": 145580.54016992,
    "open": 145123.87091991,
    "low": 140956.87897272,
    "high": 148669.89984942,
    "Timestamp": 1741254830
  },
  {
    "s": "ok",
    "price": 145531.77093057,
    "open": 145123.87091991,
    "low": 140956.87897272,
    "high": 148669.89984942,
    "Timestamp": 1741254865
  },
  {
    "s": "ok",
    "price": 145509.7590418,
    "open": 145123.87091991,
    "low": 140956.87897272,
    "high": 148669.89984942,
    "Timestamp": 1741254900
  },
  {
    "s": "ok",
    "price": 145317.62793503,
    "open": 145123.87091991,
    "low": 140956.87897272,
    "high": 148669.89984942,
    "Timestamp": 1741254937
  },
  {
    "s": "ok",
    "price": 145433.38322856,
    "open": 145123.87091991,
    "low": 140956.87897272,
    "high": 148669.89984942,
    "Timestamp": 1741254975
  },
  {
    "s": "ok",
    "price": 145439.97079788,
    "open": 145123.87091991,
    "low": 140956.87897272,
    "high": 148669.89984942,
    "Timestamp": 1741255008
  },
  {
    "s": "ok",
    "price": 145439.44186895,
    "open": 145123.87091991,
    "low": 140956.87897272,
    "high": 148669.89984942,
    "Timestamp": 1741255052
  },
  {
    "s": "ok",
    "price": 145460.67916664,
    "open": 145123.87091991,
    "low": 140956.87897272,
    "high": 148669.89984942,
    "Timestamp": 1741255089
  },
  {
    "s": "ok",
    "price": 145436.23623911,
    "open": 145123.87091991,
    "low": 140956.87897272,
    "high": 148669.89984942,
    "Timestamp": 1741255121
  },
  {
    "s": "ok",
    "price": 145420.62482179,
    "open": 145123.87091991,
    "low": 140956.87897272,
    "high": 148669.89984942,
    "Timestamp": 1741255155
  },
  {
    "s": "ok",
    "price": 145458.88401393,
    "open": 145123.87091991,
    "low": 140956.87897272,
    "high": 148669.89984942,
    "Timestamp": 1741255186
  }
]

 

Of course, at Ten Seconds, in Sixty Minutes, we would have: 

  1. Convert 60 minutes to seconds:
    • 60 minutes × 60 seconds/minute = 3,600 seconds
  2. Divide total seconds by 10-second intervals:
    • 3,600 seconds ÷ 10 seconds/interval = 360 intervals

So, there are 360 ten-second intervals in 60 minutes, thus making up one Candle on the One Hour interval.

 

The Input Schema Class: AIBreakoutInputSchema.cs

namespace AdvancedCryptoTrader.AI
{



    #region Using Statements:



    using Microsoft.ML.Data;



    #endregion



    /// <summary>
    /// Represents the input schema for breakout prediction, including both historical and short-term features.
    /// </summary>
    public class AIBreakoutInputSchema
    {
        /// <summary>
        /// The label for the breakout prediction (optional for training, "Bullish", "Bearish", or "NoBreakout").
        /// </summary>
        [ColumnName("Label")]
        public string Label { get; set; }

        /// <summary>
        /// Historical timestamp in Unix seconds for the candlestick.
        /// </summary>
        [ColumnName("Timestamp")]
        public long Timestamp { get; set; }

        /// <summary>
        /// Closing price of the historical candlestick.
        /// </summary>
        [ColumnName("ClosePrice")]
        public float ClosePrice { get; set; }

        /// <summary>
        /// Opening price of the historical candlestick.
        /// </summary>
        [ColumnName("OpenPrice")]
        public float OpenPrice { get; set; }

        /// <summary>
        /// Highest price during the historical candlestick period.
        /// </summary>
        [ColumnName("HighPrice")]
        public float HighPrice { get; set; }

        /// <summary>
        /// Lowest price during the historical candlestick period.
        /// </summary>
        [ColumnName("LowPrice")]
        public float LowPrice { get; set; }

        /// <summary>
        /// Trading volume during the historical candlestick period.
        /// </summary>
        [ColumnName("Volume")]
        public float Volume { get; set; }

        /// <summary>
        /// Price change percentage for the historical candlestick ((Close - Open) / Open * 100).
        /// </summary>
        [ColumnName("PriceChangePct")]
        public float PriceChangePct { get; set; }

        /// <summary>
        /// Price range for the historical candlestick (High - Low).
        /// </summary>
        [ColumnName("PriceRange")]
        public float PriceRange { get; set; }

        /// <summary>
        /// Simple Moving Average over a lookback period (e.g., 20 periods).
        /// </summary>
        [ColumnName("Sma")]
        public float Sma { get; set; }

        /// <summary>
        /// Relative Strength Index over a lookback period (e.g., 14 periods).
        /// </summary>
        [ColumnName("Rsi")]
        public float Rsi { get; set; }

        /// <summary>
        /// Average True Range over a lookback period (e.g., 14 periods) for historical volatility.
        /// </summary>
        [ColumnName("Atr")]
        public float Atr { get; set; }

        /// <summary>
        /// Short-term (10-second) price difference magnitude (absolute value of Close price change over 10 seconds).
        /// </summary>
        [ColumnName("ShortTermPriceDiffMagnitude")]
        public float ShortTermPriceDiffMagnitude { get; set; }

        /// <summary>
        /// Short-term (10-second) rolling average of price difference magnitude over a window (e.g., 50 updates, 500 seconds).
        /// </summary>
        [ColumnName("ShortTermPriceDiffRollingAvg")]
        public float ShortTermPriceDiffRollingAvg { get; set; }

        /// <summary>
        /// Short-term (10-second) volatility (standard deviation of price differences over a window).
        /// </summary>
        [ColumnName("ShortTermVolatility")]
        public float ShortTermVolatility { get; set; }

        /// <summary>
        /// Slope of short-term price movement over a window (e.g., last 20 updates, 200 seconds).
        /// </summary>
        [ColumnName("ShortTermPriceSlope")]
        public float ShortTermPriceSlope { get; set; }
    }
}

 

Here is the C# ML.NET Class: AIBreakoutPredictor.cs

namespace AdvancedCryptoTrader.AI
{



    #region Using Statements:



    using Microsoft.ML;
    using Microsoft.ML.Data;
    using Microsoft.ML.Transforms;

    using System;
    using System.Linq;
    using System.Collections.Generic;



    #endregion


    /// <summary>
    /// AIBreakoutPredictor class that uses a Transformer model to predict market breakout trends (bullish, bearish, or no breakout)
    /// based on historical and short-term (10-second) price data.
    /// </summary>
    public partial class AIBreakoutPredictor
    {



        #region Fields:



        /// <summary>
        /// The MLContext for the Machine Learning Pipeline.
        /// </summary>
        private readonly MLContext _mlContext;



        /// <summary>
        /// The Transformer Model.
        /// </summary>
        private ITransformer _model;



        /// <summary>
        /// Historical Loopback Period.
        /// </summary>
        private const int HistoricalLookbackPeriod = 50; // Number of historical candlesticks (e.g., 1H) for sequence



        /// <summary>
        /// Shortterm Loopback Period.
        /// </summary>
        private const int ShortTermLookbackPeriod = 50;  // Number of 10-second updates for short-term features



        /// <summary>
        /// Breakout threshold.
        /// </summary>
        private const float BreakoutThreshold = 0.05f;   // 5% price change threshold for defining breakouts



        #endregion



        #region Properties:



        #endregion



        /// <summary>
        /// Initializes a new instance of the AIBreakoutPredictor class.
        /// </summary>
        /// <param name="ailogger">Optional logging action for debugging and monitoring. Defaults to Console.WriteLine if null.</param>
        public AIBreakoutPredictor(Action<string> ailogger = null)
        {

            _mlContext = new MLContext(seed: 0);
            LoadModelFromDisk(ailogger ?? (s => Console.WriteLine(s))); // Load existing model or start fresh
        }



        /// <summary>
        /// Trains the Transformer model with historical and short-term price data to predict breakouts.
        /// </summary>
        /// <param name="prices">List of Price objects containing historical candlestick data (e.g., 1H timeframe).</param>
        /// <param name="shortTermPrices">List of Price objects containing short-term (10-second) price updates.</param>
        /// <param name="ailogger">Logging action for training progress and errors.</param>
        /// <summary>
        /// Trains the Transformer model with historical and short-term price data to predict breakouts.
        /// </summary>
        /// <param name="prices">List of Price objects containing historical candlestick data (e.g., 1H timeframe).</param>
        /// <param name="shortTermPrices">List of Price objects containing short-term (10-second) price updates.</param>
        /// <param name="ailogger">Logging action for training progress and errors.</param>
        public void Train(List<Price> prices, List<Price> shortTermPrices, Action<string> ailogger)
        {

            if (prices == null || prices.Count == 0 || shortTermPrices == null || shortTermPrices.Count == 0)
            {
                ailogger("Train: Error - No price data provided for training.");
                throw new ArgumentException("Price data cannot be null or empty.");
            }

            ailogger($"Train: Processing {prices.Count} historical candlesticks and {shortTermPrices.Count} short-term updates.");

            // Calculate features from historical and short-term data
            var historicalFeatures = CalculateHistoricalFeatures(prices).ToList();
            var shortTermFeatures = CalculateShortTermFeatures(shortTermPrices).ToList();

            // Align historical and short-term data by timestamp
            var trainingData = AlignData(historicalFeatures, shortTermFeatures, ailogger).ToList();

            if (trainingData.Count == 0)
            {
                ailogger("Train: Error - No aligned training data generated.");
                throw new InvalidOperationException("No training data available after alignment.");
            }

            ailogger($"Train: Generated {trainingData.Count} training records. Sample - Timestamp={trainingData.First().Timestamp}, ClosePrice={trainingData.First().ClosePrice:F2}, ShortTermPriceDiffMagnitude={trainingData.First().ShortTermPriceDiffMagnitude:F2}");

            // Create ML.NET data view
            var dataView = _mlContext.Data.LoadFromEnumerable(trainingData);
            ailogger("Train: DataView created for Transformer training.");

            // Define the pipeline for non-sequence multiclass classification (no Sequence transform for older ML.NET)
            var pipeline = _mlContext.Transforms
                .Conversion.MapValueToKey("Label", "Label") // Convert string labels to keys for multiclass
                .Append(_mlContext.Transforms.Concatenate("Features", // Concatenate all numeric features
                    "ClosePrice", "OpenPrice", "HighPrice", "LowPrice", "Volume", "PriceChangePct", "PriceRange",
                    "Sma", "Rsi", "Atr", "ShortTermPriceDiffMagnitude", "ShortTermPriceDiffRollingAvg",
                    "ShortTermVolatility", "ShortTermPriceSlope"))
                .Append(_mlContext.Transforms.NormalizeMinMax("Features")) // Normalize features for better training
                .Append(_mlContext.MulticlassClassification.Trainers
                    .LbfgsMaximumEntropy(
                        labelColumnName: "Label",
                        featureColumnName: "Features",
                        l1Regularization: 1f,
                        l2Regularization: 1f,
                        optimizationTolerance: 1E-07f,
                        historySize: 20,
                        enforceNonNegativity: false)) // Use L-BFGS for multiclass without advancedSettings
                .Append(_mlContext.Transforms.Conversion.MapKeyToValue("PredictedLabel", "PredictedLabel")); // Convert keys back to labels

            ailogger("Train: Pipeline defined for L-BFGS multiclass model.");
            _model = pipeline.Fit(dataView);
            ailogger("Train: Model training completed successfully.");

            SaveModelToDisk(ailogger); // Save the trained model
        }



        /// <summary>
        /// Predicts the next breakout trend (Bullish, Bearish, or NoBreakout) for a sequence of prices.
        /// </summary>
        /// <param name="prices">List of historical Price objects (e.g., 1H candlesticks).</param>
        /// <param name="shortTermPrices">List of short-term (10-second) Price updates.</param>
        /// <param name="ailogger">Logging action for prediction details.</param>
        /// <returns>Predicted breakout trend with probability scores and confidence.</returns>
        public AIBreakoutOutputSchema Predict(List<Price> prices, List<Price> shortTermPrices, Action<string> ailogger)
        {

            if (_model == null)
                throw new InvalidOperationException("Model must be trained before predicting.");

            if (prices == null || prices.Count == 0 || shortTermPrices == null || shortTermPrices.Count == 0)
            {
                ailogger("Predict: Error - No price data provided for prediction.");
                throw new ArgumentException("Price data cannot be null or empty.");
            }

            ailogger($"Predict: Processing {prices.Count} historical candlesticks and {shortTermPrices.Count} short-term updates.");

            // Calculate features from historical and short-term data
            var historicalFeatures = CalculateHistoricalFeatures(prices).ToList();
            var shortTermFeatures = CalculateShortTermFeatures(shortTermPrices).ToList();

            // Align data for prediction
            var predictionInput = AlignData(historicalFeatures, shortTermFeatures, ailogger).Last();

            // Create prediction engine for single-row prediction
            var predEngine = _mlContext.Model.CreatePredictionEngine<AIBreakoutInputSchema, AIBreakoutOutputSchema>(_model);
            var prediction = predEngine.Predict(predictionInput);

            // Calculate confidence as the highest probability
            prediction.Confidence = prediction.Probabilities.Max();

            ailogger($"Predict: Output - PredictedLabel={prediction.PredictedLabel}, Probabilities=[{string.Join(", ", prediction.Probabilities.Select(p => p.ToString("F2")))}], Confidence={prediction.Confidence:F2}");
            return prediction;
        }



        /// <summary>
        /// Calculates technical and derived features from historical price data (e.g., 1H candlesticks).
        /// </summary>
        /// <param name="prices">List of Price objects for historical data.</param>
        /// <returns>Enumerable of InputBreakoutSchema with calculated features.</returns>
        private IEnumerable<AIBreakoutInputSchema> CalculateHistoricalFeatures(List<Price> prices)
        {

            var priceList = prices.ToList();
            var features = new List<AIBreakoutInputSchema>();

            for (int i = 0; i < priceList.Count; i++)
            {
                var price = priceList[i];
                var feature = new AIBreakoutInputSchema
                {
                    Timestamp = price.Timestamp,
                    ClosePrice = (float)price.ClosePrice,
                    OpenPrice = (float)price.OpenPrice,
                    HighPrice = (float)price.HighPrice,
                    LowPrice = (float)price.LowPrice,
                    Volume = (float)price.Volume,
                    PriceChangePct = (float)((price.ClosePrice - price.OpenPrice) / price.OpenPrice * 100),
                    PriceRange = (float)(price.HighPrice - price.LowPrice)
                };

                // Calculate SMA (Simple Moving Average) over HistoricalLookbackPeriod
                if (i >= HistoricalLookbackPeriod)
                {
                    var periodPrices = priceList.Skip(i - HistoricalLookbackPeriod).Take(HistoricalLookbackPeriod);
                    feature.Sma = (float)periodPrices.Average(p => p.ClosePrice);
                }

                // Calculate RSI (Relative Strength Index) over HistoricalLookbackPeriod
                if (i >= HistoricalLookbackPeriod)
                {
                    var periodPrices = priceList.Skip(i - HistoricalLookbackPeriod).Take(HistoricalLookbackPeriod);
                    feature.Rsi = (float)CalculateRSI(periodPrices.Select(p => p.ClosePrice));
                }

                // Calculate ATR (Average True Range) over HistoricalLookbackPeriod
                if (i >= HistoricalLookbackPeriod)
                {
                    var periodPrices = priceList.Skip(i - HistoricalLookbackPeriod).Take(HistoricalLookbackPeriod);
                    feature.Atr = (float)CalculateATR(periodPrices);
                }

                features.Add(feature);
            }

            return features;
        }



        /// <summary>
        /// Calculates short-term features from 10-second price updates.
        /// </summary>
        /// <param name="prices">List of Price objects for 10-second updates.</param>
        /// <returns>Enumerable of short-term feature aggregates for alignment with historical data.</returns>
        private IEnumerable<AIBreakoutInputSchema> CalculateShortTermFeatures(List<Price> prices)
        {

            var priceList = prices.ToList();
            var features = new List<AIBreakoutInputSchema>();

            for (int i = 0; i < priceList.Count; i++)
            {
                var price = priceList[i];
                var feature = new AIBreakoutInputSchema
                {
                    Timestamp = price.Timestamp,
                    ClosePrice = (float)price.ClosePrice
                };

                // Calculate 10-second price difference (magnitude)
                if (i > 0)
                {
                    var prevPrice = priceList[i - 1];
                    float priceDiff = (float)(price.ClosePrice - prevPrice.ClosePrice);
                    feature.ShortTermPriceDiffMagnitude = Math.Abs(priceDiff);
                }

                // Calculate rolling average of price difference magnitude over ShortTermLookbackPeriod
                if (i >= ShortTermLookbackPeriod)
                {
                    var periodDiffs = priceList.Skip(i - ShortTermLookbackPeriod).Take(ShortTermLookbackPeriod)
                        .Zip(priceList.Skip(i - ShortTermLookbackPeriod + 1).Take(ShortTermLookbackPeriod),
                            (prev, curr) => Math.Abs((float)(curr.ClosePrice - prev.ClosePrice)));
                    feature.ShortTermPriceDiffRollingAvg = periodDiffs.Average();
                }

                // Calculate short-term volatility (standard deviation of price differences)
                if (i >= ShortTermLookbackPeriod)
                {
                    var periodDiffs = priceList.Skip(i - ShortTermLookbackPeriod).Take(ShortTermLookbackPeriod)
                        .Zip(priceList.Skip(i - ShortTermLookbackPeriod + 1).Take(ShortTermLookbackPeriod),
                            (prev, curr) => (float)(curr.ClosePrice - prev.ClosePrice));
                    feature.ShortTermVolatility = (float)periodDiffs.StandardDeviation();
                }

                // Calculate slope of price movement over ShortTermLookbackPeriod
                if (i >= ShortTermLookbackPeriod)
                {
                    var periodTimestamps = priceList.Skip(i - ShortTermLookbackPeriod).Take(ShortTermLookbackPeriod)
                        .Select(p => (double)p.Timestamp);
                    var periodPrices = priceList.Skip(i - ShortTermLookbackPeriod).Take(ShortTermLookbackPeriod)
                        .Select(p => (float)p.ClosePrice);
                    feature.ShortTermPriceSlope = (float)CalculateSlope(periodTimestamps, periodPrices);
                }

                features.Add(feature);
            }

            return features;
        }



        /// <summary>
        /// Aligns historical and short-term features by timestamp for training or prediction.
        /// </summary>
        /// <param name="historicalFeatures">Historical features from 1H candlesticks.</param>
        /// <param name="shortTermFeatures">Short-term features from 10-second updates.</param>
        /// <param name="ailogger">Logging action for alignment details.</param>
        /// <returns>Enumerable of aligned InputBreakoutSchema records.</returns>
        private IEnumerable<AIBreakoutInputSchema> AlignData(IEnumerable<AIBreakoutInputSchema> historicalFeatures, IEnumerable<AIBreakoutInputSchema> shortTermFeatures, Action<string> ailogger)
        {

            var historicalList = historicalFeatures.ToList();
            var shortTermList = shortTermFeatures.ToList();
            var alignedData = new List<AIBreakoutInputSchema>();

            // Assume short-term data is continuous and recent; align with the most recent historical data
            for (int i = 0; i < historicalList.Count; i++)
            {
                var historical = historicalList[i];
                var shortTermWindow = shortTermList
                    .Where(st => st.Timestamp >= historical.Timestamp && st.Timestamp < (i + 1 < historicalList.Count ? historicalList[i + 1].Timestamp : long.MaxValue))
                    .ToList();

                if (shortTermWindow.Any())
                {
                    var aligned = new AIBreakoutInputSchema
                    {
                        Timestamp = historical.Timestamp,
                        ClosePrice = historical.ClosePrice,
                        OpenPrice = historical.OpenPrice,
                        HighPrice = historical.HighPrice,
                        LowPrice = historical.LowPrice,
                        Volume = historical.Volume,
                        PriceChangePct = historical.PriceChangePct,
                        PriceRange = historical.PriceRange,
                        Sma = historical.Sma,
                        Rsi = historical.Rsi,
                        Atr = historical.Atr,
                        ShortTermPriceDiffMagnitude = shortTermWindow.Last().ShortTermPriceDiffMagnitude,
                        ShortTermPriceDiffRollingAvg = shortTermWindow.Last().ShortTermPriceDiffRollingAvg,
                        ShortTermVolatility = shortTermWindow.Last().ShortTermVolatility,
                        ShortTermPriceSlope = shortTermWindow.Last().ShortTermPriceSlope
                    };

                    // Set label based on historical breakout (look ahead)
                    if (i + 1 < historicalList.Count)
                    {
                        float nextPriceChange = (historicalList[i + 1].ClosePrice - historical.ClosePrice) / historical.ClosePrice;
                        aligned.Label = Math.Abs(nextPriceChange) >= BreakoutThreshold
                            ? (nextPriceChange > 0 ? "Bullish" : "Bearish")
                            : "NoBreakout";
                    }

                    alignedData.Add(aligned);
                }
            }

            ailogger($"AlignData: Aligned {alignedData.Count} records from {historicalList.Count} historical and {shortTermList.Count} short-term features.");
            return alignedData;
        }



        /// <summary>
        /// Calculates the Relative Strength Index (RSI) for a sequence of closing prices.
        /// </summary>
        /// <param name="prices">Sequence of closing prices.</param>
        /// <returns>RSI value (0-100).</returns>
        private double CalculateRSI(IEnumerable<double> prices)
        {

            var priceChanges = prices.Zip(prices.Skip(1), (prev, curr) => curr - prev).ToList();
            var gains = priceChanges.Where(x => x > 0).DefaultIfEmpty(0).Average();
            var losses = Math.Abs(priceChanges.Where(x => x < 0).DefaultIfEmpty(0).Average());

            if (losses == 0) return 100;
            var rs = gains / losses;
            return 100 - (100 / (1 + rs));
        }



        /// <summary>
        /// Calculates the Average True Range (ATR) for a sequence of prices.
        /// </summary>
        /// <param name="prices">Sequence of Price objects.</param>
        /// <returns>ATR value.</returns>
        private double CalculateATR(IEnumerable<Price> prices)
        {

            var trueRanges = prices.Zip(prices.Skip(1), (prev, curr) =>
            {
                double highLow = curr.HighPrice - curr.LowPrice;
                double highClose = Math.Abs(curr.HighPrice - prev.ClosePrice);
                double lowClose = Math.Abs(curr.LowPrice - prev.ClosePrice);
                return Math.Max(highLow, Math.Max(highClose, lowClose));
            }).ToList();

            return trueRanges.DefaultIfEmpty(0).Average();
        }



        /// <summary>
        /// Calculates the slope of a linear regression for timestamps and prices.
        /// </summary>
        /// <param name="timestamps">Sequence of timestamps (as doubles).</param>
        /// <param name="prices">Sequence of prices (as floats).</param>
        /// <returns>Slope of the linear regression line.</returns>
        private double CalculateSlope(IEnumerable<double> timestamps, IEnumerable<float> prices)
        {

            var ts = timestamps.ToList();
            var ps = prices.ToList();
            if (ts.Count != ps.Count || ts.Count < 2) return 0;

            double sumX = ts.Sum();
            double sumY = ps.Sum();
            double sumXY = ts.Zip(ps, (x, y) => x * y).Sum();
            double sumXX = ts.Sum(x => x * x);
            double n = ts.Count;

            double slope = (n * sumXY - sumX * sumY) / (n * sumXX - sumX * sumX);
            return slope;
        }



        /// <summary>
        /// Loads a pre-trained model from disk if it exists, otherwise initializes a new model.
        /// </summary>
        /// <param name="ailogger">Logging action for model loading status.</param>
        private void LoadModelFromDisk(Action<string> ailogger)
        {

            string modelPath = "Models/BreakoutPredictorModel.zip";

            if (System.IO.File.Exists(modelPath))
            {
                try
                {
                    _model = _mlContext.Model.Load(modelPath, out var schema);
                    ailogger($"Loaded BreakoutPredictor model from {modelPath}");
                }
                catch (Exception ex)
                {
                    ailogger($"Model load failed: {ex.Message}. Starting fresh!");
                    _model = null; // Reset to force retraining
                }
            }
            else
            {
                ailogger("No model found at Models/BreakoutPredictorModel.zip—starting with a new one!");
                _model = null; // Fresh start
            }
        }



        /// <summary>
        /// Saves the trained model to disk atomically to prevent corruption.
        /// </summary>
        /// <param name="ailogger">Logging action for model saving status.</param>
        private void SaveModelToDisk(Action<string> ailogger)
        {

            string modelPath = "Models/BreakoutPredictorModel.zip";
            string tempModelPath = modelPath + ".tmp";

            try
            {
                if (_model != null)
                {
                    _mlContext.Model.Save(_model, null, tempModelPath);
                    System.IO.File.Move(tempModelPath, modelPath, overwrite: true);
                    ailogger($"BreakoutPredictor model saved to {modelPath}");
                }
                else
                {
                    ailogger("No model to save—train it first!");
                }
                System.Threading.Thread.Sleep(1000); // Brief pause to ensure file operations settle
            }
            catch (Exception ex)
            {
                ailogger($"Save failed: {ex.Message}. Keeping the model in memory!");
                if (System.IO.File.Exists(tempModelPath))
                    System.IO.File.Delete(tempModelPath); // Clean up temp file on failure
            }
        }
    }



    /// <summary>
    /// BreakoutPredictor class that uses a Transformer model to predict market breakout trends (bullish, bearish, or no breakout)
    /// based on historical and short-term (10-second) price data.
    /// </summary>
    public partial class AIBreakoutPredictor
    {



        /// <summary>
        /// Tests the BreakoutPredictor by training it on historical and short-term price data, then predicting
        /// breakout trends for the current market, evaluating performance metrics like accuracy, win rate, and confidence.
        /// </summary>
        /// <param name="historicalPrices">List of historical Price objects (e.g., 1-hour candlesticks) for training and testing.</param>
        /// <param name="shortTermPrices">List of short-term Price objects (e.g., 10-second updates) for real-time analysis.</param>
        /// <param name="ailogger">Logging action for test progress, results, and errors. Defaults to Console.WriteLine if null.</param>
        /// <param name="windowSize">Number of historical candlesticks to use for each prediction (default: 50, matching HistoricalLookbackPeriod).</param>
        /// <param name="testEpisodes">Number of prediction episodes to simulate (default: 100).</param>
        /// <param name="trainingRatio">Ratio of data to use for training (0-1, default: 0.7 or 70%).</param>
        /// <returns>Tuple containing overall accuracy, win rate, and average confidence for predictions.</returns>
        public (float Accuracy, float WinRate, float AverageConfidence) TestBreakoutPrediction(List<Price> historicalPrices,
                                                                                               List<Price> shortTermPrices,
                                                                                               Action<string> ailogger = null,
                                                                                               int windowSize = 50,
                                                                                               int testEpisodes = 100,
                                                                                               float trainingRatio = 0.7f)
        {

            // Default logger to Console.WriteLine if none provided
            ailogger = ailogger ?? (s => Console.WriteLine(s));

            // Validate input data
            if (historicalPrices == null || historicalPrices.Count < windowSize + 1)
            {
                ailogger($"TestBreakoutPrediction: Error - Insufficient historical price data: {historicalPrices?.Count ?? 0} prices, need at least {windowSize + 1}");
                return (0f, 0f, 0f);
            }

            if (shortTermPrices == null || shortTermPrices.Count == 0)
            {
                ailogger("TestBreakoutPrediction: Error - No short-term price data provided.");
                return (0f, 0f, 0f);
            }

            // Calculate the number of data points for training and testing
            int trainingCount = (int)(historicalPrices.Count * trainingRatio);
            if (trainingCount < windowSize || trainingCount >= historicalPrices.Count)
            {
                ailogger($"TestBreakoutPrediction: Error - Invalid training split: need between {windowSize} and {historicalPrices.Count - 1} prices for training");
                return (0f, 0f, 0f);
            }

            // Split data into training and testing sets
            var trainingHistoricalPrices = historicalPrices.Take(trainingCount).ToList();
            var testingHistoricalPrices = historicalPrices.Skip(trainingCount).ToList();
            int maxEpisodes = Math.Min(testEpisodes, testingHistoricalPrices.Count - windowSize);

            if (maxEpisodes <= 0)
            {
                ailogger("TestBreakoutPrediction: Error - Not enough testing data for specified episodes and window size.");
                return (0f, 0f, 0f);
            }

            ailogger($"TestBreakoutPrediction: Starting test with {maxEpisodes} episodes, {trainingRatio * 100:F1}% training data, window size={windowSize}");

            // Train the model with training data
            try
            {
                Train(trainingHistoricalPrices, shortTermPrices.Take(trainingCount * 6).ToList(), ailogger); // Assuming 10s updates, 360 per hour, 6 per 1H candle
                ailogger("TestBreakoutPrediction: Model trained successfully.");
            }
            catch (Exception ex)
            {
                ailogger($"TestBreakoutPrediction: Training failed - {ex.Message}");
                return (0f, 0f, 0f);
            }

            // Initialize metrics
            int correctPredictions = 0;
            int totalPredictions = 0;
            int bullishWins = 0, bearishWins = 0, totalBullish = 0, totalBearish = 0;
            float totalConfidence = 0f;
            const float confidenceThreshold = 0.65f; // Minimum confidence for a prediction to be considered valid

            // Simulate predictions for testing episodes
            for (int i = 0; i < maxEpisodes; i++)
            {
                // Get a window of historical data for prediction
                var predictionWindow = testingHistoricalPrices.Skip(i).Take(windowSize).ToList();
                var currentShortTermWindow = shortTermPrices
                    .Skip(i * (windowSize * 6)) // Assuming 6 short-term updates per 1H candle (10s * 6 = 60s)
                    .Take(windowSize * 6) // Match the number of short-term updates to historical window
                    .ToList();

                // Predict the next breakout trend
                AIBreakoutOutputSchema prediction;
                try
                {
                    prediction = Predict(predictionWindow, currentShortTermWindow, ailogger);
                }
                catch (Exception ex)
                {
                    ailogger($"TestBreakoutPrediction: Prediction failed for episode {i + 1} - {ex.Message}");
                    continue;
                }

                // Determine the actual outcome (look ahead one step in historical data)
                string actualLabel = "NoBreakout";
                if (i + windowSize < testingHistoricalPrices.Count)
                {
                    float nextPriceChange = (float)((testingHistoricalPrices[i + windowSize].ClosePrice - testingHistoricalPrices[i + windowSize - 1].ClosePrice) /
                                           (testingHistoricalPrices[i + windowSize - 1].ClosePrice));
                    actualLabel = Math.Abs(nextPriceChange) >= BreakoutThreshold
                        ? (nextPriceChange > 0 ? "Bullish" : "Bearish")
                        : "NoBreakout";
                }

                // Calculate confidence (highest probability among classes)
                float confidence = prediction.Probabilities.Max();
                totalConfidence += confidence;

                // Log prediction details
                ailogger($"TestBreakoutPrediction Episode {i + 1}: Predicted={prediction.PredictedLabel}, Actual={actualLabel}, " +
                         $"Confidence={confidence:F2}, Probabilities=[{string.Join(", ", prediction.Probabilities.Select(p => p.ToString("F2")))}]");

                // Evaluate if prediction is valid (above confidence threshold) and correct
                if (confidence >= confidenceThreshold)
                {
                    totalPredictions++;
                    if (prediction.PredictedLabel == actualLabel)
                    {
                        correctPredictions++;
                        if (prediction.PredictedLabel == "Bullish") bullishWins++;
                        if (prediction.PredictedLabel == "Bearish") bearishWins++;
                    }

                    // Track totals for win rate calculation
                    if (prediction.PredictedLabel == "Bullish") totalBullish++;
                    if (prediction.PredictedLabel == "Bearish") totalBearish++;
                }
            }

            // Calculate performance metrics
            float accuracy = totalPredictions > 0 ? (float)correctPredictions / totalPredictions * 100 : 0f;
            float winRate = (totalBullish + totalBearish) > 0
                ? ((float)(bullishWins + bearishWins) / (totalBullish + totalBearish)) * 100
                : 0f;
            float averageConfidence = totalPredictions > 0 ? totalConfidence / totalPredictions : 0f;

            // Log final results
            ailogger($"TestBreakoutPrediction Complete: Accuracy={accuracy:F2}%, Win Rate={winRate:F2}%, " +
                     $"Average Confidence={averageConfidence:F2}, Total Predictions={totalPredictions}, Correct={correctPredictions}");
            ailogger($"Breakdown: Bullish Predictions={totalBullish}, Bullish Wins={bullishWins}, " +
                     $"Bearish Predictions={totalBearish}, Bearish Wins={bearishWins}");

            return (accuracy, winRate, averageConfidence);
        }
    }
}

 

The Output Schema: AIBreakoutOutputSchema.cs

namespace AdvancedCryptoTrader.AI
{



    #region Using Statements:



    using Microsoft.ML.Data;



    #endregion



    /// <summary>
    /// Represents the output schema for breakout prediction, including the predicted label and confidence scores.
    /// </summary>
    public class AIBreakoutOutputSchema
    {
        /// <summary>
        /// The predicted breakout label ("Bullish", "Bearish", or "NoBreakout").
        /// </summary>
        [ColumnName("PredictedLabel")]
        public string PredictedLabel { get; set; }

        /// <summary>
        /// Probability scores for each possible breakout class (Bullish, Bearish, NoBreakout).
        /// </summary>
        [ColumnName("Score")]
        public float[] Probabilities { get; set; }

        /// <summary>
        /// Confidence score for the predicted label (highest probability among classes).
        /// </summary>
        public float Confidence { get; internal set; }
    }
}

 

More coming soon...

Best Wishes,

   Chris

Order By: Standard | Newest | Votes
FringeIdeas posted this 07 March 2025

I'm not much into A.I., but this application would be interesting to see.

About 10 ish years ago, maybe a bit less, I used to try to write my own predictions for some futures markets. When TradingView was just getting big. I never did get used to their Pine script. Also I dabbled a bit with Sierra Charts, which has a c++ based framework. Of course, no A.I. back then, it would be interesting to see more! Not that I have time to play, but interesting nonetheless.

Marcel

Chris posted this 07 March 2025

My Friends,

@Marcel, Oh cool, you've had a fair bit of experience then! I better not embarrass myself then eh!

@Everyone, here is a small class that tried to predict the same Breakout mathematically:

namespace AdvancedCryptoTrader.Strategies
{



    #region Using Statements:



    using System;
    using System.Linq;
    using System.Collections.Generic;



    #endregion




    /// <summary>
    /// Calculates market strength and breakout potential using RSI and price deviation.
    /// </summary>
    public class BreakoutCalculator
    {



        #region Fields:



        /// <summary>
        /// e.g., 14 for standard RSI
        /// </summary>
        private readonly int rsiPeriod;



        #endregion



        #region Properties:



        /// <summary>
        /// Historical candlestick data
        /// </summary>
        private List<Price> Prices { get; set; } = new List<Price>();



        #endregion



        /// <summary>
        /// CTOR: 
        /// </summary>
        /// <param name="prices"></param>
        /// <param name="rsiPeriod"></param>
        /// <exception cref="ArgumentException"></exception>
        public BreakoutCalculator(List<Price> prices, int rsiPeriod = 14)
        {

            if (prices == null || prices.Count < rsiPeriod)
                throw new ArgumentException($"At least {rsiPeriod} candles are required.");

            this.Prices = prices;
            this.rsiPeriod = rsiPeriod;
        }



        /// <summary>
        /// Calculates the Breakout Strength based on RSI and current price deviation.
        /// </summary>
        /// <param name="currentPrice">The live ticker price.</param>
        /// <returns>Breakout Strength value (higher indicates stronger breakout potential).</returns>
        public double CalculateBreakoutStrength(double currentPrice)
        {

            // Get the last 'rsiPeriod' candles plus the most recent completed candle
            var recentPrices = Prices.TakeLast(rsiPeriod + 1).ToList();
            if (recentPrices.Count < rsiPeriod + 1)
                return 0; // Not enough data

            // Calculate RSI based on the last 'rsiPeriod' candles (excluding the most recent)
            double rsi = CalculateRSI(recentPrices.Take(rsiPeriod).ToList());

            // Use the most recent completed candle for price deviation
            var lastCandle = recentPrices.Last();
            double priceDeviation = CalculatePriceDeviation(currentPrice, lastCandle);

            // Calculate average candle range for normalization (over rsiPeriod)
            double avgCandleRange = recentPrices.Take(rsiPeriod)
                .Average(p => p.HighPrice - p.LowPrice);

            // Avoid division by zero
            if (avgCandleRange == 0) return 0;

            // Breakout Strength = RSI * (Price Deviation / Average Candle Range)
            double breakoutStrength = rsi * (priceDeviation / avgCandleRange);

            return breakoutStrength;
        }



        /// <summary>
        /// Calculates the Relative Strength Index (RSI) for a given list of prices.
        /// RSI is a momentum indicator that measures the magnitude of recent price changes
        /// to evaluate overbought or oversold conditions.
        /// </summary>
        /// <param name="priceList">A list of Price objects containing closing prices to analyze.
        /// The list should contain at least rsiPeriod + 1 elements for meaningful results.</param>
        /// <returns>
        /// The calculated RSI value between 0 and 100, where:
        /// - Values >= 70 typically indicate overbought conditions
        /// - Values <= 30 typically indicate oversold conditions
        /// - Returns 0 if there are insufficient data points
        /// - Returns 100 if there are no losses (to avoid division by zero)
        /// </returns>
        /// <remarks>
        /// This implementation uses the Wilder RSI formula with the following steps:
        /// 1. Calculates price changes between consecutive periods
        /// 2. Separates gains (positive changes) and losses (negative changes)
        /// 3. Computes average gain and loss over the specified period
        /// 4. Calculates the relative strength (RS) and final RSI value
        /// </remarks>
        /// <example>
        /// <code>
        /// List&lt;Price&gt; prices = new List&lt;Price&gt; 
        /// { 
        ///     new Price { ClosePrice = 44.34 },
        ///     new Price { ClosePrice = 44.09 },
        ///     new Price { ClosePrice = 44.15 },
        ///     // ... more prices ...
        /// };
        /// double rsi = CalculateRSI(prices);
        /// </code>
        /// </example>
        private double CalculateRSI(List<Price> priceList)
        {

            /// <summary>
            /// Minimum number of periods required for RSI calculation
            /// </summary>
            // Assuming rsiPeriod is a class field
            // private int rsiPeriod = 14; // Should be defined in the class

            if (priceList.Count < rsiPeriod)
                return 0;

            // Calculate gains and losses
            double totalGain = 0;
            double totalLoss = 0;

            for (int i = 1; i < priceList.Count; i++)
            {
                double priceChange = priceList[i].ClosePrice - priceList[i - 1].ClosePrice;
                if (priceChange > 0)
                    totalGain += priceChange;
                else
                    totalLoss += Math.Abs(priceChange);
            }

            double avgGain = totalGain / rsiPeriod;
            double avgLoss = totalLoss / rsiPeriod;

            // Avoid division by zero
            if (avgLoss == 0)
                return 100;

            double rs = avgGain / avgLoss;
            double rsi = 100 - (100 / (1 + rs));

            return rsi;
        }



        /// <summary>
        /// Calculate the Price Deviation.
        /// </summary>
        /// <param name="currentPrice">The Current Price.</param>
        /// <param name="lastCandle">The last Candle.</param>
        /// <returns>A double, the Price Deviation.</returns>
        private double CalculatePriceDeviation(double currentPrice, Price lastCandle)
        {

            // Price deviation as percentage change from last close
            double deviation = (currentPrice - lastCandle.ClosePrice) / lastCandle.ClosePrice * 100;
            return Math.Abs(deviation); // Absolute value to capture magnitude
        }
    }
}



This does a similar thing to the above code: AIBreakoutInputSchema.cs, AIBreakoutPredictor.cs and AIBreakoutOutputSchema.cs, however, this Math Predictor does not learn new, or different types of Breakouts.

Again, I am not a Financial Expert, this is just an interesting project I am exploring, for fun, and if a little bit of money can be made along the way, that's just a benefit.

Best Wishes,

   Chris

Chris posted this 15 June 2026

My Friends,

After months of work, I finally have a system that is very much impressing me!

I have used an idea that AI told me explicitly was fringe and would not work, but you know me and I persisted anyway!

 

Its a fairly complex system, so one post here is not going to get the entire code and idea across. However, I am using a Fractal based AI Perdition Engine which actually works amazingly well to predict Peaks and Lows. I still use some of what's above, but generally, here is my procedure:

  • Sort all Crypto Currencies and give them Statistics, Spread being one of the most important! Any high Spread Coins are marked as Do Not Trade, acceptable spread as Trade. $$\text{Spread} = \text{Ask Price} - \text{Bid Price}$$
  • I monitor the Currency, for market low using the AI Model, and other Market Stats including ATR and Bollinger Bands.
  • If Layer 1 passes, I pass the Crypto and stats to the LLM for Approval. 
  • If Approved, I buy, if not reject.
  • Keep monitoring for peak and or Take Profit, but keep a dynamic Stop Loss trailing the Currency that's calculated on average market drops during the period. This could be a bit better, but is working really well atm.
  • Current Tests are showing a 100% Win Rate!

 

I have a very Non-User friendly UI, but am happy to share after I get the UI more useful and a bit more testing.

Accurately Predicting the Lows was the biggest breakthrough in this system! There is no Emotional Buying here, so there is more room for success. The 100% Win Rate, winning every trade I made, was a real shock and could just be current market conditions atm. This system is a better Trader than what I am, because it monitors 24/7 and has no Emotion. Its a solid Trading Machine!

If youre interested in this small project, please ask here...

Best Wishes,

   Chris

  • Liked by
  • FringeIdeas
  • Plasmonic
thaelin posted this 6 days ago

Went back to this subject again and would like to persue this trading software. I would assume it is meant for Windows and the only machine is 7 home. Fantastic other things you are sharing on AI. It is a great tool but needs to be used wisely.

 

thay

 

  • Liked by
  • Chris
Chris posted this 4 days ago

Hi Thaelin,

Here is a really good little class I wrote to predict the Crypto Markets:

namespace FractalTimeEngine
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.IO;
    using System.Linq;
    using System.Text.Json;
    using System.Threading.Tasks;

    // ==========================================
    // 1. TENSOR MATH OPERATIONS
    // ==========================================
    public enum ActivationType { LeakyReLU, ReLU, Tanh, Sigmoid, Swish, GELU }

    public static class TensorOps
    {
        public static double[,] MatMul(double[,] A, double[,] B)
        {
            int aRows = A.GetLength(0); int aCols = A.GetLength(1);
            int bRows = B.GetLength(0); int bCols = B.GetLength(1);
            if (aCols != bRows) throw new Exception("Matrix dimension mismatch.");
            double[,] result = new double[aRows, bCols];
            for (int i = 0; i < aRows; i++)
                for (int j = 0; j < bCols; j++)
                {
                    double sum = 0;
                    for (int k = 0; k < aCols; k++) sum += A[i, k] * B[k, j];
                    result[i, j] = sum;
                }
            return result;
        }

        public static double[,] Transpose(double[,] matrix)
        {
            int rows = matrix.GetLength(0); int cols = matrix.GetLength(1);
            double[,] result = new double[cols, rows];
            for (int i = 0; i < rows; i++) for (int j = 0; j < cols; j++) result[j, i] = matrix[i, j];
            return result;
        }

        public static double[,] Scale(double[,] matrix, double scalar)
        {
            int rows = matrix.GetLength(0); int cols = matrix.GetLength(1);
            double[,] result = new double[rows, cols];
            for (int i = 0; i < rows; i++) for (int j = 0; j < cols; j++) result[i, j] = matrix[i, j] * scalar;
            return result;
        }

        public static double[,] Add(double[,] A, double[,] B)
        {
            int rows = A.GetLength(0); int cols = A.GetLength(1);
            double[,] result = new double[rows, cols];
            for (int i = 0; i < rows; i++) for (int j = 0; j < cols; j++) result[i, j] = A[i, j] + B[i, j];
            return result;
        }

        public static double[,] Softmax(double[,] matrix, bool causalMask = false, double temperature = 1.0)
        {
            int rows = matrix.GetLength(0); int cols = matrix.GetLength(1);
            double[,] result = new double[rows, cols];
            for (int i = 0; i < rows; i++)
            {
                double max = double.MinValue;
                for (int j = 0; j < cols; j++) { if (causalMask && j > i) continue; if (matrix[i, j] > max) max = matrix[i, j]; }
                double sum = 0;
                for (int j = 0; j < cols; j++)
                {
                    if (causalMask && j > i) result[i, j] = 0;
                    else { result[i, j] = Math.Exp((matrix[i, j] / temperature) - max); sum += result[i, j]; }
                }
                for (int j = 0; j < cols; j++) if (!causalMask || j <= i) result[i, j] /= sum;
            }
            return result;
        }

        public static double[,] LayerNorm(double[,] matrix)
        {
            int rows = matrix.GetLength(0); int cols = matrix.GetLength(1);
            double[,] result = new double[rows, cols];
            double epsilon = 1e-5;
            for (int i = 0; i < rows; i++)
            {
                double sum = 0; for (int j = 0; j < cols; j++) sum += matrix[i, j];
                double mean = sum / cols;
                double variance = 0; for (int j = 0; j < cols; j++) variance += Math.Pow(matrix[i, j] - mean, 2);
                variance /= cols;
                for (int j = 0; j < cols; j++) result[i, j] = (matrix[i, j] - mean) / Math.Sqrt(variance + epsilon);
            }
            return result;
        }

        public static double[,] ApplyActivation(double[,] matrix, ActivationType type)
        {
            int rows = matrix.GetLength(0); int cols = matrix.GetLength(1);
            double[,] result = new double[rows, cols];
            for (int i = 0; i < rows; i++)
                for (int j = 0; j < cols; j++)
                {
                    double x = matrix[i, j];
                    switch (type)
                    {
                        case ActivationType.LeakyReLU: result[i, j] = x > 0 ? x : x * 0.01; break;
                        case ActivationType.ReLU: result[i, j] = Math.Max(0, x); break;
                        case ActivationType.Tanh: result[i, j] = Math.Tanh(x); break;
                        case ActivationType.Sigmoid: result[i, j] = 1.0 / (1.0 + Math.Exp(-x)); break;
                        case ActivationType.Swish: result[i, j] = x * (1.0 / (1.0 + Math.Exp(-x))); break;
                        case ActivationType.GELU: result[i, j] = 0.5 * x * (1.0 + Math.Tanh(Math.Sqrt(2.0 / Math.PI) * (x + 0.044715 * Math.Pow(x, 3)))); break;
                    }
                }
            return result;
        }

        public static double ActivationDerivative(double x, ActivationType type)
        {
            switch (type)
            {
                case ActivationType.LeakyReLU: return x > 0 ? 1.0 : 0.01;
                case ActivationType.ReLU: return x > 0 ? 1.0 : 0.0;
                case ActivationType.Tanh: double t = Math.Tanh(x); return 1.0 - t * t;
                case ActivationType.Sigmoid: double s = 1.0 / (1.0 + Math.Exp(-x)); return s * (1.0 - s);
                case ActivationType.Swish: double sig = 1.0 / (1.0 + Math.Exp(-x)); return sig + x * sig * (1.0 - sig);
                case ActivationType.GELU:
                    double inner = Math.Sqrt(2.0 / Math.PI) * (x + 0.044715 * Math.Pow(x, 3));
                    double tanhInner = Math.Tanh(inner);
                    return 0.5 * (1.0 + tanhInner) + 0.5 * x * (1.0 - tanhInner * tanhInner) * Math.Sqrt(2.0 / Math.PI) * (1.0 + 3.0 * 0.044715 * x * x);
                default: return 1.0;
            }
        }

        // --- NEW I/O HELPERS FOR MODEL SERIALIZATION ---
        public static void SaveMatrix(BinaryWriter bw, double[,] matrix)
        {
            int rows = matrix.GetLength(0); int cols = matrix.GetLength(1);
            bw.Write(rows);
            bw.Write(cols);
            for (int i = 0; i < rows; i++)
                for (int j = 0; j < cols; j++)
                    bw.Write(matrix[i, j]);
        }

        public static double[,] LoadMatrix(BinaryReader br)
        {
            int rows = br.ReadInt32(); int cols = br.ReadInt32();
            double[,] matrix = new double[rows, cols];
            for (int i = 0; i < rows; i++)
                for (int j = 0; j < cols; j++)
                    matrix[i, j] = br.ReadDouble();
            return matrix;
        }
    }

    // ==========================================
    // 2. FRACTAL WEIGHT GENERATORS 
    // ==========================================
    public enum FractalType { PrimeGapSignature, GoldenRatioPhase }

    public class FractalWeights
    {
        public static double[,] Generate(int rows, int cols, FractalType type)
        {
            double[,] weights = new double[rows, cols];
            double scale = Math.Sqrt(2.0 / cols);
            int size = rows * cols;
            List<double> pool = (type == FractalType.PrimeGapSignature) ? GetPrimeGapPool(size) : GetGoldenRatioPool(size);
            int index = 0;
            for (int i = 0; i < rows; i++) for (int j = 0; j < cols; j++) weights[i, j] = pool[index++] * scale;
            return weights;
        }

        private static List<double> GetPrimeGapPool(int size)
        {
            List<int> primes = new List<int>(); List<double> pool = new List<double>(); int num = 2;
            while (pool.Count < size)
            {
                bool isPrime = true; for (int i = 2; i * i <= num; i++) if (num % i == 0) { isPrime = false; break; }
                if (isPrime) { primes.Add(num); if (primes.Count > 1) { int gap = primes[primes.Count - 1] - primes[primes.Count - 2]; pool.Add(Math.Sin(gap) * Math.Cos(gap * Math.PI / 4.0)); } }
                num++;
            }
            return pool;
        }

        private static List<double> GetGoldenRatioPool(int size)
        {
            List<double> pool = new List<double>(); double phi = 1.618033988749895;
            for (int i = 0; i < size; i++) { double val = (i * phi) - Math.Floor(i * phi); pool.Add((val * 2.0) - 1.0); }
            return pool;
        }
    }

    // ==========================================
    // 3. ARCHITECTURAL ENGINE COMPONENTS
    // ==========================================
    public class GeometricPositionalEncoding
    {
        public static double[,] Apply(double[,] stateMatrix)
        {
            int seqLen = stateMatrix.GetLength(0); int dModel = stateMatrix.GetLength(1);
            double[,] result = new double[seqLen, dModel];
            for (int pos = 0; pos < seqLen; pos++)
                for (int dim = 0; dim < dModel; dim++)
                {
                    double angle = (pos * 1.61803) + (dim * 2.71828);
                    double pe = (dim % 2 == 0) ? Math.Sin(angle) : Math.Cos(angle * 1.5);
                    result[pos, dim] = stateMatrix[pos, dim] + pe;
                }
            return result;
        }
    }

    public class VarianceModulator
    {
        public static double[,] Apply(double[,] stateVector)
        {
            int seqLen = stateVector.GetLength(0); int dModel = stateVector.GetLength(1);
            double[,] result = new double[seqLen, dModel];
            for (int i = 0; i < seqLen; i++)
            {
                double mean = 0, variance = 0;
                for (int j = 0; j < dModel; j++) mean += stateVector[i, j]; mean /= dModel;
                for (int j = 0; j < dModel; j++) variance += Math.Pow(stateVector[i, j] - mean, 2); variance /= dModel;
                double tokenStructuralSignature = variance * 15.0;
                for (int j = 0; j < dModel; j++) result[i, j] = stateVector[i, j] * Math.Cos(tokenStructuralSignature);
            }
            return result;
        }
    }

    public class OnboardingNetwork
    {
        public double[,] W { get; private set; }
        private double[,] _xCache, _zCache; private ActivationType _activation; private int _inputDim, _dModel;

        public OnboardingNetwork(int inputDim, int dModel, ActivationType activation)
        {
            _inputDim = inputDim; _dModel = dModel; _activation = activation; W = new double[inputDim, dModel];
            Random rnd = new Random(42); double scale = Math.Sqrt(2.0 / (inputDim + dModel));
            for (int i = 0; i < inputDim; i++) for (int j = 0; j < dModel; j++) W[i, j] = (rnd.NextDouble() * 2.0 - 1.0) * scale;
        }

        public double[,] Forward(double[,] x) { _xCache = x; _zCache = TensorOps.MatMul(x, W); return TensorOps.ApplyActivation(_zCache, _activation); }

        public double[,] Backward(double[,] dOut, double learningRate)
        {
            int seqLen = dOut.GetLength(0); double[,] dZ = new double[seqLen, _dModel];
            for (int i = 0; i < seqLen; i++) for (int j = 0; j < _dModel; j++) dZ[i, j] = dOut[i, j] * TensorOps.ActivationDerivative(_zCache[i, j], _activation);
            double[,] dW = TensorOps.MatMul(TensorOps.Transpose(_xCache), dZ);
            for (int i = 0; i < _inputDim; i++) for (int j = 0; j < _dModel; j++) W[i, j] -= learningRate * dW[i, j];
            return TensorOps.MatMul(dZ, TensorOps.Transpose(W));
        }

        public void Save(BinaryWriter bw) { TensorOps.SaveMatrix(bw, W); }
        public void Load(BinaryReader br) { W = TensorOps.LoadMatrix(br); }
    }

    public class MultiHeadFractalAttention
    {
        private double[,] W_q, W_k, W_v, W_o; private int _dModel, _numHeads, _headDim;

        public MultiHeadFractalAttention(int dModel, int numHeads, FractalType qType, FractalType kType, FractalType vType, FractalType oType)
        {
            _dModel = dModel; _numHeads = numHeads; _headDim = dModel / numHeads;
            W_q = FractalWeights.Generate(dModel, dModel, qType); W_k = FractalWeights.Generate(dModel, dModel, kType);
            W_v = FractalWeights.Generate(dModel, dModel, vType); W_o = FractalWeights.Generate(dModel, dModel, oType);
        }

        public double[,] Forward(double[,] x)
        {
            int seqLen = x.GetLength(0);
            double[,] Q_full = TensorOps.MatMul(x, W_q); double[,] K_full = TensorOps.MatMul(x, W_k); double[,] V_full = TensorOps.MatMul(x, W_v);
            double[,] combinedContext = new double[seqLen, _dModel];

            for (int h = 0; h < _numHeads; h++)
            {
                double[,] Q_h = ExtractSlice(Q_full, h * _headDim, _headDim); double[,] K_h = ExtractSlice(K_full, h * _headDim, _headDim); double[,] V_h = ExtractSlice(V_full, h * _headDim, _headDim);
                double[,] scores = TensorOps.Scale(TensorOps.MatMul(Q_h, TensorOps.Transpose(K_h)), 1.0 / Math.Sqrt(_headDim));
                double[,] attention = TensorOps.Softmax(scores, causalMask: true);
                InsertSlice(combinedContext, TensorOps.MatMul(attention, V_h), h * _headDim);
            }
            return VarianceModulator.Apply(TensorOps.MatMul(combinedContext, W_o));
        }

        private double[,] ExtractSlice(double[,] source, int startCol, int width)
        {
            int rows = source.GetLength(0); double[,] slice = new double[rows, width];
            for (int i = 0; i < rows; i++) for (int j = 0; j < width; j++) slice[i, j] = source[i, startCol + j];
            return slice;
        }

        private void InsertSlice(double[,] dest, double[,] slice, int startCol)
        {
            int rows = dest.GetLength(0); int width = slice.GetLength(1);
            for (int i = 0; i < rows; i++) for (int j = 0; j < width; j++) dest[i, startCol + j] = slice[i, j];
        }

        public void Save(BinaryWriter bw)
        {
            TensorOps.SaveMatrix(bw, W_q); TensorOps.SaveMatrix(bw, W_k);
            TensorOps.SaveMatrix(bw, W_v); TensorOps.SaveMatrix(bw, W_o);
        }

        public void Load(BinaryReader br)
        {
            W_q = TensorOps.LoadMatrix(br); W_k = TensorOps.LoadMatrix(br);
            W_v = TensorOps.LoadMatrix(br); W_o = TensorOps.LoadMatrix(br);
        }
    }

    public class FractalTransformerBlock
    {
        private MultiHeadFractalAttention _attention; private double[,] _w1, _w2; private ActivationType _activation;

        public FractalTransformerBlock(int dModel, int dFF, int numHeads, FractalType initType, ActivationType activation)
        {
            _attention = new MultiHeadFractalAttention(dModel, numHeads, initType, initType, initType, initType);
            _w1 = FractalWeights.Generate(dModel, dFF, initType); _w2 = FractalWeights.Generate(dFF, dModel, initType);
            _activation = activation;
        }

        public double[,] Forward(double[,] x)
        {
            double[,] norm1 = TensorOps.LayerNorm(x);
            x = TensorOps.Add(x, _attention.Forward(norm1));
            double[,] hidden = TensorOps.ApplyActivation(TensorOps.MatMul(TensorOps.LayerNorm(x), _w1), _activation);
            return TensorOps.Add(x, TensorOps.MatMul(hidden, _w2));
        }

        public void Save(BinaryWriter bw)
        {
            _attention.Save(bw);
            TensorOps.SaveMatrix(bw, _w1); TensorOps.SaveMatrix(bw, _w2);
        }

        public void Load(BinaryReader br)
        {
            _attention.Load(br);
            _w1 = TensorOps.LoadMatrix(br); _w2 = TensorOps.LoadMatrix(br);
        }
    }

    // ==========================================
    // 3.5 CONTINUOUS TIME SERIES MODEL
    // ==========================================
    public class FractalTimeSeriesModel
    {
        private double[,] _regressionHeadWeights;
        private List<FractalTransformerBlock> _blocks;
        private OnboardingNetwork _onboardingNet;
        private int _inputFeatures, _outputTargets, _dModel;

        public FractalTimeSeriesModel(int inputFeatures, int outputTargets, int dModel, int dFF, int numLayers, int numHeads, FractalType initMethod, ActivationType activationMethod)
        {
            _inputFeatures = inputFeatures; _outputTargets = outputTargets; _dModel = dModel;
            _onboardingNet = new OnboardingNetwork(inputFeatures, dModel, activationMethod);
            _regressionHeadWeights = FractalWeights.Generate(dModel, outputTargets, initMethod);
            _blocks = new List<FractalTransformerBlock>();
            for (int i = 0; i < numLayers; i++) _blocks.Add(new FractalTransformerBlock(dModel, dFF, numHeads, initMethod, activationMethod));
        }

        public double[] PredictFutureBars(double[,] sequenceData)
        {
            int seqLen = sequenceData.GetLength(0);
            double[,] x = _onboardingNet.Forward(sequenceData);
            x = GeometricPositionalEncoding.Apply(x);
            foreach (var block in _blocks) x = block.Forward(x);

            double[,] finalBarState = new double[1, _dModel];
            for (int d = 0; d < _dModel; d++) finalBarState[0, d] = x[seqLen - 1, d];

            double[,] predictions = TensorOps.MatMul(finalBarState, _regressionHeadWeights);
            double[] result = new double[_outputTargets];
            for (int t = 0; t < _outputTargets; t++) result[t] = predictions[0, t];
            return result;
        }

        public double TrainStep(double[,] contextSequence, double[] targetSequence, double learningRate)
        {
            int seqLen = contextSequence.GetLength(0);
            double[,] x = _onboardingNet.Forward(contextSequence);
            x = GeometricPositionalEncoding.Apply(x);
            foreach (var block in _blocks) x = block.Forward(x);

            double[,] finalBarState = new double[1, _dModel];
            for (int d = 0; d < _dModel; d++) finalBarState[0, d] = x[seqLen - 1, d];

            double[,] predictions = TensorOps.MatMul(finalBarState, _regressionHeadWeights);

            double mseLoss = 0.0;
            double[,] dPredictions = new double[1, _outputTargets];
            for (int t = 0; t < _outputTargets; t++)
            {
                double error = predictions[0, t] - targetSequence[t];
                mseLoss += error * error;
                dPredictions[0, t] = 2.0 * error;
            }
            mseLoss /= _outputTargets;

            double[,] dWeights = TensorOps.MatMul(TensorOps.Transpose(finalBarState), dPredictions);
            double[,] dFinalBarState = TensorOps.MatMul(dPredictions, TensorOps.Transpose(_regressionHeadWeights));

            for (int i = 0; i < _dModel; i++)
                for (int j = 0; j < _outputTargets; j++)
                    _regressionHeadWeights[i, j] -= learningRate * dWeights[i, j];

            double[,] dXOut = new double[seqLen, _dModel];
            for (int d = 0; d < _dModel; d++) dXOut[seqLen - 1, d] = dFinalBarState[0, d];

            _onboardingNet.Backward(dXOut, learningRate);
            return mseLoss;
        }

        public double Evaluate(double[,] contextSequence, double[] targetSequence)
        {
            int seqLen = contextSequence.GetLength(0);
            double[,] x = _onboardingNet.Forward(contextSequence);
            x = GeometricPositionalEncoding.Apply(x);
            foreach (var block in _blocks) x = block.Forward(x);

            double[,] finalBarState = new double[1, _dModel];
            for (int d = 0; d < _dModel; d++) finalBarState[0, d] = x[seqLen - 1, d];

            double[,] predictions = TensorOps.MatMul(finalBarState, _regressionHeadWeights);
            double mseLoss = 0.0;
            for (int t = 0; t < _outputTargets; t++)
            {
                double error = predictions[0, t] - targetSequence[t];
                mseLoss += error * error;
            }
            return mseLoss / _outputTargets;
        }

        public void Save(BinaryWriter bw)
        {
            _onboardingNet.Save(bw);
            TensorOps.SaveMatrix(bw, _regressionHeadWeights);
            foreach (var block in _blocks) block.Save(bw);
        }

        public void Load(BinaryReader br)
        {
            _onboardingNet.Load(br);
            _regressionHeadWeights = TensorOps.LoadMatrix(br);
            foreach (var block in _blocks) block.Load(br);
        }
    }

    // ==========================================
    // 4. DATA PIPELINE
    // ==========================================
    public class TimeSeriesScaler
    {
        public double Mean { get; private set; }
        public double StdDev { get; private set; }
        public void Fit(List<double> data)
        {
            Mean = data.Average();
            double variance = data.Average(v => Math.Pow(v - Mean, 2));
            StdDev = Math.Sqrt(variance) + 1e-7;
        }
        public double Transform(double value) => (value - Mean) / StdDev;
        public double InverseTransform(double scaled) => (scaled * StdDev) + Mean;

        public void Save(BinaryWriter bw) { bw.Write(Mean); bw.Write(StdDev); }
        public void Load(BinaryReader br) { Mean = br.ReadDouble(); StdDev = br.ReadDouble(); }
    }

    public class MultiFeatureScaler
    {
        private TimeSeriesScaler[] _scalers;
        public MultiFeatureScaler(int featureCount)
        {
            _scalers = new TimeSeriesScaler[featureCount];
            for (int i = 0; i < featureCount; i++) _scalers[i] = new TimeSeriesScaler();
        }

        public void Fit(List<double[]> dataset)
        {
            int features = _scalers.Length;
            for (int f = 0; f < features; f++)
            {
                var singleFeatureData = dataset.Select(row => row[f]).ToList();
                _scalers[f].Fit(singleFeatureData);
            }
        }

        public double[] Transform(double[] values)
        {
            double[] scaled = new double[values.Length];
            for (int f = 0; f < values.Length; f++) scaled[f] = _scalers[f].Transform(values[f]);
            return scaled;
        }

        public double InverseTransformFeature(double scaledValue, int featureIndex)
        {
            return _scalers[featureIndex].InverseTransform(scaledValue);
        }

        public void Save(BinaryWriter bw) { foreach (var s in _scalers) s.Save(bw); }
        public void Load(BinaryReader br) { foreach (var s in _scalers) s.Load(br); }
    }

    public class SlidingWindowBatcher
    {
        public class TrainingExample
        {
            public double[,] ContextFeatures { get; set; }
            public double[] TargetFeatures { get; set; }
        }

        public static List<TrainingExample> CreateBatches(List<double[]> rawPrices, MultiFeatureScaler scaler, int contextSize, int targetHorizon)
        {
            var dataset = new List<TrainingExample>();
            int limit = rawPrices.Count - contextSize - targetHorizon;

            for (int i = 0; i < limit; i++)
            {
                double[,] context = new double[contextSize, 5];
                for (int c = 0; c < contextSize; c++)
                {
                    double[] scaledRow = scaler.Transform(rawPrices[i + c]);
                    for (int f = 0; f < 5; f++) context[c, f] = scaledRow[f];
                }

                double[] target = new double[targetHorizon * 2];
                double contextHigh = rawPrices.Skip(i).Take(contextSize).Max(x => x[1]);
                double contextLow = rawPrices.Skip(i).Take(contextSize).Min(x => x[2]);

                for (int t = 0; t < targetHorizon; t++)
                {
                    double futureClose = rawPrices[i + contextSize + t][3];
                    double futureHigh = rawPrices[i + contextSize + t][1];
                    double futureLow = rawPrices[i + contextSize + t][2];

                    double peakSignal = 0.0;
                    if (futureHigh > contextHigh * 1.015) peakSignal = 1.0;
                    else if (futureLow < contextLow * 0.985) peakSignal = -1.0;

                    target[t * 2] = scaler.Transform(new double[] { 0, 0, 0, futureClose, 0 })[3];
                    target[t * 2 + 1] = peakSignal;
                }

                dataset.Add(new TrainingExample { ContextFeatures = context, TargetFeatures = target });
            }
            return dataset;
        }
    }

    // ==========================================
    // 5. INTERACTIVE RUNTIME EXECUTOR
    // ==========================================
    class Program
    {

        static MultiFeatureScaler scaler;
        static FractalTimeSeriesModel model;

        static List<SlidingWindowBatcher.TrainingExample> trainDataset;
        static List<SlidingWindowBatcher.TrainingExample> testDataset;
        static Random rng = new Random();

        // Architectural Parameters
        static int contextWindowSize = 16;
        static int outputHorizon = 5;
        static int inputFeatures = 5;

        static int dModel = 128; // 64;
        static int dFF = 256;
        static int numLayers = 4; // 2;
        static int numHeads = 4;

        static bool modelLoaded = false;

        static List<double[]> rawTrainPrices = new List<double[]>();
        static List<double[]> rawTestPrices = new List<double[]>();

        static string datasetPath = "Data/SOLUSDT-30-price-history.json";

        static void Main(string[] args)
        {
            Directory.CreateDirectory("Data");

            while (true)
            {
                Console.Clear();
                Console.ForegroundColor = ConsoleColor.Cyan;
                Console.WriteLine("  FRACTAL-INITIALIZED ADAPTIVE CRYPTO FORECASTER [OHLCV MULTIVARIATE]");
                Console.ForegroundColor = ConsoleColor.White;
                Console.WriteLine($"\n  Model Status: {(modelLoaded ? "[ONLINE]" : "[OFFLINE]")}");
                Console.WriteLine($"  Data Depth: Train ({rawTrainPrices.Count} bars) | Test ({rawTestPrices.Count} bars)");
                Console.WriteLine($"  Forecast Horizon: {outputHorizon} Bars Ahead");
                Console.WriteLine("  --------------------------------------------------");
                Console.WriteLine("  [I] Ingest JSON Crypto Data & Configure");
                Console.WriteLine("  [T] Train Model (MSE Regression)");
                Console.WriteLine("  [P] Predict N Bars & Market Cycle Signals");
                Console.WriteLine("  [S] Save Model Weights & Scaler State");
                Console.WriteLine("  [L] Load Model Weights & Scaler State");
                Console.WriteLine("  [Q] Quit");
                Console.WriteLine("  --------------------------------------------------");
                Console.Write("  Select Action: ");

                var key = Console.ReadKey(true).Key;
                Console.WriteLine();

                switch (key)
                {
                    case ConsoleKey.I: InitMenu(); break;
                    case ConsoleKey.T: TrainMenu(); break;
                    case ConsoleKey.P: PredictMenu(); break;
                    case ConsoleKey.S: SaveMenu(); break;
                    case ConsoleKey.L: LoadMenu(); break;
                    case ConsoleKey.Q: Environment.Exit(0); break;
                }
            }
        }

        static void InitMenu()
        {
            if (!File.Exists(datasetPath))
            {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine($"  [Error] Missing file at {datasetPath}");
                System.Threading.Thread.Sleep(2000);
                return;
            }

            Console.ForegroundColor = ConsoleColor.Yellow;
            Console.Write("  Enter N Candles to predict ahead (e.g. 5): ");
            if (!int.TryParse(Console.ReadLine(), out outputHorizon)) outputHorizon = 5;

            Console.WriteLine($"  [Ingestion] Loading OHLCV data from {datasetPath}...");

            List<double[]> fullPriceHistory = new List<double[]>();

            try
            {
                string jsonText = File.ReadAllText(datasetPath);
                using (JsonDocument doc = JsonDocument.Parse(jsonText))
                {
                    JsonElement root = doc.RootElement;

                    if (root.ValueKind == JsonValueKind.Object && root.TryGetProperty("c", out JsonElement cArr))
                    {
                        var oArr = root.GetProperty("o").EnumerateArray().ToList();
                        var hArr = root.GetProperty("h").EnumerateArray().ToList();
                        var lArr = root.GetProperty("l").EnumerateArray().ToList();
                        var cArrList = cArr.EnumerateArray().ToList();
                        var vArr = root.GetProperty("v").EnumerateArray().ToList();

                        for (int i = 0; i < cArrList.Count; i++)
                        {
                            try
                            {
                                fullPriceHistory.Add(new double[] {
                                    oArr[i].GetDouble(), hArr[i].GetDouble(), lArr[i].GetDouble(),
                                    cArrList[i].GetDouble(), vArr[i].GetDouble()
                                });
                            }
                            catch { }
                        }
                    }
                    else
                    {
                        JsonElement targetArray = root.ValueKind == JsonValueKind.Array ? root : default;
                        if (root.ValueKind == JsonValueKind.Object)
                        {
                            foreach (JsonProperty prop in root.EnumerateObject())
                                if (prop.Value.ValueKind == JsonValueKind.Array) { targetArray = prop.Value; break; }
                        }

                        foreach (JsonElement element in targetArray.EnumerateArray())
                        {
                            if (element.GetArrayLength() >= 6)
                            {
                                try
                                {
                                    fullPriceHistory.Add(new double[] {
                                        double.Parse(element[1].GetString()),
                                        double.Parse(element[2].GetString()),
                                        double.Parse(element[3].GetString()),
                                        double.Parse(element[4].GetString()),
                                        double.Parse(element[5].GetString())
                                    });
                                }
                                catch { }
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine($"  [Error] Failed to parse OHLCV JSON: {ex.Message}");
                System.Threading.Thread.Sleep(2000);
                return;
            }

            if (fullPriceHistory.Count < contextWindowSize + outputHorizon + 10) return;

            int splitIndex = (int)(fullPriceHistory.Count * 0.8);
            rawTrainPrices = fullPriceHistory.Take(splitIndex).ToList();
            rawTestPrices = fullPriceHistory.Skip(splitIndex).ToList();

            scaler = new MultiFeatureScaler(inputFeatures);
            scaler.Fit(rawTrainPrices);

            trainDataset = SlidingWindowBatcher.CreateBatches(rawTrainPrices, scaler, contextWindowSize, outputHorizon);
            testDataset = SlidingWindowBatcher.CreateBatches(rawTestPrices, scaler, contextWindowSize, outputHorizon);

            model = new FractalTimeSeriesModel(inputFeatures, outputHorizon * 2, dModel, dFF, numLayers, numHeads, FractalType.GoldenRatioPhase, ActivationType.Swish);
            modelLoaded = true;

            Console.ForegroundColor = ConsoleColor.Green;
            Console.WriteLine($"  [Success] Loaded {fullPriceHistory.Count} bars with OHLCV features.");
            System.Threading.Thread.Sleep(1500);
        }

        static void TrainMenu()
        {
            if (!modelLoaded)
            {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine("  [!] Please ingest data or load a model first.");
                System.Threading.Thread.Sleep(1500);
                return;
            }

            Console.ForegroundColor = ConsoleColor.Yellow;
            Console.Write("  Enter number of Epochs (e.g. 50): ");
            if (!int.TryParse(Console.ReadLine(), out int epochs)) epochs = 50;

            double currentLr = 0.0005;
            double bestLoss = double.MaxValue;
            int patience = 0;

            Console.WriteLine($"\n  [Training] MSE Regression on {inputFeatures}D Vector Space...");

            for (int epoch = 1; epoch <= epochs; epoch++)
            {
                for (int i = trainDataset.Count - 1; i > 0; i--)
                {
                    int j = rng.Next(i + 1);
                    var temp = trainDataset[i]; trainDataset[i] = trainDataset[j]; trainDataset[j] = temp;
                }

                var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = Math.Max(2, Environment.ProcessorCount / 2) };
                double[] batchLosses = new double[trainDataset.Count];

                Parallel.For(0, trainDataset.Count, parallelOptions, i =>
                {
                    batchLosses[i] = model.TrainStep(trainDataset[i].ContextFeatures, trainDataset[i].TargetFeatures, currentLr);
                });

                double trainLoss = batchLosses.Average();
                double[] testLosses = new double[testDataset.Count];
                Parallel.For(0, testDataset.Count, parallelOptions, i =>
                {
                    testLosses[i] = model.Evaluate(testDataset[i].ContextFeatures, testDataset[i].TargetFeatures);
                });

                double testLoss = testLosses.Average();

                Console.ForegroundColor = ConsoleColor.White;
                Console.WriteLine($"  Epoch {epoch3} | Train MSE: {trainLoss:F6} | Test MSE: {testLoss:F6} | LR: {currentLr:F6}");

                if (testLoss < bestLoss) { bestLoss = testLoss; patience = 0; }
                else
                {
                    patience++;
                    if (patience >= 3) { currentLr *= 0.75; patience = 0; }
                }
            }

            Console.WriteLine("  Press any key to return...");
            Console.ReadKey(true);
        }

        static void PredictMenu()
        {
            if (!modelLoaded || rawTestPrices.Count < contextWindowSize)
            {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine("  [!] Please ingest data and test set first.");
                System.Threading.Thread.Sleep(1500);
                return;
            }

            var lastKnownContext = new double[contextWindowSize, 5];

            Console.ForegroundColor = ConsoleColor.DarkGray;
            Console.WriteLine("  Recent Actual Closes:");
            for (int c = 0; c < contextWindowSize; c++)
            {
                double[] rawBar = rawTestPrices[rawTestPrices.Count - contextWindowSize + c];
                double[] scaledBar = scaler.Transform(rawBar);
                for (int f = 0; f < 5; f++) lastKnownContext[c, f] = scaledBar[f];
                if (c >= contextWindowSize - 5) Console.WriteLine($"   T-{contextWindowSize - c}: ${rawBar[3]:F4} (Vol: {rawBar[4]:F2})");
            }

            double[] scaledPredictions = model.PredictFutureBars(lastKnownContext);

            Console.ForegroundColor = ConsoleColor.Green;
            Console.WriteLine($"\n  --- FRACTAL PREDICTION ({outputHorizon} BARS AHEAD) ---");

            bool cycleEventDetected = false;

            for (int i = 0; i < outputHorizon; i++)
            {
                double scaledClose = scaledPredictions[i * 2];
                double rawPeakSignal = scaledPredictions[i * 2 + 1];

                double projectedPrice = scaler.InverseTransformFeature(scaledClose, 3);

                string swingStatus = "Stable Momentum";
                ConsoleColor statusColor = ConsoleColor.White;

                if (rawPeakSignal > 0.6)
                {
                    swingStatus = "🚨 CYCLE PEAK (SELL BIAS)";
                    statusColor = ConsoleColor.Red;
                    cycleEventDetected = true;
                }
                else if (rawPeakSignal < -0.6)
                {
                    swingStatus = "🟢 BOTTOM SWING (BUY BIAS)";
                    statusColor = ConsoleColor.Green;
                    cycleEventDetected = true;
                }

                Console.ForegroundColor = ConsoleColor.Cyan;
                Console.Write($"   Target T+{i + 1}: ${projectedPrice:F4} | ");
                Console.ForegroundColor = statusColor;
                Console.WriteLine(swingStatus);
            }

            if (cycleEventDetected)
            {
                Console.ForegroundColor = ConsoleColor.Magenta;
                Console.WriteLine("\n  [!] MAJOR MARKET CYCLE EVENT PREDICTED IN IMMEDIATE HORIZON.");
            }

            Console.ForegroundColor = ConsoleColor.DarkGray;
            Console.WriteLine("\n  Press any key to return...");
            Console.ReadKey(true);
        }

        static void SaveMenu()
        {
            if (!modelLoaded)
            {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine("  [!] No model is currently loaded or initialized.");
                System.Threading.Thread.Sleep(1500);
                return;
            }

            Console.ForegroundColor = ConsoleColor.Yellow;
            Console.Write("  Enter filename to save (e.g., model.bin): ");
            string file = Console.ReadLine();
            if (string.IsNullOrWhiteSpace(file)) file = "model.bin";
            string path = Path.Combine("Data", file);

            try
            {
                using (var fs = File.Create(path))
                using (var bw = new BinaryWriter(fs))
                {
                    // 1. Serialize Architecture Header
                    bw.Write(contextWindowSize);
                    bw.Write(outputHorizon);
                    bw.Write(inputFeatures);
                    bw.Write(dModel);
                    bw.Write(dFF);
                    bw.Write(numLayers);
                    bw.Write(numHeads);

                    // 2. Serialize Scaler
                    scaler.Save(bw);

                    // 3. Serialize Tensors
                    model.Save(bw);
                }
                Console.ForegroundColor = ConsoleColor.Green;
                Console.WriteLine($"\n  [Success] Model state securely written to {path}");
            }
            catch (Exception ex)
            {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine($"\n  [Error] Failed to save model: {ex.Message}");
            }
            Console.WriteLine("  Press any key to return...");
            Console.ReadKey(true);
        }

        static void LoadMenu()
        {
            Console.ForegroundColor = ConsoleColor.Yellow;
            Console.Write("  Enter filename to load (e.g., model.bin): ");
            string file = Console.ReadLine();
            if (string.IsNullOrWhiteSpace(file)) file = "model.bin";
            string path = Path.Combine("Data", file);

            if (!File.Exists(path))
            {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine($"  [!] File not found at {path}");
                System.Threading.Thread.Sleep(1500);
                return;
            }

            try
            {
                using (var fs = File.OpenRead(path))
                using (var br = new BinaryReader(fs))
                {
                    // 1. Rebuild Architecture from Header
                    contextWindowSize = br.ReadInt32();
                    outputHorizon = br.ReadInt32();
                    inputFeatures = br.ReadInt32();
                    dModel = br.ReadInt32();
                    dFF = br.ReadInt32();
                    numLayers = br.ReadInt32();
                    numHeads = br.ReadInt32();

                    // 2. Load Scaler
                    scaler = new MultiFeatureScaler(inputFeatures);
                    scaler.Load(br);

                    // 3. Reinstantiate and Inject Weights
                    model = new FractalTimeSeriesModel(inputFeatures, outputHorizon * 2, dModel, dFF, numLayers, numHeads, FractalType.GoldenRatioPhase, ActivationType.Swish);
                    model.Load(br);
                }

                modelLoaded = true;
                Console.ForegroundColor = ConsoleColor.Green;
                Console.WriteLine($"\n  [Success] Neural architecture restored from {path}");
                Console.WriteLine($"  Rebuilt constraints: Horizon={outputHorizon}, dModel={dModel}, Layers={numLayers}");
            }
            catch (Exception ex)
            {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine($"\n  [Error] Corrupted save file or schema mismatch: {ex.Message}");
            }
            Console.WriteLine("  Press any key to return...");
            Console.ReadKey(true);
        }
    }
}

 

 

Run on any machine you like!

I got a bit tired of this project, the meat has been stripped out, as market movements are so small that  you may only make a few dollars at a time. Although I was getting a very good success rate, the returns are only small. If one had 100K to put down at a time, the returns might be better,  but who has this sort of money these days to put down on a single return.

Best Wishes,

   Chris

We're Light Years Ahead!
Members Online:

No one online at the moment


Past Visitors: 0 | Live Visitors: 0


3D globe widget by: Chris Sykes

What is a Scalar:

In physics, scalars are physical quantities that are unaffected by changes to a vector space basis. Scalars are often accompanied by units of measurement, as in "10 cm". Examples of scalar quantities are mass, distance, charge, volume, time, speed, and the magnitude of physical vectors in general.

You need to forget the Non-Sense that some spout with out knowing the actual Definition of the word Scalar! Some people talk absolute Bull Sh*t!

The pressure P in the formula P = pgh, pgh is a scalar that tells you the amount of this squashing force per unit area in a fluid.

A Scalar, having both direction and magnitude, can be anything! The Magnetic Field, a Charge moving, yet some Numb Nuts think it means Magic Science!

Message from God:

Hello my children. This is Yahweh, the one true Lord. You have found creation's secret. Now share it peacefully with the world.

Ref: Message from God written inside the Human Genome

God be in my head, and in my thinking.

God be in my eyes, and in my looking.

God be in my mouth, and in my speaking.

Oh, God be in my heart, and in my understanding.

We love and trust in our Lord, Jesus Christ of Nazareth!

Your Support:

More than anything else, your contributions to this forum are most important! We are trying to actively get all visitors involved, but we do only have a few main contributors, which are very much appreciated! If you would like to see more pages with more detailed experiments and answers, perhaps a contribution of another type maybe possible:

PayPal De-Platformed me!

They REFUSE to tell me why!

We now use Wise!

Donate
Use E-Mail: Chris at aboveunity.com

The content I am sharing is not only unique, but is changing the world as we know it! Please Support Us!

Thank You So Much!

Weeks High Earners:
The great Nikola Tesla:
N. Tesla

Ere many generations pass, our machinery will be driven by a power obtainable at any point of the universe. This idea is not novel. Men have been led to it long ago by instinct or reason. It has been expressed in many ways, and in many places, in the history of old and new. We find it in the delightful myth of Antheus, who drives power from the earth; we find it among the subtle speculations of one of your splendid mathematicians, and in many hints and statements of thinkers of the present time. Throughout space there is energy. Is this energy static or kinetic? If static, our hopes are in vain; if kinetic - and this we know it is for certain - then it is a mere question of time when men will succeed in attaching their machinery to the very wheelwork of nature.

Experiments With Alternate Currents Of High Potential And High Frequency (February 1892).

Close