SniperGold_ML/MQL5/Include/AlgoForge/AF_Engine1_MTFData.mqh

366 lines
13 KiB
MQL5
Raw Permalink Normal View History

//+------------------------------------------------------------------+
//| AF_Engine1_MTFData.mqh |
//| Project : Algo Forge (refactor total) |
//| Sumber inspirasi: SniperGold SMC Pro+ (c) Waseem Shahrukh |
//| https://www.mql5.com/en/code/75466 |
//+------------------------------------------------------------------+
//| Engine 1 - Pengumpul Data Bar MTF + Cache Index Reusable |
//| |
//| TUGAS: |
//| 1. Mengumpulkan bar MTF (OHLCV) dari beberapa timeframe |
//| sesuai kebutuhan agen N/C/E/P (Engine 2) dan display |
//| (Engine 3). |
//| 2. Cache internal berbasis INDEX BAR: CopyRates hanya dipanggil |
//| ketika Bars(_Symbol,tf) berubah. Ini pola yang sudah |
//| terbukti di SniperGold v4.5 (RefreshMTFCache/BuildMTFStruct)|
//| dan mencegah pembacaan History berulang (anti-freeze). |
//| 3. Independen dari timeframe chart: semua akses memakai symbol |
//| eksplisit + ENUM_TIMEFRAMES eksplisit. PERIOD_CURRENT |
//| DILARANG (ditolak saat Register). |
//| 4. Closed-bar lock: cache HANYA berisi bar yang sudah tertutup.|
//| Bar pembentuk (bar terbaru yang masih berjalan) dibuang |
//| saat Build, sehingga analisis tidak pernah repaint. |
//| 5. Reusable & aman: guard reentrancy + statistik panggilan |
//| History (untuk unit test dan audit). |
//| |
//| INDEKS: cache disimpan seri (series): bars[0] = bar tertutup |
//| TERBARU, bars[n-1] = bar tertua. Konsumen memakai |
//| idxFromRight: 0 = bar tertutup terakhir. |
//+------------------------------------------------------------------+
#ifndef AF_ENGINE1_MTFDATA_MQH
#define AF_ENGINE1_MTFDATA_MQH
#include "AF_Defines.mqh"
//------------------------------------------------------------------
// Struct bar ringkas (subset MqlRates - cukup untuk agen sinyal)
//------------------------------------------------------------------
struct AFBar
{
datetime time;
double open;
double high;
double low;
double close;
long tick_volume;
long real_volume;
int spread;
};
//------------------------------------------------------------------
// Engine 1 - MTF Bar Collector + Cache
//------------------------------------------------------------------
class AFEngine1MTF
{
private:
struct AFSlot
{
string symbol; // symbol eksplisit (default _Symbol)
ENUM_TIMEFRAMES tf; // timeframe eksplisit
int maxBars; // kapasitas cache
// --- state cache ---
long lastBars; // Bars(symbol,tf) saat terakhir dibangun (cache index)
datetime lastRefresh; // waktu refresh terakhir (untuk throttle retry)
int count; // jumlah bar TERTUTUP di cache
AFBar bars[]; // [0] = bar tertutup TERBARU
bool ready;
// --- statistik (unit test / audit) ---
int historyCalls; // jumlah CopyRates yang benar-benar dieksekusi
int refreshCount; // jumlah rebuild
};
AFSlot m_slots[]; // daftar slot terdaftar (dynamic array)
int m_nSlots;
bool m_inRefresh; // guard reentrancy
int m_find(ENUM_TIMEFRAMES tf,const string symbol) const;
void Build(int idx); // isi ulang cache slot (HANYA saat bar baru TF itu)
public:
AFEngine1MTF() { m_nSlots=0; m_inRefresh=false; }
// --- lifecycle ---
int Register(ENUM_TIMEFRAMES tf,int maxBars=AF_E1_DEFAULT_BARS,const string symbol="");
bool Refresh(); // panggil tiap OnCalculate/OnTick; skip otomatis bila tak ada bar baru
// --- read API (idxFromRight: 0 = bar tertutup terakhir) ---
bool IsReady(int slot) const;
int Count(int slot) const;
bool GetBar(int slot,int idxFromRight,AFBar &out) const;
bool GetBarByTime(int slot,datetime t,AFBar &out) const;
int FindBarIndex(int slot,datetime t) const; // -1 bila tidak ada
double Open (int slot,int idxFromRight) const;
double High (int slot,int idxFromRight) const;
double Low (int slot,int idxFromRight) const;
double Close(int slot,int idxFromRight) const;
datetime Time (int slot,int idxFromRight) const;
long TickVolume(int slot,int idxFromRight) const;
// --- diagnostik / statistik ---
int HistoryCalls(int slot) const;
int RefreshCount(int slot) const;
int TotalHistoryCalls() const;
int SlotCount() const { return m_nSlots; }
long LastBars(int slot) const;
ENUM_TIMEFRAMES TfOf(int slot) const;
string SymbolOf(int slot) const;
// --- utilitas ---
static int PeriodSecondsOf(ENUM_TIMEFRAMES tf); // detik per TF (tanpa PERIOD_CURRENT)
static bool IsBarClosed(datetime barTime,ENUM_TIMEFRAMES tf,datetime now);
double ATR(int slot,int period=14) const; // ATR sederhana dari cache (bar tertutup)
};
//+------------------------------------------------------------------+
//| Implementasi |
//+------------------------------------------------------------------+
int AFEngine1MTF::m_find(ENUM_TIMEFRAMES tf,const string symbol) const
{
for(int i=0;i<m_nSlots;i++)
if(m_slots[i].tf==tf && m_slots[i].symbol==symbol)
return i;
return -1;
}
int AFEngine1MTF::Register(ENUM_TIMEFRAMES tf,int maxBars,const string symbol)
{
if(tf==PERIOD_CURRENT) return AF_E1_ERR_SLOT; // TF eksplisit WAJIB
if(maxBars<=0) maxBars=AF_E1_DEFAULT_BARS;
if(maxBars>AF_E1_MAX_BARS) maxBars=AF_E1_MAX_BARS;
string sym=(symbol=="" ? _Symbol : symbol);
int existing=m_find(tf,sym);
if(existing>=0) return existing; // idempoten
if(m_nSlots>=AF_E1_MAX_SLOTS) return AF_E1_ERR_SLOT; // slot penuh
int idx=m_nSlots;
ArrayResize(m_slots,idx+1);
m_slots[idx].symbol=sym;
m_slots[idx].tf=tf;
m_slots[idx].maxBars=maxBars;
m_slots[idx].lastBars=-1;
m_slots[idx].lastRefresh=0;
m_slots[idx].count=0;
m_slots[idx].ready=false;
m_slots[idx].historyCalls=0;
m_slots[idx].refreshCount=0;
ArrayResize(m_slots[idx].bars,maxBars);
m_nSlots++;
return idx;
}
bool AFEngine1MTF::Refresh()
{
if(m_inRefresh) return false; // guard reentrancy
m_inRefresh=true;
bool any=false;
datetime now=TimeCurrent();
for(int i=0;i<m_nSlots;i++)
{
long barsNow=Bars(m_slots[i].symbol,m_slots[i].tf);
if(barsNow<1) continue; // history TF belum siap -> coba lagi nanti
if(m_slots[i].ready && barsNow==m_slots[i].lastBars) continue; // TIDAK ada bar baru -> TANPA baca History
if(!m_slots[i].ready && (now-m_slots[i].lastRefresh)<AF_E1_RETRY_SEC) continue; // throttle retry
m_slots[i].historyCalls++;
Build(i);
if(m_slots[i].ready) m_slots[i].lastBars=barsNow;
m_slots[i].lastRefresh=now;
any=true;
}
m_inRefresh=false;
return any;
}
void AFEngine1MTF::Build(int idx)
{
m_slots[idx].count=0;
m_slots[idx].ready=false;
MqlRates rates[];
ArraySetAsSeries(rates,true); // [0] = bar terbaru
// Closed-bar lock: ambil maxBars+1 bar, lalu BUANG bar pembentuk
// (bar yang belum tertutup) bila ada. Dengan ini SEMUA bar di
// cache dijamin tertutup -> non-repaint.
int got=CopyRates(m_slots[idx].symbol,m_slots[idx].tf,0,m_slots[idx].maxBars+1,rates);
if(got<=0) return;
int skip=0;
if(!IsBarClosed(rates[0].time,m_slots[idx].tf,TimeCurrent())) skip=1;
int n=MathMin(got-skip,m_slots[idx].maxBars);
if(n<=0) return;
for(int i=0;i<n;i++)
{
m_slots[idx].bars[i].time = rates[i+skip].time;
m_slots[idx].bars[i].open = rates[i+skip].open;
m_slots[idx].bars[i].high = rates[i+skip].high;
m_slots[idx].bars[i].low = rates[i+skip].low;
m_slots[idx].bars[i].close = rates[i+skip].close;
m_slots[idx].bars[i].tick_volume = rates[i+skip].tick_volume;
m_slots[idx].bars[i].real_volume = rates[i+skip].real_volume;
m_slots[idx].bars[i].spread = rates[i+skip].spread;
}
m_slots[idx].count=n;
m_slots[idx].ready=true; m_slots[idx].refreshCount++;
}
//--- read API ---
bool AFEngine1MTF::IsReady(int slot) const
{
return (slot>=0 && slot<m_nSlots && m_slots[slot].ready);
}
int AFEngine1MTF::Count(int slot) const
{
return (slot>=0 && slot<m_nSlots) ? m_slots[slot].count : 0;
}
bool AFEngine1MTF::GetBar(int slot,int idxFromRight,AFBar &out) const
{
if(slot<0 || slot>=m_nSlots) return false;
if(!m_slots[slot].ready) return false;
if(idxFromRight<0 || idxFromRight>=m_slots[slot].count) return false;
out=m_slots[slot].bars[idxFromRight];
return true;
}
int AFEngine1MTF::FindBarIndex(int slot,datetime t) const
{
if(slot<0 || slot>=m_nSlots) return -1;
if(!m_slots[slot].ready) return -1;
for(int i=0;i<m_slots[slot].count;i++)
if(m_slots[slot].bars[i].time==t) return i;
return -1;
}
bool AFEngine1MTF::GetBarByTime(int slot,datetime t,AFBar &out) const
{
int idx=FindBarIndex(slot,t);
if(idx<0) return false;
return GetBar(slot,idx,out);
}
double AFEngine1MTF::Open(int slot,int idxFromRight) const
{
AFBar b;
return GetBar(slot,idxFromRight,b) ? b.open : 0.0;
}
double AFEngine1MTF::High(int slot,int idxFromRight) const
{
AFBar b;
return GetBar(slot,idxFromRight,b) ? b.high : 0.0;
}
double AFEngine1MTF::Low(int slot,int idxFromRight) const
{
AFBar b;
return GetBar(slot,idxFromRight,b) ? b.low : 0.0;
}
double AFEngine1MTF::Close(int slot,int idxFromRight) const
{
AFBar b;
return GetBar(slot,idxFromRight,b) ? b.close : 0.0;
}
datetime AFEngine1MTF::Time(int slot,int idxFromRight) const
{
AFBar b;
return GetBar(slot,idxFromRight,b) ? b.time : (datetime)0;
}
long AFEngine1MTF::TickVolume(int slot,int idxFromRight) const
{
AFBar b;
return GetBar(slot,idxFromRight,b) ? b.tick_volume : 0;
}
//--- diagnostik ---
int AFEngine1MTF::HistoryCalls(int slot) const
{
return (slot>=0 && slot<m_nSlots) ? m_slots[slot].historyCalls : 0;
}
int AFEngine1MTF::RefreshCount(int slot) const
{
return (slot>=0 && slot<m_nSlots) ? m_slots[slot].refreshCount : 0;
}
int AFEngine1MTF::TotalHistoryCalls() const
{
int total=0;
for(int i=0;i<m_nSlots;i++) total+=m_slots[i].historyCalls;
return total;
}
long AFEngine1MTF::LastBars(int slot) const
{
return (slot>=0 && slot<m_nSlots) ? m_slots[slot].lastBars : -1;
}
ENUM_TIMEFRAMES AFEngine1MTF::TfOf(int slot) const
{
return (slot>=0 && slot<m_nSlots) ? m_slots[slot].tf : PERIOD_CURRENT;
}
string AFEngine1MTF::SymbolOf(int slot) const
{
return (slot>=0 && slot<m_nSlots) ? m_slots[slot].symbol : "";
}
//--- utilitas ---
int AFEngine1MTF::PeriodSecondsOf(ENUM_TIMEFRAMES tf)
{
switch(tf)
{
case PERIOD_M1: return 60;
case PERIOD_M2: return 120;
case PERIOD_M3: return 180;
case PERIOD_M4: return 240;
case PERIOD_M5: return 300;
case PERIOD_M6: return 360;
case PERIOD_M10: return 600;
case PERIOD_M12: return 720;
case PERIOD_M15: return 900;
case PERIOD_M20: return 1200;
case PERIOD_M30: return 1800;
case PERIOD_H1: return 3600;
case PERIOD_H2: return 7200;
case PERIOD_H3: return 10800;
case PERIOD_H4: return 14400;
case PERIOD_H6: return 21600;
case PERIOD_H8: return 28800;
case PERIOD_H12: return 43200;
case PERIOD_D1: return 86400;
case PERIOD_W1: return 604800;
case PERIOD_MN1: return 2592000;
default: return 0; // PERIOD_CURRENT dkk -> invalid (0)
}
}
bool AFEngine1MTF::IsBarClosed(datetime barTime,ENUM_TIMEFRAMES tf,datetime now)
{
int sec=PeriodSecondsOf(tf);
if(sec<=0) return false;
return (barTime + (datetime)sec) <= now;
}
double AFEngine1MTF::ATR(int slot,int period) const
{
if(slot<0 || slot>=m_nSlots) return 0.0;
if(!m_slots[slot].ready || period<1) return 0.0;
int n=MathMin(period,m_slots[slot].count);
if(n==0) return 0.0;
double sum=0.0;
for(int i=0;i<n;i++)
{
double tr=m_slots[slot].bars[i].high-m_slots[slot].bars[i].low;
int j=i+1;
if(j<m_slots[slot].count)
{
double pc=m_slots[slot].bars[j].close;
double t1=MathAbs(m_slots[slot].bars[i].high-pc);
double t2=MathAbs(m_slots[slot].bars[i].low-pc);
tr=MathMax(tr,MathMax(t1,t2));
}
sum+=tr;
}
return sum/(double)n;
}
#endif // AF_ENGINE1_MTFDATA_MQH