mql5/Experts/_Architecture/State/StatePersistenceManager.mqh

167 lines
6.1 KiB
MQL5

2026-08-14 16:19:56 +02:00
//+------------------------------------------------------------------+
//| StatePersistenceManager.mqh |
//| Copyright 2025, MetaQuotes Ltd. |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, MetaQuotes Ltd."
#property link "https://www.mql5.com"
#property version "1.00"
//+------------------------------------------------------------------+
//| StatePersistenceManager.mqh |
//+------------------------------------------------------------------+
#include "../Analysers/IAnalyser.mqh"
#include <JSON.mqh> // your JSON class
#include <Arrays\List.mqh>
class StatePersistenceManager
{
private:
string m_fileName;
JSON::Object* m_root; // holds all current state
// Internal structure to hold a snapshot entry
struct Snapshot
{
string analyserName;
string symbol;
ENUM_TIMEFRAMES tf;
JSON::Object* state;
};
Snapshot m_snapshots[]; // current in-memory snapshots
int m_snapshotCount;
// Helpers to find a snapshot by analyser/symbol/tf
int FindSnapshot(string analyserName, string symbol, ENUM_TIMEFRAMES tf, int startFromEnd = 0)
{
// If startFromEnd = 0, search all; if 1, search from end to get latest
for(int i = m_snapshotCount-1; i >= 0; i--)
{
if(m_snapshots[i].analyserName == analyserName &&
m_snapshots[i].symbol == symbol &&
m_snapshots[i].tf == tf)
return i;
}
return -1;
}
public:
StatePersistenceManager(string fileName) : m_fileName(fileName), m_snapshotCount(0) {}
// Load all existing snapshots from file (if any)
bool LoadFromFile()
{
if(!FileIsExist(m_fileName))
return false; // no file yet
string jsonStr = "";
int handle = FileOpen(m_fileName, FILE_READ|FILE_TXT|FILE_ANSI);
if(handle == INVALID_HANDLE)
return false;
while(!FileIsEnding(handle))
jsonStr += FileReadString(handle, 0);
FileClose(handle);
JSON::Object* root = new JSON::Object(jsonStr);
if(root == NULL) return false;
// Parse the stored structure:
// {
// "snapshots": [
// {
// "analyser": "OrderBlockAnalyser",
// "symbol": "EURUSD",
// "tf": 60, // timeframe as integer
// "state": { ... }
// },
// ...
// ]
// }
JSON::Array* snapshots = root.getArray("snapshots");
if(snapshots == NULL) { delete root; return false; }
for(int i = 0; i < snapshots.getLength(); i++)
{
JSON::Object* snapObj = snapshots.getObject(i);
if(snapObj == NULL) continue;
string analyser = snapObj.getString("analyser");
string symbol = snapObj.getString("symbol");
int tfInt = (int)snapObj.getNumber("tf");
ENUM_TIMEFRAMES tf = (ENUM_TIMEFRAMES)tfInt;
JSON::Object* state = snapObj.getObject("state");
if(state == NULL) continue;
// Add to internal list
int idx = m_snapshotCount++;
ArrayResize(m_snapshots, idx+1);
m_snapshots[idx].analyserName = analyser;
m_snapshots[idx].symbol = symbol;
m_snapshots[idx].tf = tf;
m_snapshots[idx].state = state; // take ownership
}
delete root;
return true;
}
// Save a snapshot for a specific analyser/symbol/tf.
// If a snapshot for the same key exists, it will be replaced (but we still keep history?
// The user wants append not rewrite on reset. So we need to preserve old snapshots.
// We'll add a new snapshot entry each time, allowing multiple snapshots per key.
// Later LoadLatest will get the most recent one.
void SaveSnapshot(string analyserName, string symbol, ENUM_TIMEFRAMES tf, JSON::Object* state)
{
int idx = m_snapshotCount++;
ArrayResize(m_snapshots, idx+1);
m_snapshots[idx].analyserName = analyserName;
m_snapshots[idx].symbol = symbol;
m_snapshots[idx].tf = tf;
m_snapshots[idx].state = state;
}
// Load the latest snapshot for a given analyser/symbol/tf.
// Returns a copy? We'll return the pointer directly; caller must not delete.
JSON::Object* LoadLatest(string analyserName, string symbol, ENUM_TIMEFRAMES tf)
{
int idx = FindSnapshot(analyserName, symbol, tf); // returns latest (since we search from end)
if(idx < 0) return NULL;
return m_snapshots[idx].state;
}
// Write all snapshots to file (append or rewrite? user wants append not rewrite on reset,
// but the file itself is rewritten each time with the full list of snapshots, which includes
// all old ones because we never delete them. That effectively appends to history.)
bool SaveToFile()
{
JSON::Object* root = new JSON::Object();
JSON::Array* arr = new JSON::Array();
for(int i = 0; i < m_snapshotCount; i++)
{
JSON::Object* snapObj = new JSON::Object();
snapObj.setProperty("analyser", m_snapshots[i].analyserName);
snapObj.setProperty("symbol", m_snapshots[i].symbol);
snapObj.setProperty("tf", (int)m_snapshots[i].tf);
snapObj.setProperty("state", m_snapshots[i].state); // deep copy? JSON::Object* setProperty will store pointer; our JSON class may not deep copy. We'll assume it does or we need to clone.
arr.add(snapObj);
}
root.setProperty("snapshots", arr);
string jsonStr = root.toString();
delete root;
int handle = FileOpen(m_fileName, FILE_WRITE|FILE_TXT|FILE_ANSI);
if(handle == INVALID_HANDLE) return false;
FileWriteString(handle, jsonStr);
FileClose(handle);
return true;
}
~StatePersistenceManager()
{
for(int i = 0; i < m_snapshotCount; i++)
delete m_snapshots[i].state;
}
};
//+------------------------------------------------------------------+