221 lines
8.1 KiB
MQL5
221 lines
8.1 KiB
MQL5
//+------------------------------------------------------------------+
| |||
//| CSQLiteExplorationCounter.mqh - SQLite-based Atomic Counters |
| |||
//| P1-5: SQLite exploration counters with zero external deps |
| |||
//| Replaces Redis with MT5 native SQLite (Build 2340+) |
| |||
//+------------------------------------------------------------------+
| |||
#ifndef CSQLITEEXPLORATIONCOUNTER_MQH
| |||
#define CSQLITEEXPLORATIONCOUNTER_MQH
| |||
| |||
#include <Trade\Trade.mqh>
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| SQLite Exploration Counter - Zero External Dependencies |
| |||
//+------------------------------------------------------------------+
| |||
class CSQLiteExplorationCounter
| |||
{
| |||
private:
| |||
int m_db;
| |||
string m_dbPath;
| |||
bool m_initialized;
| |||
| |||
public:
| |||
CSQLiteExplorationCounter() : m_db(INVALID_HANDLE), m_initialized(false)
| |||
{
| |||
m_dbPath = "DualEA" + IntegerToString(AccountInfoInteger(ACCOUNT_LOGIN)) + "_explore.db";
| |||
}
| |||
| |||
~CSQLiteExplorationCounter()
| |||
{
| |||
Shutdown();
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Initialize SQLite database |
| |||
//+------------------------------------------------------------------+
| |||
bool Initialize()
| |||
{
| |||
if(m_initialized) return true;
| |||
| |||
// Open database (creates if not exists)
| |||
m_db = DatabaseOpen(m_dbPath, DATABASE_OPEN_READWRITE | DATABASE_OPEN_CREATE | DATABASE_OPEN_COMMON);
| |||
if(m_db == INVALID_HANDLE)
| |||
{
| |||
Print("[CSQLiteExplorationCounter] Failed to open database: ", GetLastError());
| |||
return false;
| |||
}
| |||
| |||
// Create counters table
| |||
string sql = "CREATE TABLE IF NOT EXISTS exploration_counters ("
| |||
"key TEXT PRIMARY KEY,"
| |||
"count INTEGER DEFAULT 0,"
| |||
"last_updated DATETIME DEFAULT CURRENT_TIMESTAMP,"
| |||
"symbol TEXT,"
| |||
"strategy TEXT)";
| |||
| |||
if(!DatabaseExecute(m_db, sql))
| |||
{
| |||
Print("[CSQLiteExplorationCounter] Failed to create table: ", GetLastError());
| |||
DatabaseClose(m_db);
| |||
m_db = INVALID_HANDLE;
| |||
return false;
| |||
}
| |||
| |||
m_initialized = true;
| |||
Print("[CSQLiteExplorationCounter] SQLite initialized: ", m_dbPath);
| |||
return true;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Increment counter atomically |
| |||
//+------------------------------------------------------------------+
| |||
bool IncrementCounter(const string key, const string symbol = "", const string strategy = "")
| |||
{
| |||
if(!m_initialized && !Initialize()) return false;
| |||
| |||
string sql;
| |||
if(symbol != "" && strategy != "")
| |||
sql = StringFormat("INSERT INTO exploration_counters (key, count, symbol, strategy) VALUES ('%s', 1, '%s', '%s') "
| |||
"ON CONFLICT(key) DO UPDATE SET count = count + 1, last_updated = CURRENT_TIMESTAMP",
| |||
key, symbol, strategy);
| |||
else
| |||
sql = StringFormat("INSERT INTO exploration_counters (key, count) VALUES ('%s', 1) "
| |||
"ON CONFLICT(key) DO UPDATE SET count = count + 1, last_updated = CURRENT_TIMESTAMP",
| |||
key);
| |||
| |||
if(!DatabaseExecute(m_db, sql))
| |||
{
| |||
Print("[CSQLiteExplorationCounter] Increment failed: ", GetLastError());
| |||
return false;
| |||
}
| |||
return true;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Get counter value |
| |||
//+------------------------------------------------------------------+
| |||
int GetCounter(const string &key)
| |||
{
| |||
if(!m_initialized && !Initialize()) return 0;
| |||
| |||
string sql = StringFormat("SELECT count FROM exploration_counters WHERE key = '%s'", key);
| |||
int request = DatabasePrepare(m_db, sql);
| |||
| |||
if(request == INVALID_HANDLE) return 0;
| |||
| |||
int count = 0;
| |||
if(DatabaseRead(request))
| |||
DatabaseColumnInteger(request, 0, count);
| |||
| |||
DatabaseFinalize(request);
| |||
return count;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Get all counters for dashboard |
| |||
//+------------------------------------------------------------------+
| |||
void GetAllCounters(string &keys[], int &counts[], string &symbols[], string &strategies[])
| |||
{
| |||
ArrayResize(keys, 0);
| |||
ArrayResize(counts, 0);
| |||
ArrayResize(symbols, 0);
| |||
ArrayResize(strategies, 0);
| |||
| |||
if(!m_initialized && !Initialize()) return;
| |||
| |||
string sql = "SELECT key, count, symbol, strategy FROM exploration_counters ORDER BY count DESC";
| |||
int request = DatabasePrepare(m_db, sql);
| |||
| |||
if(request == INVALID_HANDLE) return;
| |||
| |||
int idx = 0;
| |||
while(DatabaseRead(request))
| |||
{
| |||
ArrayResize(keys, idx + 1);
| |||
ArrayResize(counts, idx + 1);
| |||
ArrayResize(symbols, idx + 1);
| |||
ArrayResize(strategies, idx + 1);
| |||
| |||
DatabaseColumnText(request, 0, keys[idx]);
| |||
DatabaseColumnInteger(request, 1, counts[idx]);
| |||
DatabaseColumnText(request, 2, symbols[idx]);
| |||
DatabaseColumnText(request, 3, strategies[idx]);
| |||
| |||
idx++;
| |||
}
| |||
| |||
DatabaseFinalize(request);
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Reset specific counter |
| |||
//+------------------------------------------------------------------+
| |||
bool ResetCounter(const string &key)
| |||
{
| |||
if(!m_initialized && !Initialize()) return false;
| |||
| |||
string sql = StringFormat("UPDATE exploration_counters SET count = 0 WHERE key = '%s'", key);
| |||
return DatabaseExecute(m_db, sql);
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Shutdown and cleanup |
| |||
//+------------------------------------------------------------------+
| |||
void Shutdown()
| |||
{
| |||
if(m_db != INVALID_HANDLE)
| |||
{
| |||
DatabaseClose(m_db);
| |||
m_db = INVALID_HANDLE;
| |||
m_initialized = false;
| |||
Print("[CSQLiteExplorationCounter] Shutdown complete");
| |||
}
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Check if initialized |
| |||
//+------------------------------------------------------------------+
| |||
bool IsInitialized() const { return m_initialized; }
| |||
};
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Global Singleton |
| |||
//+------------------------------------------------------------------+
| |||
CSQLiteExplorationCounter* g_sqliteCounter = NULL;
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Initialize Global Counter |
| |||
//+------------------------------------------------------------------+
| |||
bool InitializeSQLiteCounter()
| |||
{
| |||
if(g_sqliteCounter != NULL) return true;
| |||
| |||
g_sqliteCounter = new CSQLiteExplorationCounter();
| |||
return g_sqliteCounter.Initialize();
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Shutdown Global Counter |
| |||
//+------------------------------------------------------------------+
| |||
void ShutdownSQLiteCounter()
| |||
{
| |||
if(g_sqliteCounter != NULL)
| |||
{
| |||
g_sqliteCounter.Shutdown();
| |||
delete g_sqliteCounter;
| |||
g_sqliteCounter = NULL;
| |||
}
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Quick increment helper |
| |||
//+------------------------------------------------------------------+
| |||
bool IncrementExplore(const string key, const string symbol = "", const string strategy = "")
| |||
{
| |||
if(g_sqliteCounter == NULL)
| |||
{
| |||
if(!InitializeSQLiteCounter()) return false;
| |||
}
| |||
return g_sqliteCounter.IncrementCounter(key, symbol, strategy);
| |||
}
| |||
| |||
#endif // CSQLITEEXPLORATIONCOUNTER_MQH
|