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