2025-09-10 13:27:03 -04:00 | | | #ifndef __DUALEA_MLCLIENT_MQH__
|
| | | #define __DUALEA_MLCLIENT_MQH__
|
2025-08-27 12:44:21 -04:00 | | |
|
| | | 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]<t) lo=mid+1; else hi=mid-1;
|
| | | }
|
| | | int idx = MathMax(0, MathMin(hi, ArraySize(m_ts)-1));
|
| | | score_out=m_score[idx]; pl_out=m_prob_long[idx]; ps_out=m_prob_short[idx];
|
| | | return true;
|
| | | }
|
| | |
|
| | | bool GetLatest(double &score_out, double &pl_out, double &ps_out)
|
| | | {
|
| | | if(ArraySize(m_ts)==0) return false;
|
| | | int k = ArraySize(m_ts)-1;
|
| | | score_out = m_score[k]; pl_out = m_prob_long[k]; ps_out = m_prob_short[k];
|
| | | return true;
|
| | | }
|
| | | };
|
2025-09-10 13:27:03 -04:00 | | |
|
| | | #endif // __DUALEA_MLCLIENT_MQH__
|