mql5/Experts/Advisors/DualEA/Include/PolicyEngine.mqh

517 lines
15 KiB
MQL5
Raw Permalink Normal View History

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__