| // include guard for MQL5
|
| #ifndef __POLICYENGINE_MQH__
|
| #define __POLICYENGINE_MQH__
|
| //+------------------------------------------------------------------+
|
| //| PolicyEngine.mqh |
|
| //| Purpose: Centralized policy loading/queries with JSON support |
|
| //| Status: Full implementation with file-based policy management |
|
| //+------------------------------------------------------------------+
|
|
|
| #include <Files/File.mqh>
|
| #include "LogMiddleware.mqh"
|
|
|
| struct PolicySlice
|
| {
|
| string strategy;
|
| string symbol;
|
| int timeframe;
|
| double probability;
|
| double sl_scale;
|
| double tp_scale;
|
| double trail_atr_mult;
|
| double min_confidence;
|
| };
|
|
|
| class CPolicyEngine
|
| {
|
| private:
|
| string m_policy_path;
|
| bool m_loaded;
|
| PolicySlice m_slices[];
|
| string m_slice_keys[];
|
| int m_slice_indices[];
|
|
|
| string GetSliceKey(const string strategy, const string symbol, const ENUM_TIMEFRAMES timeframe) const
|
| {
|
| return StringFormat("%s_%s_%d", strategy, symbol, (int)timeframe);
|
| }
|
|
|
| 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;
|
| }
|
|
|
| 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;
|
| }
|
|
|
| public:
|
| CPolicyEngine()
|
| {
|
| m_loaded = false;
|
| m_policy_path = "";
|
| ArrayResize(m_slices, 0);
|
| }
|
|
|
| bool Load(const string path)
|
| {
|
| m_policy_path = path;
|
| m_loaded = false;
|
| ArrayResize(m_slices, 0);
|
| ClearIndex();
|
|
|
| // 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)
|
| {
|
| LOG(StringFormat("PolicyEngine: Cannot open policy file %s, error %d", path, GetLastError()));
|
| return false;
|
| }
|
| }
|
|
|
| // Read entire file content
|
| string content = "";
|
| while(!FileIsEnding(h))
|
| {
|
| 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;
|
|
|
| // Extract and parse object
|
| string objStr = StringSubstr(arrayContent, objStart, objEnd - objStart + 1);
|
| PolicySlice slice;
|
| if(ParseJsonObject(objStr, slice))
|
| {
|
| ArrayResize(m_slices, slice_count + 1);
|
| m_slices[slice_count] = slice;
|
|
|
| string key = GetSliceKey(slice.strategy, slice.symbol, (ENUM_TIMEFRAMES)slice.timeframe);
|
| AddIndex(key, slice_count);
|
|
|
| slice_count++;
|
| }
|
|
|
| pos = objEnd + 1;
|
| }
|
|
|
| m_loaded = (slice_count > 0);
|
| LOG(StringFormat("PolicyEngine: Loaded %d policy slices from %s", slice_count, path));
|
|
|
| return m_loaded;
|
| }
|
|
|
| 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:
|
|
|
| double GetPolicyProb(const string strategy, const string symbol, const ENUM_TIMEFRAMES timeframe)
|
| {
|
| // Sanity: invalid timeframe -> unknown
|
| if((int)timeframe <= 0) return -1.0;
|
|
|
| if(!m_loaded) return -1.0;
|
|
|
| string key = GetSliceKey(strategy, symbol, timeframe);
|
| int index;
|
|
|
| if(TryGetIndex(key, index))
|
| {
|
| 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
|
| }
|
|
|
| bool HasSlice(const string strategy, const string symbol, const ENUM_TIMEFRAMES timeframe)
|
| {
|
| if(!m_loaded) return false;
|
|
|
| string key = GetSliceKey(strategy, symbol, timeframe);
|
| int index;
|
| return TryGetIndex(key, index);
|
| }
|
|
|
| // Get full policy slice data
|
| bool GetPolicySlice(const string strategy, const string symbol, const ENUM_TIMEFRAMES timeframe, PolicySlice &slice)
|
| {
|
| if(!m_loaded) return false;
|
|
|
| string key = GetSliceKey(strategy, symbol, timeframe);
|
| int index;
|
|
|
| if(TryGetIndex(key, index))
|
| {
|
| if(index >= 0 && index < ArraySize(m_slices))
|
| {
|
| slice = m_slices[index];
|
| return true;
|
| }
|
| }
|
|
|
| return false;
|
| }
|
|
|
| // Get policy scaling factors
|
| bool GetPolicyScaling(string strategy, string symbol, ENUM_TIMEFRAMES timeframe,
|
| double &sl_scale, double &tp_scale, double &trail_atr_mult)
|
| {
|
| PolicySlice slice;
|
| if(GetPolicySlice(strategy, symbol, timeframe, slice))
|
| {
|
| sl_scale = slice.sl_scale;
|
| tp_scale = slice.tp_scale;
|
| 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
|
| bool IsLoaded() { return m_loaded; }
|
| 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);
|
| }
|
| };
|
|
|
| #endif // __POLICYENGINE_MQH__
|