#region Using declarations using System; using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Input; using System.Windows.Media; using System.Xml.Serialization; using NinjaTrader.Cbi; using NinjaTrader.Gui; using NinjaTrader.Gui.Chart; using NinjaTrader.Gui.SuperDom; using NinjaTrader.Gui.Tools; using NinjaTrader.Data; using NinjaTrader.NinjaScript; using NinjaTrader.Core.FloatingPoint; using NinjaTrader.NinjaScript.DrawingTools; using Microsoft.ML; using Microsoft.ML.Data; using Microsoft.ML.Transforms; #endregion using NinjaTrader.NinjaScript.Indicators.MLNET.nsZi8ML01Prediction01; using Microsoft.ML.Trainers.FastTree; //This namespace holds Indicators in this folder and is required. Do not change it. namespace NinjaTrader.NinjaScript.Indicators.MLNET { // // -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // // á é í ó ú ñ . Á É Í Ó Ú Ñ // // Fecha de creación: 15 de marzo de 2025 // // -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // public class Zi8ML01Prediction01 : Indicator { #region Properties // Número de barras para el entrenamiento [NinjaScriptProperty] [Range(100, int.MaxValue)] [Display(Name = "Training Period", Description = "Set the number of historical bars for model training (minimum 100 recommended)", Order = 220, GroupName = "Properties")] public int TrainingPeriod { get; set; } // Es el período de los indicadores que se utilicen como features [NinjaScriptProperty] [Range(1, int.MaxValue)] [Display(Name = "Lookback Period", Description = "Define the range of bars to analyze for generating features", Order = 320, GroupName = "Properties")] public int LookbackPeriod { get; set; } // Número de barras hacia delante donde se realiza la predicción [NinjaScriptProperty] [Range(1, int.MaxValue)] [Display(Name = "Prediction Horizon", Description = "Specify the number of bars ahead to predict", Order = 420, GroupName = "Properties")] public int PredictionHorizon { get; set; } // Informa cada cuántas barras debe ejecutarse el modelo: entrenamiento + predicción [NinjaScriptProperty] [Range(50, int.MaxValue)] [Display(Name = "Prediction Loop", Description = "Adjust how often the model retrains and makes prediction", Order = 520, GroupName = "Properties")] public int PredictionLoop { get; set; } [NinjaScriptProperty] [Range(1, int.MaxValue)] [Display(Name = "Number of Leaves", Description = "Controls the complexity of each tree", Order = 120, GroupName = "Model Properties")] public int NumberOfLeaves { get; set; } [NinjaScriptProperty] [Range(1, int.MaxValue)] [Display(Name = "MinimumExampleCounterPerLeaf", Description = "Ensures generalization by setting a minimum number of samples per leaf", Order = 320, GroupName = "Model Properties")] public int MinimumExampleCounterPerLeaf { get; set; } [NinjaScriptProperty] [Range(0.0, double.MaxValue)] [Display(Name = "Learning Rate", Description = "Adjusts the weight updates during training to balance speed and accuracy", Order = 220, GroupName = "Model Properties")] public double LearningRate { get; set; } #endregion private MLContext mlContext; private TransformerChain> closeModel; #region OnStateChange protected override void OnStateChange() { if (State == State.SetDefaults) { #region SetDefaults Description = @"Enter the description for your new custom Indicator here."; Name = "Zi8ML01Prediction01"; Calculate = Calculate.OnBarClose; IsOverlay = true; DisplayInDataBox = true; DrawOnPricePanel = true; DrawHorizontalGridLines = true; DrawVerticalGridLines = true; PaintPriceMarkers = true; ScaleJustification = NinjaTrader.Gui.Chart.ScaleJustification.Right; //Disable this property if your indicator requires custom values that cumulate with each new market data event. //See Help Guide for additional information. IsSuspendedWhileInactive = true; TrainingPeriod = 500; LookbackPeriod = 12; PredictionHorizon = 14; PredictionLoop = 300; NumberOfLeaves = 50; MinimumExampleCounterPerLeaf = 10; LearningRate = 0.25; #endregion } else if (State == State.DataLoaded) { mlContext = new MLContext(); } } #endregion protected override void OnBarUpdate() { if (CurrentBar < PredictionHorizon + TrainingPeriod) return; try { if (CurrentBar % PredictionLoop == 0) { Print($"\r\n{CurrentBar} Iniciando proceso predictivo..."); TrainModel(); PredictLevels(); } } catch (Exception ex) { Print($"Error en OnBarUpdate: {ex.ToString()}"); } } #region CalculateIndices private List CalculateIndices(int period) { var indices = new List(); double step = period / 3.0; for(int i = 0; i <= 3; i++) { indices.Add((int)Math.Round(i * step)); } return indices; } #endregion #region PrepareData private List PrepareData() { List data = new List(); List idx = CalculateIndices(LookbackPeriod); for(int i = 1; i < TrainingPeriod; i++) { data.Add(new BarData { Open1 = (float)Open[i + PredictionHorizon], Open2 = (float)Open[i + PredictionHorizon + idx[1]], Open3 = (float)Open[i + PredictionHorizon + idx[2]], Open4 = (float)Open[i + PredictionHorizon + idx[3]], High1 = (float)High[i + PredictionHorizon], High2 = (float)High[i + PredictionHorizon + idx[1]], High3 = (float)High[i + PredictionHorizon + idx[2]], High4 = (float)High[i + PredictionHorizon + idx[3]], Low1 = (float)Low[i + PredictionHorizon], Low2 = (float)Low[i + PredictionHorizon + idx[1]], Low3 = (float)Low[i + PredictionHorizon + idx[2]], Low4 = (float)Low[i + PredictionHorizon + idx[3]], Close1 = (float)Close[i + PredictionHorizon], Close2 = (float)Close[i + PredictionHorizon + idx[1]], Close3 = (float)Close[i + PredictionHorizon + idx[2]], Close4 = (float)Close[i + PredictionHorizon + idx[3]], ATR1 = (float)ATR(1)[i + PredictionHorizon], ATR2 = (float)ATR(1)[i + PredictionHorizon + idx[1]], ATR3 = (float)ATR(1)[i + PredictionHorizon + idx[2]], ATR4 = (float)ATR(1)[i + PredictionHorizon + idx[3]], EMA1 = (float)EMA(LookbackPeriod)[i + PredictionHorizon], EMA2 = (float)EMA(LookbackPeriod)[i + PredictionHorizon + idx[1]], EMA3 = (float)EMA(LookbackPeriod)[i + PredictionHorizon + idx[2]], EMA4 = (float)EMA(LookbackPeriod)[i + PredictionHorizon + idx[3]], FutureClose = (float)Close[i] }); } return data; } #endregion #region TrainModel private void TrainModel() { List data = PrepareData(); if(!data.Any()) { Print("Error: no hay datos disponibles para el entrenamiento"); return; } var trainingDataView = mlContext.Data.LoadFromEnumerable(data); try { var pipelineClose = mlContext.Transforms.Concatenate("Features", "Open1", "Open2", "Open3", "Open4", "High1", "High2", "High3", "High4", "Low1", "Low2", "Low3", "Low4", "Close1", "Close2", "Close3", "Close4", "ATR1", "ATR2", "ATR3", "ATR4", "EMA1", "EMA2", "EMA3", "EMA4") .Append(mlContext.Transforms.CopyColumns(outputColumnName: "Label", inputColumnName: "FutureClose")) .Append(mlContext.Regression.Trainers.FastTree(numberOfLeaves: NumberOfLeaves, minimumExampleCountPerLeaf: MinimumExampleCounterPerLeaf, learningRate: LearningRate)); closeModel = pipelineClose.Fit(trainingDataView); var metricsClose = mlContext.Regression.Evaluate(closeModel.Transform(trainingDataView)); Print($"RSquared: {metricsClose.RSquared} RMSE: {metricsClose.RootMeanSquaredError}"); Print("TrainModel completado con éxito"); } catch (Exception ex) { Print($"Error durante el TrainModel: {ex.ToString()}"); } } #endregion #region PredictLevels private void PredictLevels() { if(closeModel == null) { Print("Error: modelo no entrenado... no hay predicción"); return; } try { var predictionEngineClose = mlContext.Model.CreatePredictionEngine(closeModel); List idx = CalculateIndices(LookbackPeriod); var currentbarData = new BarData { Open1 = (float)Open[0], Open2 = (float)Open[idx[1]], Open3 = (float)Open[idx[2]], Open4 = (float)Open[idx[3]], High1 = (float)High[0], High2 = (float)High[idx[1]], High3 = (float)High[idx[2]], High4 = (float)High[idx[3]], Low1 = (float)Low[0], Low2 = (float)Low[idx[1]], Low3 = (float)Low[idx[2]], Low4 = (float)Low[idx[3]], Close1 = (float)Close[0], Close2 = (float)Close[idx[1]], Close3 = (float)Close[idx[2]], Close4 = (float)Close[idx[3]], ATR1 = (float)ATR(1)[0], ATR2 = (float)ATR(1)[idx[1]], ATR3 = (float)ATR(1)[idx[2]], ATR4 = (float)ATR(1)[idx[3]], EMA1 = (float)EMA(LookbackPeriod)[0], EMA2 = (float)EMA(LookbackPeriod)[idx[1]], EMA3 = (float)EMA(LookbackPeriod)[idx[2]], EMA4 = (float)EMA(LookbackPeriod)[idx[3]], }; var predictionClose = predictionEngineClose.Predict(currentbarData); Print($"Predicción = {predictionClose.FutureClose}"); Draw.ArrowLine(this, "PredictDirection " + CurrentBar + " " + PredictionHorizon, 0, Close[0], -PredictionHorizon, predictionClose.FutureClose, Close[0] < predictionClose.FutureClose ? Brushes.DodgerBlue : Close[0] > predictionClose.FutureClose ? Brushes.Magenta : Brushes.Silver, DashStyleHelper.Solid, 3); } catch (Exception ex) { Print($"Error durante la predicción: {ex.ToString()}"); } } #endregion } } namespace NinjaTrader.NinjaScript.Indicators.MLNET.nsZi8ML01Prediction01 { public class BarData { public float Open1 { get; set; } public float Open2 { get; set; } public float Open3 { get; set; } public float Open4 { get; set; } public float High1 { get; set; } public float High2 { get; set; } public float High3 { get; set; } public float High4 { get; set; } public float Low1 { get; set; } public float Low2 { get; set; } public float Low3 { get; set; } public float Low4 { get; set; } public float Close1 { get; set; } public float Close2 { get; set; } public float Close3 { get; set; } public float Close4 { get; set; } public float ATR1 { get; set; } public float ATR2 { get; set; } public float ATR3 { get; set; } public float ATR4 { get; set; } public float EMA1 { get; set; } public float EMA2 { get; set; } public float EMA3 { get; set; } public float EMA4 { get; set; } public float FutureClose { get; set; } } public class ClosePrediction { [ColumnName("Score")] public float FutureClose { get; set; } } } #region NinjaScript generated code. Neither change nor remove. namespace NinjaTrader.NinjaScript.Indicators { public partial class Indicator : NinjaTrader.Gui.NinjaScript.IndicatorRenderBase { private MLNET.Zi8ML01Prediction01[] cacheZi8ML01Prediction01; public MLNET.Zi8ML01Prediction01 Zi8ML01Prediction01(int trainingPeriod, int lookbackPeriod, int predictionHorizon, int predictionLoop, int numberOfLeaves, int minimumExampleCounterPerLeaf, double learningRate) { return Zi8ML01Prediction01(Input, trainingPeriod, lookbackPeriod, predictionHorizon, predictionLoop, numberOfLeaves, minimumExampleCounterPerLeaf, learningRate); } public MLNET.Zi8ML01Prediction01 Zi8ML01Prediction01(ISeries input, int trainingPeriod, int lookbackPeriod, int predictionHorizon, int predictionLoop, int numberOfLeaves, int minimumExampleCounterPerLeaf, double learningRate) { if (cacheZi8ML01Prediction01 != null) for (int idx = 0; idx < cacheZi8ML01Prediction01.Length; idx++) if (cacheZi8ML01Prediction01[idx] != null && cacheZi8ML01Prediction01[idx].TrainingPeriod == trainingPeriod && cacheZi8ML01Prediction01[idx].LookbackPeriod == lookbackPeriod && cacheZi8ML01Prediction01[idx].PredictionHorizon == predictionHorizon && cacheZi8ML01Prediction01[idx].PredictionLoop == predictionLoop && cacheZi8ML01Prediction01[idx].NumberOfLeaves == numberOfLeaves && cacheZi8ML01Prediction01[idx].MinimumExampleCounterPerLeaf == minimumExampleCounterPerLeaf && cacheZi8ML01Prediction01[idx].LearningRate == learningRate && cacheZi8ML01Prediction01[idx].EqualsInput(input)) return cacheZi8ML01Prediction01[idx]; return CacheIndicator(new MLNET.Zi8ML01Prediction01(){ TrainingPeriod = trainingPeriod, LookbackPeriod = lookbackPeriod, PredictionHorizon = predictionHorizon, PredictionLoop = predictionLoop, NumberOfLeaves = numberOfLeaves, MinimumExampleCounterPerLeaf = minimumExampleCounterPerLeaf, LearningRate = learningRate }, input, ref cacheZi8ML01Prediction01); } } } namespace NinjaTrader.NinjaScript.MarketAnalyzerColumns { public partial class MarketAnalyzerColumn : MarketAnalyzerColumnBase { public Indicators.MLNET.Zi8ML01Prediction01 Zi8ML01Prediction01(int trainingPeriod, int lookbackPeriod, int predictionHorizon, int predictionLoop, int numberOfLeaves, int minimumExampleCounterPerLeaf, double learningRate) { return indicator.Zi8ML01Prediction01(Input, trainingPeriod, lookbackPeriod, predictionHorizon, predictionLoop, numberOfLeaves, minimumExampleCounterPerLeaf, learningRate); } public Indicators.MLNET.Zi8ML01Prediction01 Zi8ML01Prediction01(ISeries input , int trainingPeriod, int lookbackPeriod, int predictionHorizon, int predictionLoop, int numberOfLeaves, int minimumExampleCounterPerLeaf, double learningRate) { return indicator.Zi8ML01Prediction01(input, trainingPeriod, lookbackPeriod, predictionHorizon, predictionLoop, numberOfLeaves, minimumExampleCounterPerLeaf, learningRate); } } } namespace NinjaTrader.NinjaScript.Strategies { public partial class Strategy : NinjaTrader.Gui.NinjaScript.StrategyRenderBase { public Indicators.MLNET.Zi8ML01Prediction01 Zi8ML01Prediction01(int trainingPeriod, int lookbackPeriod, int predictionHorizon, int predictionLoop, int numberOfLeaves, int minimumExampleCounterPerLeaf, double learningRate) { return indicator.Zi8ML01Prediction01(Input, trainingPeriod, lookbackPeriod, predictionHorizon, predictionLoop, numberOfLeaves, minimumExampleCounterPerLeaf, learningRate); } public Indicators.MLNET.Zi8ML01Prediction01 Zi8ML01Prediction01(ISeries input , int trainingPeriod, int lookbackPeriod, int predictionHorizon, int predictionLoop, int numberOfLeaves, int minimumExampleCounterPerLeaf, double learningRate) { return indicator.Zi8ML01Prediction01(input, trainingPeriod, lookbackPeriod, predictionHorizon, predictionLoop, numberOfLeaves, minimumExampleCounterPerLeaf, learningRate); } } } #endregion