167 lines
No EOL
6.1 KiB
MQL5
167 lines
No EOL
6.1 KiB
MQL5
//+------------------------------------------------------------------+
| |
//| 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;
| |
}
| |
};
| |
//+------------------------------------------------------------------+ |