//+------------------------------------------------------------------+
//|                                                  ONNX_Engine.mqh |
//|                                  Copyright 2026, MetaQuotes Ltd. |
//+------------------------------------------------------------------+
#property copyright "Open Source"
#property version   "1.00"

//+------------------------------------------------------------------+
//| Class: CONNXPredictor                                            |
//| Purpose: Manages ONNX session, tensor binding, and inference     |
//+------------------------------------------------------------------+
class CONNXPredictor
  {
private:
   long              m_onnx_handle;       // Unique session identifier
   string            m_model_path;        // Directory path to the .onnx file
   bool              m_is_initialized;    // State validation flag

   bool              SetTensorShapes(void);

public:
                     CONNXPredictor(string model_filename);
                    ~CONNXPredictor(void);
                    
   bool              Initialize(void);
   double            PredictDirection(const double &features[]);
   void              NormalizeData(double &features[], double min_val, double max_val);
   bool              IsReady(void) const { return m_is_initialized; }
  };

//+------------------------------------------------------------------+
//| Constructor: Assigns the file path and sets default states       |
//+------------------------------------------------------------------+
CONNXPredictor::CONNXPredictor(string model_filename)
  {
   m_model_path = model_filename;
   m_onnx_handle = INVALID_HANDLE;
   m_is_initialized = false;
  }

//+------------------------------------------------------------------+
//| Destructor: Safely releases the ONNX graph from system memory    |
//+------------------------------------------------------------------+
CONNXPredictor::~CONNXPredictor(void)
  {
   if(m_onnx_handle != INVALID_HANDLE)
     {
      OnnxRelease(m_onnx_handle);
     }
  }

//+------------------------------------------------------------------+
//| Initializes the ONNX session and binds the required tensor shapes|
//+------------------------------------------------------------------+
bool CONNXPredictor::Initialize(void)
  {
   m_onnx_handle = OnnxCreate(m_model_path, FILE_COMMON);
   
   if(m_onnx_handle == INVALID_HANDLE)
     {
      PrintFormat("Error: Failed to load ONNX model. Code: %d", GetLastError());
      return false;
     }
     
   if(!SetTensorShapes()) return false;
   
   m_is_initialized = true;
   return true;
  }

//+------------------------------------------------------------------+
//| Defines the strict mathematical boundaries for the data exchange |
//+------------------------------------------------------------------+
bool CONNXPredictor::SetTensorShapes(void)
  {
   long input_shape[] = {1, 5}; 
   if(!OnnxSetInputShape(m_onnx_handle, 0, input_shape)) return false;
   
   long output_shape[] = {1, 1}; 
   if(!OnnxSetOutputShape(m_onnx_handle, 0, output_shape)) return false;
   
   return true;
  }

//+------------------------------------------------------------------+
//| Injects live data into the graph and extracts the prediction     |
//+------------------------------------------------------------------+
double CONNXPredictor::PredictDirection(const double &features[])
  {
   if(!m_is_initialized || ArraySize(features) != 5) return 0.0;
   
   double vector_input[];
   double vector_output[1];
   
   ArrayCopy(vector_input, features);
   
   if(!OnnxRun(m_onnx_handle, ONNX_NO_CONVERSION, vector_input, vector_output))
     {
      Print("Error: ONNX inference execution failed.");
      return 0.0;
     }
     
   return vector_output[0];
  }

//+------------------------------------------------------------------+
//| Applies Min-Max Normalization to the extracted feature array     |
//+------------------------------------------------------------------+
void CONNXPredictor::NormalizeData(double &features[], double min_val, double max_val)
  {
   int total = ArraySize(features);
   double range = max_val - min_val;
   
   if(range <= 0.0) return; // Prevent division by zero
   
   for(int i = 0; i < total; i++)
     {
      features[i] = (features[i] - min_val) / range;
      
      // Clamp values to ensure strict boundaries
      if(features[i] > 1.0) features[i] = 1.0;
      if(features[i] < 0.0) features[i] = 0.0;
     }
  }