2025-09-20 02:27:35 -04:00 | | | // include guard for MQL5
|
| | | #ifndef __POLICYENGINE_MQH__
|
| | | #define __POLICYENGINE_MQH__
|
| | | //+------------------------------------------------------------------+
|
| | | //| PolicyEngine.mqh |
|
2025-09-24 17:58:57 -04:00 | | | //| Purpose: Centralized policy loading/queries with JSON support |
|
| | | //| Status: Full implementation with file-based policy management |
|
2025-09-20 02:27:35 -04:00 | | | //+------------------------------------------------------------------+
|
| | |
|
2025-09-24 17:58:57 -04:00 | | | #include <Files/File.mqh>
|
2025-10-16 18:03:12 -04:00 | | | #include "LogMiddleware.mqh"
|
2025-09-24 17:58:57 -04:00 | | |
|
| | | struct PolicySlice
|
| | | {
|
| | | string strategy;
|
| | | string symbol;
|
| | | int timeframe;
|
| | | double probability;
|
| | | double sl_scale;
|
| | | double tp_scale;
|
| | | double trail_atr_mult;
|
| | | double min_confidence;
|
| | | };
|
| | |
|
2025-09-20 02:27:35 -04:00 | | | class CPolicyEngine
|
| | | {
|
2025-09-24 17:58:57 -04:00 | | | private:
|
| | | string m_policy_path;
|
| | | bool m_loaded;
|
| | | PolicySlice m_slices[];
|
2025-10-16 18:03:12 -04:00 | | | string m_slice_keys[];
|
| | | int m_slice_indices[];
|
2025-09-24 17:58:57 -04:00 | | |
|
| | | string GetSliceKey(const string strategy, const string symbol, const ENUM_TIMEFRAMES timeframe) const
|
| | | {
|
| | | return StringFormat("%s_%s_%d", strategy, symbol, (int)timeframe);
|
| | | }
|
| | |
|
2025-10-16 18:03:12 -04:00 | | | void ClearIndex()
|
| | | {
|
| | | ArrayResize(m_slice_keys, 0);
|
| | | ArrayResize(m_slice_indices, 0);
|
| | | }
|
| | |
|
| | | void AddIndex(const string key, const int value)
|
| | | {
|
| | | int size = ArraySize(m_slice_keys);
|
| | | ArrayResize(m_slice_keys, size + 1);
|
| | | ArrayResize(m_slice_indices, size + 1);
|
| | | m_slice_keys[size] = key;
|
| | | m_slice_indices[size] = value;
|
| | | }
|
| | |
|
| | | bool TryGetIndex(const string key, int &value) const
|
| | | {
|
| | | int size = ArraySize(m_slice_keys);
|
| | | for(int i = 0; i < size; ++i)
|
| | | {
|
| | | if(m_slice_keys[i] == key)
|
| | | {
|
| | | value = m_slice_indices[i];
|
| | | return true;
|
| | | }
|
| | | }
|
| | | return false;
|
| | | }
|
| | |
|
2025-09-24 17:58:57 -04:00 | | | bool ParseJsonLine(const string line, PolicySlice &slice)
|
| | | {
|
| | | // Simple JSON parsing for policy format
|
| | | // Expected: {"strategy":"...", "symbol":"...", "timeframe":..., "probability":..., etc}
|
| | |
|
| | | // Extract strategy
|
| | | int start = StringFind(line, "\"strategy\":\"");
|
| | | if(start < 0) return false;
|
| | | start += 12; // length of "strategy":""
|
| | | int end = StringFind(line, "\"", start);
|
| | | if(end < 0) return false;
|
| | | slice.strategy = StringSubstr(line, start, end - start);
|
| | |
|
| | | // Extract symbol
|
| | | start = StringFind(line, "\"symbol\":\"");
|
| | | if(start < 0) return false;
|
| | | start += 10; // length of "symbol":""
|
| | | end = StringFind(line, "\"", start);
|
| | | if(end < 0) return false;
|
| | | slice.symbol = StringSubstr(line, start, end - start);
|
| | |
|
| | | // Extract timeframe
|
| | | start = StringFind(line, "\"timeframe\":");
|
| | | if(start < 0) return false;
|
| | | start += 12; // length of "timeframe":"
|
| | | end = StringFind(line, ",", start);
|
| | | if(end < 0) end = StringFind(line, "}", start);
|
| | | if(end < 0) return false;
|
| | | string tf_str = StringSubstr(line, start, end - start);
|
| | | slice.timeframe = (int)StringToInteger(tf_str);
|
| | |
|
| | | // Extract probability
|
| | | start = StringFind(line, "\"probability\":");
|
| | | if(start < 0) return false;
|
| | | start += 14; // length of "probability":"
|
| | | end = StringFind(line, ",", start);
|
| | | if(end < 0) end = StringFind(line, "}", start);
|
| | | if(end < 0) return false;
|
| | | string prob_str = StringSubstr(line, start, end - start);
|
| | | slice.probability = StringToDouble(prob_str);
|
| | |
|
| | | // Extract optional fields with defaults
|
| | | slice.sl_scale = 1.0;
|
| | | slice.tp_scale = 1.0;
|
| | | slice.trail_atr_mult = 2.0;
|
| | | slice.min_confidence = 0.5;
|
| | |
|
| | | // Extract sl_scale if present
|
| | | start = StringFind(line, "\"sl_scale\":");
|
| | | if(start >= 0)
|
| | | {
|
| | | start += 11;
|
| | | end = StringFind(line, ",", start);
|
| | | if(end < 0) end = StringFind(line, "}", start);
|
| | | if(end >= 0)
|
| | | {
|
| | | string sl_str = StringSubstr(line, start, end - start);
|
| | | slice.sl_scale = StringToDouble(sl_str);
|
| | | }
|
| | | }
|
| | |
|
| | | // Extract tp_scale if present
|
| | | start = StringFind(line, "\"tp_scale\":");
|
| | | if(start >= 0)
|
| | | {
|
| | | start += 11;
|
| | | end = StringFind(line, ",", start);
|
| | | if(end < 0) end = StringFind(line, "}", start);
|
| | | if(end >= 0)
|
| | | {
|
| | | string tp_str = StringSubstr(line, start, end - start);
|
| | | slice.tp_scale = StringToDouble(tp_str);
|
| | | }
|
| | | }
|
| | |
|
| | | // Extract trail_atr_mult if present
|
| | | start = StringFind(line, "\"trail_atr_mult\":");
|
| | | if(start >= 0)
|
| | | {
|
| | | start += 17;
|
| | | end = StringFind(line, ",", start);
|
| | | if(end < 0) end = StringFind(line, "}", start);
|
| | | if(end >= 0)
|
| | | {
|
| | | string trail_str = StringSubstr(line, start, end - start);
|
| | | slice.trail_atr_mult = StringToDouble(trail_str);
|
| | | }
|
| | | }
|
| | |
|
| | | // Extract min_confidence if present
|
| | | start = StringFind(line, "\"min_confidence\":");
|
| | | if(start >= 0)
|
| | | {
|
| | | start += 17;
|
| | | end = StringFind(line, ",", start);
|
| | | if(end < 0) end = StringFind(line, "}", start);
|
| | | if(end >= 0)
|
| | | {
|
| | | string conf_str = StringSubstr(line, start, end - start);
|
| | | slice.min_confidence = StringToDouble(conf_str);
|
| | | }
|
| | | }
|
| | |
|
| | | return true;
|
| | | }
|
| | |
|
2025-09-20 02:27:35 -04:00 | | | public:
|
2025-09-24 17:58:57 -04:00 | | | CPolicyEngine()
|
| | | {
|
| | | m_loaded = false;
|
| | | m_policy_path = "";
|
| | | ArrayResize(m_slices, 0);
|
| | | }
|
2025-09-20 02:27:35 -04:00 | | |
|
| | | bool Load(const string path)
|
| | | {
|
2025-09-24 17:58:57 -04:00 | | | m_policy_path = path;
|
| | | m_loaded = false;
|
| | | ArrayResize(m_slices, 0);
|
2025-10-16 18:03:12 -04:00 | | | ClearIndex();
|
2025-09-24 17:58:57 -04:00 | | |
|
| | | // Try to open policy file
|
| | | int h = FileOpen(path, FILE_READ|FILE_TXT|FILE_COMMON);
|
| | | if(h == INVALID_HANDLE)
|
| | | {
|
| | | h = FileOpen(path, FILE_READ|FILE_TXT); // Try user files
|
| | | if(h == INVALID_HANDLE)
|
| | | {
|
2025-10-16 18:03:12 -04:00 | | | LOG(StringFormat("PolicyEngine: Cannot open policy file %s, error %d", path, GetLastError()));
|
2025-09-24 17:58:57 -04:00 | | | return false;
|
| | | }
|
| | | }
|
| | |
|
2026-02-24 12:47:37 -05:00 | | | // Read entire file content
|
| | | string content = "";
|
2025-09-24 17:58:57 -04:00 | | | while(!FileIsEnding(h))
|
| | | {
|
2026-02-24 12:47:37 -05:00 | | | content += FileReadString(h);
|
| | | }
|
| | | FileClose(h);
|
| | |
|
| | | // Find policies array
|
| | | int policiesStart = StringFind(content, "\"policies\":");
|
| | | if(policiesStart < 0)
|
| | | {
|
| | | LOG("PolicyEngine: No policies array found in JSON");
|
| | | return false;
|
| | | }
|
| | |
|
| | | // Find array start
|
| | | int arrayStart = StringFind(content, "[", policiesStart);
|
| | | if(arrayStart < 0)
|
| | | {
|
| | | LOG("PolicyEngine: Invalid policies array format");
|
| | | return false;
|
| | | }
|
| | |
|
| | | // Find array end (matching bracket)
|
| | | int arrayEnd = -1;
|
| | | int bracketCount = 0;
|
| | | for(int i = arrayStart; i < StringLen(content); i++)
|
| | | {
|
| | | if(StringSubstr(content, i, 1) == "[") bracketCount++;
|
| | | else if(StringSubstr(content, i, 1) == "]") bracketCount--;
|
| | | if(bracketCount == 0)
|
| | | {
|
| | | arrayEnd = i;
|
| | | break;
|
| | | }
|
| | | }
|
| | |
|
| | | if(arrayEnd < 0)
|
| | | {
|
| | | LOG("PolicyEngine: Could not find end of policies array");
|
| | | return false;
|
| | | }
|
| | |
|
| | | // Extract array content
|
| | | string arrayContent = StringSubstr(content, arrayStart + 1, arrayEnd - arrayStart - 1);
|
| | |
|
| | | // Parse individual policy objects
|
| | | int slice_count = 0;
|
| | | int pos = 0;
|
| | | while(pos < StringLen(arrayContent))
|
| | | {
|
| | | // Find next object start
|
| | | int objStart = StringFind(arrayContent, "{", pos);
|
| | | if(objStart < 0) break;
|
| | |
|
| | | // Find matching end
|
| | | int objEnd = -1;
|
| | | int braceCount = 0;
|
| | | for(int i = objStart; i < StringLen(arrayContent); i++)
|
| | | {
|
| | | if(StringSubstr(arrayContent, i, 1) == "{") braceCount++;
|
| | | else if(StringSubstr(arrayContent, i, 1) == "}") braceCount--;
|
| | | if(braceCount == 0)
|
| | | {
|
| | | objEnd = i;
|
| | | break;
|
| | | }
|
| | | }
|
| | |
|
| | | if(objEnd < 0) break;
|
2025-09-24 17:58:57 -04:00 | | |
|
2026-02-24 12:47:37 -05:00 | | | // Extract and parse object
|
| | | string objStr = StringSubstr(arrayContent, objStart, objEnd - objStart + 1);
|
2025-09-24 17:58:57 -04:00 | | | PolicySlice slice;
|
2026-02-24 12:47:37 -05:00 | | | if(ParseJsonObject(objStr, slice))
|
2025-09-24 17:58:57 -04:00 | | | {
|
| | | ArrayResize(m_slices, slice_count + 1);
|
| | | m_slices[slice_count] = slice;
|
| | |
|
| | | string key = GetSliceKey(slice.strategy, slice.symbol, (ENUM_TIMEFRAMES)slice.timeframe);
|
2025-10-16 18:03:12 -04:00 | | | AddIndex(key, slice_count);
|
2025-09-24 17:58:57 -04:00 | | |
|
| | | slice_count++;
|
| | | }
|
2026-02-24 12:47:37 -05:00 | | |
|
| | | pos = objEnd + 1;
|
2025-09-24 17:58:57 -04:00 | | | }
|
| | |
|
| | | m_loaded = (slice_count > 0);
|
2025-10-16 18:03:12 -04:00 | | | LOG(StringFormat("PolicyEngine: Loaded %d policy slices from %s", slice_count, path));
|
2025-09-24 17:58:57 -04:00 | | |
|
| | | return m_loaded;
|
2025-09-20 02:27:35 -04:00 | | | }
|
| | |
|
2026-02-24 12:47:37 -05:00 | | | private:
|
| | | // Parse JSON object (single policy entry)
|
| | | bool ParseJsonObject(const string objStr, PolicySlice &slice)
|
| | | {
|
| | | // Set defaults
|
| | | slice.strategy = "";
|
| | | slice.symbol = "";
|
| | | slice.timeframe = 0;
|
| | | slice.probability = 0.0;
|
| | | slice.sl_scale = 1.0;
|
| | | slice.tp_scale = 1.0;
|
| | | slice.trail_atr_mult = 2.0;
|
| | | slice.min_confidence = 0.5;
|
| | |
|
| | | // Extract strategy
|
| | | int start = StringFind(objStr, "\"strategy\":");
|
| | | if(start >= 0)
|
| | | {
|
| | | start = StringFind(objStr, "\"", start + 11);
|
| | | if(start >= 0)
|
| | | {
|
| | | start++;
|
| | | int end = StringFind(objStr, "\"", start);
|
| | | if(end > start)
|
| | | slice.strategy = StringSubstr(objStr, start, end - start);
|
| | | }
|
| | | }
|
| | |
|
| | | // Extract symbol
|
| | | start = StringFind(objStr, "\"symbol\":");
|
| | | if(start >= 0)
|
| | | {
|
| | | start = StringFind(objStr, "\"", start + 9);
|
| | | if(start >= 0)
|
| | | {
|
| | | start++;
|
| | | int end = StringFind(objStr, "\"", start);
|
| | | if(end > start)
|
| | | slice.symbol = StringSubstr(objStr, start, end - start);
|
| | | }
|
| | | }
|
| | |
|
| | | // Extract timeframe
|
| | | start = StringFind(objStr, "\"timeframe\":");
|
| | | if(start >= 0)
|
| | | {
|
| | | start += 12;
|
| | | int end = StringFind(objStr, ",", start);
|
| | | if(end < 0) end = StringFind(objStr, "}", start);
|
| | | if(end > start)
|
| | | {
|
| | | string tf_str = StringSubstr(objStr, start, end - start);
|
| | | StringReplace(tf_str, " ", "");
|
| | | slice.timeframe = (int)StringToInteger(tf_str);
|
| | | }
|
| | | }
|
| | |
|
| | | // Extract probability
|
| | | start = StringFind(objStr, "\"probability\":");
|
| | | if(start >= 0)
|
| | | {
|
| | | start += 14;
|
| | | int end = StringFind(objStr, ",", start);
|
| | | if(end < 0) end = StringFind(objStr, "}", start);
|
| | | if(end > start)
|
| | | {
|
| | | string p_str = StringSubstr(objStr, start, end - start);
|
| | | slice.probability = StringToDouble(p_str);
|
| | | }
|
| | | }
|
| | |
|
| | | // Extract sl_scale
|
| | | start = StringFind(objStr, "\"sl_scale\":");
|
| | | if(start >= 0)
|
| | | {
|
| | | start += 11;
|
| | | int end = StringFind(objStr, ",", start);
|
| | | if(end < 0) end = StringFind(objStr, "}", start);
|
| | | if(end > start)
|
| | | {
|
| | | string s = StringSubstr(objStr, start, end - start);
|
| | | slice.sl_scale = StringToDouble(s);
|
| | | }
|
| | | }
|
| | |
|
| | | // Extract tp_scale
|
| | | start = StringFind(objStr, "\"tp_scale\":");
|
| | | if(start >= 0)
|
| | | {
|
| | | start += 11;
|
| | | int end = StringFind(objStr, ",", start);
|
| | | if(end < 0) end = StringFind(objStr, "}", start);
|
| | | if(end > start)
|
| | | {
|
| | | string s = StringSubstr(objStr, start, end - start);
|
| | | slice.tp_scale = StringToDouble(s);
|
| | | }
|
| | | }
|
| | |
|
| | | // Extract trail_atr_mult
|
| | | start = StringFind(objStr, "\"trail_atr_mult\":");
|
| | | if(start >= 0)
|
| | | {
|
| | | start += 17;
|
| | | int end = StringFind(objStr, ",", start);
|
| | | if(end < 0) end = StringFind(objStr, "}", start);
|
| | | if(end > start)
|
| | | {
|
| | | string s = StringSubstr(objStr, start, end - start);
|
| | | slice.trail_atr_mult = StringToDouble(s);
|
| | | }
|
| | | }
|
| | |
|
| | | // Extract min_confidence
|
| | | start = StringFind(objStr, "\"min_confidence\":");
|
| | | if(start >= 0)
|
| | | {
|
| | | start += 18;
|
| | | int end = StringFind(objStr, ",", start);
|
| | | if(end < 0) end = StringFind(objStr, "}", start);
|
| | | if(end > start)
|
| | | {
|
| | | string s = StringSubstr(objStr, start, end - start);
|
| | | slice.min_confidence = StringToDouble(s);
|
| | | }
|
| | | }
|
| | |
|
| | | return (slice.strategy != "" && slice.symbol != "");
|
| | | }
|
| | |
|
| | | public:
|
| | |
|
2025-09-25 00:28:11 -04:00 | | | double GetPolicyProb(const string strategy, const string symbol, const ENUM_TIMEFRAMES timeframe)
|
2025-09-20 02:27:35 -04:00 | | | {
|
| | | // Sanity: invalid timeframe -> unknown
|
2025-09-24 17:58:57 -04:00 | | | if((int)timeframe <= 0) return -1.0;
|
| | |
|
| | | if(!m_loaded) return -1.0;
|
| | |
|
| | | string key = GetSliceKey(strategy, symbol, timeframe);
|
| | | int index;
|
| | |
|
2025-10-16 18:03:12 -04:00 | | | if(TryGetIndex(key, index))
|
2025-09-24 17:58:57 -04:00 | | | {
|
| | | if(index >= 0 && index < ArraySize(m_slices))
|
| | | {
|
| | | double p = m_slices[index].probability;
|
| | | // Defensive: never return NaN
|
| | | if(!MathIsValidNumber(p)) return -1.0;
|
| | | // Clamp to [0,1] for valid probabilities
|
| | | if(p < 0.0) return -1.0;
|
| | | if(p > 1.0) return 1.0;
|
| | | return p;
|
| | | }
|
| | | }
|
| | |
|
| | | return -1.0; // No slice found
|
| | | }
|
| | |
|
2025-09-25 00:28:11 -04:00 | | | bool HasSlice(const string strategy, const string symbol, const ENUM_TIMEFRAMES timeframe)
|
2025-09-24 17:58:57 -04:00 | | | {
|
| | | if(!m_loaded) return false;
|
| | |
|
| | | string key = GetSliceKey(strategy, symbol, timeframe);
|
| | | int index;
|
2025-10-16 18:03:12 -04:00 | | | return TryGetIndex(key, index);
|
2025-09-24 17:58:57 -04:00 | | | }
|
| | |
|
| | | // Get full policy slice data
|
2025-09-25 00:28:11 -04:00 | | | bool GetPolicySlice(const string strategy, const string symbol, const ENUM_TIMEFRAMES timeframe, PolicySlice &slice)
|
2025-09-24 17:58:57 -04:00 | | | {
|
| | | if(!m_loaded) return false;
|
| | |
|
| | | string key = GetSliceKey(strategy, symbol, timeframe);
|
| | | int index;
|
| | |
|
2025-10-16 18:03:12 -04:00 | | | if(TryGetIndex(key, index))
|
2025-09-24 17:58:57 -04:00 | | | {
|
| | | if(index >= 0 && index < ArraySize(m_slices))
|
| | | {
|
| | | slice = m_slices[index];
|
| | | return true;
|
| | | }
|
| | | }
|
| | |
|
| | | return false;
|
| | | }
|
| | |
|
| | | // Get policy scaling factors
|
2025-09-25 00:28:11 -04:00 | | | bool GetPolicyScaling(string strategy, string symbol, ENUM_TIMEFRAMES timeframe,
|
| | | double &sl_scale, double &tp_scale, double &trail_atr_mult)
|
2025-09-24 17:58:57 -04:00 | | | {
|
| | | PolicySlice slice;
|
| | | if(GetPolicySlice(strategy, symbol, timeframe, slice))
|
| | | {
|
| | | sl_scale = slice.sl_scale;
|
2025-10-16 18:03:12 -04:00 | | | tp_scale = slice.tp_scale;
|
2025-09-24 17:58:57 -04:00 | | | trail_atr_mult = slice.trail_atr_mult;
|
| | | return true;
|
| | | }
|
| | |
|
| | | // Default values
|
| | | sl_scale = 1.0;
|
| | | tp_scale = 1.0;
|
| | | trail_atr_mult = 2.0;
|
| | | return false;
|
| | | }
|
| | |
|
| | | // Get loaded status and statistics
|
2025-09-25 00:28:11 -04:00 | | | bool IsLoaded() { return m_loaded; }
|
2025-09-24 17:58:57 -04:00 | | | int GetSliceCount() const { return ArraySize(m_slices); }
|
| | | string GetPolicyPath() const { return m_policy_path; }
|
| | |
|
| | | // Reload policy from same path
|
| | | bool Reload()
|
| | | {
|
| | | if(StringLen(m_policy_path) == 0) return false;
|
| | | return Load(m_policy_path);
|
| | | }
|
2025-09-20 02:27:35 -04:00 | | | };
|
| | |
|
| | | #endif // __POLICYENGINE_MQH__
|