#ifndef __DUALEA_MLCLIENT_MQH__ #define __DUALEA_MLCLIENT_MQH__ class CMLClient : public CObject { private: string m_symbol; int m_period; string m_filepath; int m_reload_secs; datetime m_last_load; // Data columns datetime m_ts[]; double m_score[]; // [-1..+1] double m_prob_long[]; // [0..1] double m_prob_short[];// [0..1] bool Load() { ArrayResize(m_ts, 0); ArrayResize(m_score, 0); ArrayResize(m_prob_long, 0); ArrayResize(m_prob_short, 0); int handle = FileOpen(m_filepath, FILE_READ|FILE_CSV|FILE_ANSI, ','); if(handle == INVALID_HANDLE) { PrintFormat("[ML] Failed to open signals file: %s", m_filepath); return false; } // Optional header skip if first token is non-numeric bool header_checked=false; while(!FileIsEnding(handle)) { string s0 = FileReadString(handle); if(StringLen(s0)==0) { FileReadString(handle); FileReadString(handle); FileReadString(handle); continue; } // If header row, skip if(!header_checked) { header_checked = true; if(StringToTime(s0)==0 && StringFind(StringToUpper(s0), "TIME")>=0) { // consume rest of header line FileReadString(handle); FileReadString(handle); FileReadString(handle); continue; } } datetime ts = (StringToTime(s0)!=0? StringToTime(s0) : (datetime)StrToInteger(s0)); string s1 = FileReadString(handle); string s2 = FileReadString(handle); string s3 = FileReadString(handle); double score = StrToDouble(s1); double pl = StrToDouble(s2); double ps = StrToDouble(s3); int k = ArraySize(m_ts); ArrayResize(m_ts, k+1); ArrayResize(m_score, k+1); ArrayResize(m_prob_long, k+1); ArrayResize(m_prob_short, k+1); m_ts[k]=ts; m_score[k]=score; m_prob_long[k]=pl; m_prob_short[k]=ps; } FileClose(handle); m_last_load = TimeCurrent(); return (ArraySize(m_ts)>0); } public: CMLClient(): m_symbol(""), m_period(0), m_filepath(""), m_reload_secs(60), m_last_load(0) {} void Configure(const string symbol, const int period, const string filepath, const int reload_secs) { m_symbol = symbol; m_period = period; m_filepath = filepath; m_reload_secs = (reload_secs>0? reload_secs:60); } bool LoadIfStale() { if(m_filepath=="" ) return false; if(m_last_load==0 || (TimeCurrent()-m_last_load)>=m_reload_secs) return Load(); return true; } bool GetNearest(const datetime t, double &score_out, double &pl_out, double &ps_out) { if(ArraySize(m_ts)==0) return false; // Binary search nearest by time int lo=0, hi=ArraySize(m_ts)-1, mid; while(lo<=hi) { mid = (lo+hi)/2; if(m_ts[mid]==t) { score_out=m_score[mid]; pl_out=m_prob_long[mid]; ps_out=m_prob_short[mid]; return true; } if(m_ts[mid]