//+------------------------------------------------------------------+ //| 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;iAF_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 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) 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=0 && 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=0 && slot=0 && slot=0 && slot=0 && slot=0 && slot 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