//+------------------------------------------------------------------+ //| Piptology_AutoTrader.mq5 | //| Piptology - The Science of Market Precision | //| Auto-Execution Bot for MetaTrader 5 | //| Mendukung Single Chart & Multi-Pair Portfolio Scanner | //+------------------------------------------------------------------+ #property copyright "Piptology - The Science of Market Precision" #property link "https://piptology.sakti.asia" #property version "2.40" #property description "Bot Eksekusi Otomatis Sinyal SMC Piptology untuk MetaTrader 5" #property description "Mendukung Multi-Pair Portfolio Scanner & Smart Runner Tiered Profit Locking" #include enum ENUM_SCAN_MODE { SCAN_CURRENT_CHART_ONLY, // Mode 1: Hanya Chart Ini Saja (Single Pair) SCAN_ALL_POTENTIAL_PAIRS // Mode 2: Multi-Pair Scanner (1 Master Chart Pantau Semua) }; //--- Input Parameters input group "=== PILIHAN MODE PEMINDAIAN ===" input ENUM_SCAN_MODE InpScanMode = SCAN_ALL_POTENTIAL_PAIRS; // Mode Pemindaian input string InpMonitoredPairs = "XAU_USD,EUR_USD,GBP_USD,USD_JPY,BTC_USD"; // Daftar Pair (Pisahkan Koma) input string InpTimeframe = "M15"; // Timeframe Sinyal (M5, M15, M30, H1) input int InpMaxOpenTrades = 6; // Batas Maksimal Posisi Terbuka Sekaligus (Mendukung 5+ Pair) input group "=== KONEKSI SERVER PIPTOLOGY ===" input string InpServerURL = "https://piptology.sakti.asia"; // URL Web Piptology input int InpPollIntervalSec = 3; // Polling Interval (Detik) input group "=== MANAJEMEN LOT & EKSEKUSI ===" input double InpLotSize = 0.01; // Lot Size per Order input bool InpSplitPositions = true; // Split Posisi (Order 1 ke TP1, Order 2 ke TP2) input bool InpAutoBreakeven = true; // Kunci Profit Berjenjang (Tiered Profit Locking) input int InpMaxSlippagePips = 25; // Toleransi Slippage Maksimal (Pips) input int InpMaxSpreadPips = 40; // Batas Spread Maksimal (Pips) input ulong InpMagicNumber = 777999; // Magic Number Bot input group "=== TIERED PROFIT LOCKING (SMART RUNNER) ===" // --- Parameter Forex (Satuan: Pips) --- input double InpForexTier1Trigger = 20.0; // Forex: Target Pips Tier 1 (+20 pips) input double InpForexTier1LockTP1 = 10.0; // Forex: Kunci Pips TP1 (+10 pips) input double InpForexTier1LockRunner= 2.0; // Forex: Kunci Pips Runner BEP (+2 pips) input double InpForexTier2Trigger = 50.0; // Forex: Target Pips Tier 2 (+50 pips) input double InpForexTier2Lock = 30.0; // Forex: Kunci Pips Tier 2 (+30 pips) input double InpForexTier3Trigger = 80.0; // Forex: Target Pips Tier 3 Runner (+80 pips) input double InpForexTier3Lock = 50.0; // Forex: Kunci Pips Tier 3 Runner (+50 pips) // --- Parameter Emas / Gold (Satuan: Dollar $) --- input double InpGoldTier1Trigger = 3.50; // Gold: Target $ Tier 1 (+$3.50) input double InpGoldTier1LockTP1 = 1.50; // Gold: Kunci $ TP1 (+$1.50) input double InpGoldTier1LockRunner = 0.50; // Gold: Kunci $ Runner BEP (+$0.50) input double InpGoldTier2Trigger = 8.00; // Gold: Target $ Tier 2 (+$8.00) input double InpGoldTier2Lock = 4.00; // Gold: Kunci $ Tier 2 (+$4.00) input double InpGoldTier3Trigger = 14.00; // Gold: Target $ Tier 3 Runner (+$14.00) input double InpGoldTier3Lock = 8.00; // Gold: Kunci $ Tier 3 Runner (+$8.00) // --- Parameter Bitcoin / BTC (Satuan: Dollar $) --- input double InpBtcTier1Trigger = 200.0; // BTC: Target $ Tier 1 (+$200) input double InpBtcTier1LockTP1 = 100.0; // BTC: Kunci $ TP1 (+$100) input double InpBtcTier1LockRunner = 25.0; // BTC: Kunci $ Runner BEP (+$25) input double InpBtcTier2Trigger = 500.0; // BTC: Target $ Tier 2 (+$500) input double InpBtcTier2Lock = 300.0; // BTC: Kunci $ Tier 2 (+$300) input double InpBtcTier3Trigger = 900.0; // BTC: Target $ Tier 3 Runner (+$900) input double InpBtcTier3Lock = 600.0; // BTC: Kunci $ Tier 3 Runner (+$600) input group "=== LIVE CHART STREAM KE WEB ===" input bool InpEnableChartStream = true; // Stream Layar Chart ke Web Dashboard input int InpChartStreamInterval = 5; // Interval Refresh Layar (Detik) input int InpChartScreenshotWidth = 1920; // Lebar Gambar Layar (Pixel Full HD) input int InpChartScreenshotHeight = 1080; // Tinggi Gambar Layar (Pixel Full HD) input int InpChartZoomScale = 2; // Skala Zoom Candlestick (0: Terjauh s/d 5: Terdekat, -1: Manual) //--- Global Variables CTrade m_trade; string g_last_executed_ids[]; int g_last_id_count = 0; string g_bot_status = "Menunggu sinyal..."; datetime g_last_chart_upload = 0; //+------------------------------------------------------------------+ //| Cek apakah ID sinyal sudah pernah dieksekusi | //+------------------------------------------------------------------+ bool IsSignalExecuted(string sig_id) { for(int i = 0; i < g_last_id_count; i++) { if(g_last_executed_ids[i] == sig_id) return true; } return false; } void RecordExecutedSignal(string sig_id) { ArrayResize(g_last_executed_ids, g_last_id_count + 1); g_last_executed_ids[g_last_id_count] = sig_id; g_last_id_count++; } //+------------------------------------------------------------------+ //| Helper: Ekstrak string dari JSON | //+------------------------------------------------------------------+ string ExtractJsonString(string json, string key) { string search = "\"" + key + "\":\""; int start = StringFind(json, search); if(start < 0) return ""; start += StringLen(search); int end = StringFind(json, "\"", start); if(end < 0) return ""; return StringSubstr(json, start, end - start); } double ExtractJsonDouble(string json, string key) { string search = "\"" + key + "\":"; int start = StringFind(json, search); if(start < 0) return 0.0; start += StringLen(search); int end1 = StringFind(json, ",", start); int end2 = StringFind(json, "}", start); int end = end1; if(end < 0 || (end2 >= 0 && end2 < end1)) end = end2; if(end < 0) end = StringLen(json); string val_str = StringSubstr(json, start, end - start); StringTrimLeft(val_str); StringTrimRight(val_str); return StringToDouble(val_str); } bool ExtractJsonBool(string json, string key) { string search = "\"" + key + "\":"; int start = StringFind(json, search); if(start < 0) return false; start += StringLen(search); string sub = StringSubstr(json, start, 5); return (StringFind(sub, "true") >= 0); } //+------------------------------------------------------------------+ //| Dapatkan Suffix Broker dari Chart Aktif (e.g. "c", "m", ".raw") | //+------------------------------------------------------------------+ string GetCurrentChartSuffix() { string sym = _Symbol; if(StringFind(sym, "XAUUSD") == 0 && StringLen(sym) > 6) return StringSubstr(sym, 6); if(StringFind(sym, "GOLD") == 0 && StringLen(sym) > 4) return StringSubstr(sym, 4); if(StringFind(sym, "EURUSD") == 0 && StringLen(sym) > 6) return StringSubstr(sym, 6); if(StringFind(sym, "GBPUSD") == 0 && StringLen(sym) > 6) return StringSubstr(sym, 6); if(StringFind(sym, "USDJPY") == 0 && StringLen(sym) > 6) return StringSubstr(sym, 6); if(StringFind(sym, "BTCUSD") == 0 && StringLen(sym) > 6) return StringSubstr(sym, 6); if(StringLen(sym) > 6) return StringSubstr(sym, 6); return ""; } //+------------------------------------------------------------------+ //| Cari simbol broker yang sesuai (e.g. XAUUSDc, EURUSDc, dll) | //+------------------------------------------------------------------+ string MatchBrokerSymbol(string standard_name) { string base = standard_name; StringReplace(base, "_", ""); // 1. Coba dengan suffix chart saat ini (misal chart XAUUSDc -> suffix "c") string chart_suf = GetCurrentChartSuffix(); if(chart_suf != "") { string candidate = ""; if(StringFind(base, "XAU") >= 0) candidate = "XAUUSD" + chart_suf; else candidate = base + chart_suf; bool is_custom = false; if(SymbolExist(candidate, is_custom)) { SymbolSelect(candidate, true); return candidate; } if(StringFind(base, "XAU") >= 0) { string gold_cand = "GOLD" + chart_suf; if(SymbolExist(gold_cand, is_custom)) { SymbolSelect(gold_cand, true); return gold_cand; } } } // 2. Cek apakah base ada langsung di broker (misal EURUSD) bool is_custom_direct = false; if(SymbolExist(base, is_custom_direct)) { SymbolSelect(base, true); return base; } // 3. Cek variasi suffix broker umum string suffixes[] = {"c", "m", ".a", ".raw", ".pro", "#", "_i", ".s", "micro", "mini"}; if(StringFind(base, "XAU") >= 0) { string gold_names[] = {"XAUUSD", "GOLD"}; for(int g = 0; g < ArraySize(gold_names); g++) { for(int s = 0; s < ArraySize(suffixes); s++) { string cand = gold_names[g] + suffixes[s]; bool is_cust = false; if(SymbolExist(cand, is_cust)) { SymbolSelect(cand, true); return cand; } } } } else { for(int s = 0; s < ArraySize(suffixes); s++) { string cand = base + suffixes[s]; bool is_cust = false; if(SymbolExist(cand, is_cust)) { SymbolSelect(cand, true); return cand; } } } // 4. Scan seluruh database simbol broker di server int total = SymbolsTotal(false); for(int i = 0; i < total; i++) { string sname = SymbolName(i, false); if(StringFind(sname, base) >= 0) { SymbolSelect(sname, true); return sname; } if(StringFind(base, "XAU") >= 0 && (StringFind(sname, "XAUUSD") >= 0 || StringFind(sname, "GOLD") >= 0)) { SymbolSelect(sname, true); return sname; } } SymbolSelect(base, true); return base; } //+------------------------------------------------------------------+ //| Kirim Log Eksekusi ke Halaman Investor Web Piptology | //+------------------------------------------------------------------+ void SendExecutionLog(string msg, string log_type="EXEC") { string json = "{\"msg\":\"" + msg + "\",\"type\":\"" + log_type + "\"}"; char post_data[]; StringToCharArray(json, post_data, 0, WHOLE_ARRAY, CP_UTF8); if(ArraySize(post_data) > 0) ArrayResize(post_data, ArraySize(post_data) - 1); char result_data[]; string result_headers; string headers = "Content-Type: application/json\r\nUser-Agent: PiptologyMT5Client/2.30\r\n"; WebRequest("POST", InpServerURL + "/api/mt5/investor-view/log", headers, 2000, post_data, result_data, result_headers); } //+------------------------------------------------------------------+ //| Kirim Telemetri Lengkap ke Halaman Investor (Investor Heartbeat) | //+------------------------------------------------------------------+ void SendInvestorHeartbeat() { int open_cnt = 0; double total_pnl = 0.0; string pos_json = "["; for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(ticket > 0 && PositionGetInteger(POSITION_MAGIC) == InpMagicNumber) { string sym = PositionGetString(POSITION_SYMBOL); long type = PositionGetInteger(POSITION_TYPE); double vol = PositionGetDouble(POSITION_VOLUME); double op = PositionGetDouble(POSITION_PRICE_OPEN); double cp = PositionGetDouble(POSITION_PRICE_CURRENT); double sl = PositionGetDouble(POSITION_SL); double tp = PositionGetDouble(POSITION_TP); double pnl = PositionGetDouble(POSITION_PROFIT); string comm = PositionGetString(POSITION_COMMENT); bool is_bep = (type == POSITION_TYPE_BUY) ? (sl >= op - 0.05) : (sl <= op + 0.05 && sl > 0); if(open_cnt > 0) pos_json += ","; pos_json += "{\"ticket\":" + IntegerToString(ticket) + ",\"symbol\":\"" + sym + "\"" + ",\"type\":\"" + (type == POSITION_TYPE_BUY ? "BUY" : "SELL") + "\"" + ",\"lots\":" + DoubleToString(vol, 2) + ",\"open_price\":" + DoubleToString(op, 5) + ",\"current_price\":" + DoubleToString(cp, 5) + ",\"sl\":" + DoubleToString(sl, 5) + ",\"tp\":" + DoubleToString(tp, 5) + ",\"profit\":" + DoubleToString(pnl, 2) + ",\"comment\":\"" + comm + "\"" + ",\"auto_bep\":" + (is_bep ? "true" : "false") + ",\"status\":\"" + (is_bep ? "Auto-BE Locked (0% Risk)" : "Running to Target") + "\"}"; open_cnt++; total_pnl += pnl; } } pos_json += "]"; string payload = "{" + "\"account_login\":" + IntegerToString(AccountInfoInteger(ACCOUNT_LOGIN)) + "," + "\"broker\":\"" + AccountInfoString(ACCOUNT_COMPANY) + "\"," + "\"server\":\"" + AccountInfoString(ACCOUNT_SERVER) + "\"," + "\"currency\":\"" + AccountInfoString(ACCOUNT_CURRENCY) + "\"," + "\"leverage\":" + IntegerToString(AccountInfoInteger(ACCOUNT_LEVERAGE)) + "," + "\"balance\":" + DoubleToString(AccountInfoDouble(ACCOUNT_BALANCE), 2) + "," + "\"equity\":" + DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY), 2) + "," + "\"floating_profit\":" + DoubleToString(total_pnl, 2) + "," + "\"margin\":" + DoubleToString(AccountInfoDouble(ACCOUNT_MARGIN), 2) + "," + "\"free_margin\":" + DoubleToString(AccountInfoDouble(ACCOUNT_MARGIN_FREE), 2) + "," + "\"margin_level\":" + DoubleToString(AccountInfoDouble(ACCOUNT_MARGIN_LEVEL), 2) + "," + "\"bot_mode\":\"" + (InpScanMode == SCAN_ALL_POTENTIAL_PAIRS ? "MULTI-PAIR PORTFOLIO SCANNER" : "SINGLE CHART") + "\"," + "\"monitored_pairs\":\"" + InpMonitoredPairs + "\"," + "\"timeframe\":\"" + InpTimeframe + "\"," + "\"magic_number\":" + IntegerToString(InpMagicNumber) + "," + "\"auto_breakeven\":" + (InpAutoBreakeven ? "true" : "false") + "," + "\"open_positions\":" + pos_json + "}"; char post_data[]; StringToCharArray(payload, post_data, 0, WHOLE_ARRAY, CP_UTF8); if(ArraySize(post_data) > 0) ArrayResize(post_data, ArraySize(post_data) - 1); char result_data[]; string result_headers; string headers = "Content-Type: application/json\r\nUser-Agent: PiptologyMT5Client/2.30\r\n"; WebRequest("POST", InpServerURL + "/api/mt5/heartbeat", headers, 3000, post_data, result_data, result_headers); } //+------------------------------------------------------------------+ //| Stream Tangkapan Layar Chart Asli ke Web Dashboard Investor | //+------------------------------------------------------------------+ void UploadChartScreenshot() { if(!InpEnableChartStream) return; datetime now = TimeCurrent(); if(now - g_last_chart_upload < InpChartStreamInterval) return; g_last_chart_upload = now; // Optimalkan skala zoom chart agar tidak terlalu rapat/zoom in if(InpChartZoomScale >= 0 && InpChartZoomScale <= 5) { ChartSetInteger(0, CHART_SCALE, InpChartZoomScale); ChartSetInteger(0, CHART_SHIFT, true); ChartSetDouble(0, CHART_SHIFT_SIZE, 12.0); } string filename = "piptology_chart.png"; ResetLastError(); int scr_w = InpChartScreenshotWidth > 0 ? InpChartScreenshotWidth : 1920; int scr_h = InpChartScreenshotHeight > 0 ? InpChartScreenshotHeight : 1080; // Tangkap layar chart aktif resolusi Full HD if(!ChartScreenShot(0, filename, scr_w, scr_h, ALIGN_RIGHT)) return; int handle = FileOpen(filename, FILE_READ | FILE_BIN); if(handle == INVALID_HANDLE) return; int size = (int)FileSize(handle); if(size <= 0) { FileClose(handle); return; } char data[]; ArrayResize(data, size); uint read_bytes = FileReadArray(handle, data, 0, size); FileClose(handle); if(read_bytes <= 0) return; char result[]; string result_headers; string headers = "Content-Type: image/png\r\nUser-Agent: PiptologyMT5Client/2.30\r\n"; ResetLastError(); WebRequest("POST", InpServerURL + "/api/mt5/chart-upload", headers, 5000, data, result, result_headers); } //+------------------------------------------------------------------+ //| Update On-Chart Dashboard | //+------------------------------------------------------------------+ void UpdateChartDashboard() { int open_pos_count = 0; double total_pnl = 0.0; for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(ticket > 0 && PositionGetInteger(POSITION_MAGIC) == InpMagicNumber) { open_pos_count++; total_pnl += PositionGetDouble(POSITION_PROFIT); } } string mode_str = (InpScanMode == SCAN_ALL_POTENTIAL_PAIRS) ? "MULTI-PAIR PORTFOLIO (1 Master Chart)" : "SINGLE CHART (" + _Symbol + ")"; string text = "\n"; text += "======================================================\n"; text += " ⚡ PIPTOLOGY AUTO-TRADER BOT MT5 v2.40\n"; text += " The Science of Market Precision\n"; text += "======================================================\n"; text += " Mode Pemindaian : " + mode_str + "\n"; text += " Broker : " + AccountInfoString(ACCOUNT_COMPANY) + " (" + IntegerToString(AccountInfoInteger(ACCOUNT_LOGIN)) + ")\n"; text += " Pair Dipantau : " + InpMonitoredPairs + "\n"; text += " Timeframe : " + InpTimeframe + "\n"; text += " Lot per Order : " + DoubleToString(InpLotSize, 2) + " (Split TP1/TP2: " + (InpSplitPositions ? "YA" : "TIDAK") + ")\n"; text += " Trailing Model : " + (InpAutoBreakeven ? "AKTIF (Smart Runner Tiered Locking)" : "NON-AKTIF") + "\n"; text += " Posisi Berjalan : " + IntegerToString(open_pos_count) + " / Max " + IntegerToString(InpMaxOpenTrades) + " (Floating PnL: $" + DoubleToString(total_pnl, 2) + ")\n"; text += " Status Bot : " + g_bot_status + "\n"; text += "======================================================\n"; Comment(text); } //+------------------------------------------------------------------+ //| Daftarkan Seluruh Pair Potensial ke Market Watch agar Siap Order | //+------------------------------------------------------------------+ void PreloadMonitoredSymbols() { string pairs[]; int count = StringSplit(InpMonitoredPairs, ',', pairs); for(int i = 0; i < count; i++) { string p = pairs[i]; StringTrimLeft(p); StringTrimRight(p); if(p != "") { string matched = MatchBrokerSymbol(p); SymbolSelect(matched, true); double bid = SymbolInfoDouble(matched, SYMBOL_BID); Print("✅ [Multi-Pair Setup] Pair: ", p, " -> Simbol Broker: ", matched, " (Bid: ", bid, ") [Market Watch Ready]"); } } } //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { m_trade.SetExpertMagicNumber(InpMagicNumber); m_trade.SetMarginMode(); Print("🚀 [Piptology AutoTrader v2.30] Diinisialisasi! Mode: ", (InpScanMode == SCAN_ALL_POTENTIAL_PAIRS ? "MULTI-PAIR SCANNER (1 Chart Pantau Semua)" : "SINGLE CHART (" + _Symbol + ")")); Print("â„šī¸ [Piptology AutoTrader] URL Server: ", InpServerURL); // Atur skala zoom chart jika diatur if(InpChartZoomScale >= 0 && InpChartZoomScale <= 5) { ChartSetInteger(0, CHART_SCALE, InpChartZoomScale); ChartSetInteger(0, CHART_SHIFT, true); ChartSetDouble(0, CHART_SHIFT_SIZE, 12.0); } // Daftarkan seluruh pair ke Market Watch seketika agar siap eksekusi if(InpScanMode == SCAN_ALL_POTENTIAL_PAIRS) PreloadMonitoredSymbols(); EventSetTimer(InpPollIntervalSec); UpdateChartDashboard(); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { EventKillTimer(); Comment(""); Print("🛑 [Piptology AutoTrader] Dihentikan."); } //+------------------------------------------------------------------+ //| Expert tick & timer function | //+------------------------------------------------------------------+ void OnTick() { if(InpAutoBreakeven) CheckAndApplyAutoBreakeven(); } void OnTimer() { if(InpAutoBreakeven) CheckAndApplyAutoBreakeven(); if(InpScanMode == SCAN_ALL_POTENTIAL_PAIRS) PollMultiPairSignals(); else PollSingleChartSignal(); SendInvestorHeartbeat(); UploadChartScreenshot(); UpdateChartDashboard(); } //+------------------------------------------------------------------+ //| Cek & Terapkan Trailing Profit Berjenjang (Smart Runner Locking) | //+------------------------------------------------------------------+ void CheckAndApplyAutoBreakeven() { for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(ticket <= 0 || PositionGetInteger(POSITION_MAGIC) != InpMagicNumber) continue; string pos_sym = PositionGetString(POSITION_SYMBOL); string comment = PositionGetString(POSITION_COMMENT); double open_price = PositionGetDouble(POSITION_PRICE_OPEN); double current_sl = PositionGetDouble(POSITION_SL); double current_tp = PositionGetDouble(POSITION_TP); ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); double current_bid = SymbolInfoDouble(pos_sym, SYMBOL_BID); double current_ask = SymbolInfoDouble(pos_sym, SYMBOL_ASK); double point = SymbolInfoDouble(pos_sym, SYMBOL_POINT); int digits = (int)SymbolInfoInteger(pos_sym, SYMBOL_DIGITS); bool is_gold = (StringFind(pos_sym, "XAU") >= 0 || StringFind(pos_sym, "GOLD") >= 0); bool is_btc = (StringFind(pos_sym, "BTC") >= 0); bool is_runner = (StringFind(comment, "TP2") >= 0 || StringFind(comment, "Runner") >= 0); // Hitung selisih keuntungan harga (Profit Distance) double profit_dist = 0.0; if(pos_type == POSITION_TYPE_BUY) profit_dist = current_bid - open_price; else if(pos_type == POSITION_TYPE_SELL) profit_dist = open_price - current_ask; if(profit_dist <= 0.0) continue; // Posisi belum profit atau masih minus -> SL DIAM di SL awal SMC! double target_offset = 0.0; string tier_label = ""; // 1. INSTRUMEN EMAS (XAUUSD / GOLD) - Acuan Nilai Dollar ($) if(is_gold) { if(is_runner) { if(profit_dist >= InpGoldTier3Trigger) { target_offset = InpGoldTier3Lock; tier_label = "Gold Tier 3 (+$8.00)"; } else if(profit_dist >= InpGoldTier2Trigger) { target_offset = InpGoldTier2Lock; tier_label = "Gold Tier 2 (+$4.00)"; } else if(profit_dist >= InpGoldTier1Trigger) { target_offset = InpGoldTier1LockRunner; tier_label = "Gold Tier 1 BEP (+$0.50)"; } } else { // Posisi Order 1 (TP1) if(profit_dist >= InpGoldTier2Trigger) { target_offset = InpGoldTier2Lock; tier_label = "Gold Tier 2 (+$4.00)"; } else if(profit_dist >= InpGoldTier1Trigger) { target_offset = InpGoldTier1LockTP1; tier_label = "Gold TP1 Lock (+$1.50)"; } } } // 2. INSTRUMEN BITCOIN (BTCUSD) - Acuan Nilai Dollar ($) else if(is_btc) { if(is_runner) { if(profit_dist >= InpBtcTier3Trigger) { target_offset = InpBtcTier3Lock; tier_label = "BTC Tier 3 (+$600)"; } else if(profit_dist >= InpBtcTier2Trigger) { target_offset = InpBtcTier2Lock; tier_label = "BTC Tier 2 (+$300)"; } else if(profit_dist >= InpBtcTier1Trigger) { target_offset = InpBtcTier1LockRunner; tier_label = "BTC Tier 1 BEP (+$25)"; } } else { // Posisi Order 1 (TP1) if(profit_dist >= InpBtcTier2Trigger) { target_offset = InpBtcTier2Lock; tier_label = "BTC Tier 2 (+$300)"; } else if(profit_dist >= InpBtcTier1Trigger) { target_offset = InpBtcTier1LockTP1; tier_label = "BTC TP1 Lock (+$100)"; } } } // 3. INSTRUMEN FOREX (EURUSD, GBPUSD, USDJPY, dll.) - Acuan Pips else { double pip_size = (digits == 3 || digits == 5) ? 10.0 * point : point; double profit_pips = profit_dist / pip_size; if(is_runner) { if(profit_pips >= InpForexTier3Trigger) { target_offset = InpForexTier3Lock * pip_size; tier_label = "Forex Tier 3 (+50p)"; } else if(profit_pips >= InpForexTier2Trigger) { target_offset = InpForexTier2Lock * pip_size; tier_label = "Forex Tier 2 (+30p)"; } else if(profit_pips >= InpForexTier1Trigger) { target_offset = InpForexTier1LockRunner * pip_size; tier_label = "Forex Tier 1 BEP (+2p)"; } } else { // Posisi Order 1 (TP1) if(profit_pips >= InpForexTier2Trigger) { target_offset = InpForexTier2Lock * pip_size; tier_label = "Forex Tier 2 (+30p)"; } else if(profit_pips >= InpForexTier1Trigger) { target_offset = InpForexTier1LockTP1 * pip_size; tier_label = "Forex TP1 Lock (+10p)"; } } } if(target_offset <= 0.0) continue; // Belum memenuhi syarat tier manapun // Hitung Calon Stop Loss Baru double candidate_sl = 0.0; if(pos_type == POSITION_TYPE_BUY) candidate_sl = NormalizeDouble(open_price + target_offset, digits); else if(pos_type == POSITION_TYPE_SELL) candidate_sl = NormalizeDouble(open_price - target_offset, digits); // KUNCI: SL HANYA BOLEH MAJU MENGUNCI PROFIT, TIDAK BOLEH MUNDUR (Ratchet Forward) bool should_modify = false; if(pos_type == POSITION_TYPE_BUY) { if(candidate_sl > current_sl + (point * 0.5)) should_modify = true; } else if(pos_type == POSITION_TYPE_SELL) { if(current_sl == 0.0 || candidate_sl < current_sl - (point * 0.5)) should_modify = true; } if(should_modify) { if(m_trade.PositionModify(ticket, candidate_sl, current_tp)) { string ord_role = is_runner ? "Runner (TP2)" : "Target 1 (TP1)"; Print("đŸŽ¯ [Tiered-Lock] ", ord_role, " ", pos_sym, " Ticket #", ticket, " SL dinaikkan ke ", candidate_sl, " [", tier_label, "]"); g_bot_status = "đŸŽ¯ Lock Active: " + pos_sym + " (" + tier_label + ")"; SendExecutionLog("đŸŽ¯ [Profit-Lock] " + ord_role + " " + pos_sym + " Ticket #" + IntegerToString(ticket) + " SL dikunci ke " + DoubleToString(candidate_sl, digits) + " (" + tier_label + ")", "SHIELD"); } } } } //+------------------------------------------------------------------+ //| Hitung Posisi Aktif Bot Saat Ini | //+------------------------------------------------------------------+ int CountOpenBotPositions() { int count = 0; for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(ticket > 0 && PositionGetInteger(POSITION_MAGIC) == InpMagicNumber) count++; } return count; } //+------------------------------------------------------------------+ //| Cek apakah pair ini sudah memiliki posisi aktif yang berjalan | //+------------------------------------------------------------------+ bool HasOpenPositionForSymbol(string sym) { for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(ticket > 0 && PositionGetInteger(POSITION_MAGIC) == InpMagicNumber) { if(PositionGetString(POSITION_SYMBOL) == sym) return true; } } return false; } //+------------------------------------------------------------------+ //| Auto-deteksi Filling Mode broker (IOC / FOK / Return) | //+------------------------------------------------------------------+ void SetBestFillingMode(string sym) { uint sym_filling = (uint)SymbolInfoInteger(sym, SYMBOL_FILLING_MODE); if((sym_filling & SYMBOL_FILLING_IOC) != 0) m_trade.SetTypeFilling(ORDER_FILLING_IOC); else if((sym_filling & SYMBOL_FILLING_FOK) != 0) m_trade.SetTypeFilling(ORDER_FILLING_FOK); else m_trade.SetTypeFilling(ORDER_FILLING_RETURN); } //+------------------------------------------------------------------+ //| Eksekusi 1 Sinyal ke Broker | //+------------------------------------------------------------------+ bool ExecuteOrder(string sig_id, string inst_name, string action, double entry, double sl, double tp1, double tp2, string setup, string model) { if(CountOpenBotPositions() >= InpMaxOpenTrades * 2) { Print("âš ī¸ Batas maksimal posisi tercapai (", InpMaxOpenTrades, " pair). Sinyal ", inst_name, " ditunda."); g_bot_status = "âš ī¸ Max Open Trades (" + IntegerToString(InpMaxOpenTrades) + " Pair) Penuh"; return false; } string broker_sym = MatchBrokerSymbol(inst_name); SymbolSelect(broker_sym, true); // Cegah order bertumpuk pada pair yang sama jika posisinya masih aktif if(HasOpenPositionForSymbol(broker_sym)) { Print("â„šī¸ Pair ", broker_sym, " sudah memiliki posisi aktif berjalan. Menunggu TP/SL sebelum setup baru."); return false; } double current_ask = SymbolInfoDouble(broker_sym, SYMBOL_ASK); double current_bid = SymbolInfoDouble(broker_sym, SYMBOL_BID); if(current_bid <= 0 || current_ask <= 0) { Print("❌ Simbol broker tidak ditemukan / belum ada quote harga untuk: ", inst_name, " (Dicari: ", broker_sym, ")"); return false; } SetBestFillingMode(broker_sym); double point = SymbolInfoDouble(broker_sym, SYMBOL_POINT); int digits = (int)SymbolInfoInteger(broker_sym, SYMBOL_DIGITS); bool is_gold = (StringFind(broker_sym, "XAU") >= 0 || StringFind(broker_sym, "GOLD") >= 0); bool is_btc = (StringFind(broker_sym, "BTC") >= 0); // Faktor konversi poin ke pip sesuai instrumen double pips_factor = 10.0; if(is_gold) { // Pada Gold, 1 pip standar = $0.10 (10 sen). // Akun Cent / 3 desimal (point 0.001): 1 pip = 100 points // Akun 2 desimal (point 0.01): 1 pip = 10 points pips_factor = (digits == 3) ? 100.0 : 10.0; } else if(is_btc) { // Pada Bitcoin (BTC/USD), 1 pip standar crypto = $1.00 pips_factor = (digits == 2) ? 100.0 : ((digits == 1) ? 10.0 : 1.0); } long cur_spread = SymbolInfoInteger(broker_sym, SYMBOL_SPREAD); double spread_pips = (cur_spread > 0) ? (cur_spread / pips_factor) : 0.0; if(spread_pips > InpMaxSpreadPips) { Print("âš ī¸ Spread terlalu tinggi pada ", broker_sym, " (", spread_pips, " pips). Eksekusi ditunda."); return false; } double curr_price = (action == "BUY") ? current_ask : current_bid; double price_diff = MathAbs(curr_price - entry); double diff_pips = (point > 0) ? ((price_diff / point) / pips_factor) : 0.0; if(diff_pips > InpMaxSlippagePips) { Print("âš ī¸ Slippage melampaui batas pada ", broker_sym, " (", diff_pips, " pips dari entry ", entry, "). Menunggu harga retest."); g_bot_status = "Standby: Menunggu retest entry (" + broker_sym + " selisih " + DoubleToString(diff_pips, 1) + " pips)"; UpdateChartDashboard(); return false; } Print("đŸŽ¯ [EKSEKUSI MT5] Sinyal Valid: ", action, " pada ", broker_sym, " @ ", curr_price, " | Setup: ", setup); bool success = false; if(InpSplitPositions) { string comment1 = "Piptology_TP1"; string comment2 = "Piptology_TP2"; if(action == "BUY") { bool ord1 = m_trade.Buy(InpLotSize, broker_sym, current_ask, sl, tp1, comment1); bool ord2 = m_trade.Buy(InpLotSize, broker_sym, current_ask, sl, tp2, comment2); success = (ord1 || ord2); } else { bool ord1 = m_trade.Sell(InpLotSize, broker_sym, current_bid, sl, tp1, comment1); bool ord2 = m_trade.Sell(InpLotSize, broker_sym, current_bid, sl, tp2, comment2); success = (ord1 || ord2); } } else { string comment = "Piptology_Runner"; if(action == "BUY") success = m_trade.Buy(InpLotSize, broker_sym, current_ask, sl, tp2, comment); else success = m_trade.Sell(InpLotSize, broker_sym, current_bid, sl, tp2, comment); } if(success) { RecordExecutedSignal(sig_id); g_bot_status = "🚀 ORDER TERPASANG: " + broker_sym + " " + action; Print("✅ [Piptology AutoTrader] Posisi terbuka untuk ", broker_sym, " ID: ", sig_id); PlaySound("ok.wav"); SendExecutionLog("🚀 Order Terbuka: " + action + " " + DoubleToString(InpLotSize, 2) + " lot " + broker_sym + " @ " + DoubleToString(curr_price, 5) + " | Setup: " + setup + " (" + model + ")", "ORDER"); return true; } else { Print("❌ Gagal order pada ", broker_sym, ": Error ", GetLastError()); return false; } } //+------------------------------------------------------------------+ //| Mode 2: Multi-Pair Portfolio Scanner | //+------------------------------------------------------------------+ void PollMultiPairSignals() { double bal = AccountInfoDouble(ACCOUNT_BALANCE); double eq = AccountInfoDouble(ACCOUNT_EQUITY); double fm = AccountInfoDouble(ACCOUNT_MARGIN_FREE); double ml = AccountInfoDouble(ACCOUNT_MARGIN_LEVEL); long login = AccountInfoInteger(ACCOUNT_LOGIN); string company = AccountInfoString(ACCOUNT_COMPANY); string url = InpServerURL + "/api/mt5/auto-trade/batch?instruments=" + InpMonitoredPairs + "&tf=" + InpTimeframe + "&account=" + IntegerToString(login) + "&broker=" + company + "&balance=" + DoubleToString(bal, 2) + "&equity=" + DoubleToString(eq, 2) + "&free_margin=" + DoubleToString(fm, 2) + "&margin_level=" + DoubleToString(ml, 2); char post_data[]; char result_data[]; string result_headers; string headers = "Content-Type: application/json\r\nUser-Agent: PiptologyMT5Client/2.30\r\n"; ResetLastError(); int res = WebRequest("GET", url, headers, 4000, post_data, result_data, result_headers); if(res == -1) { int err = GetLastError(); if(err == 4014 || err == 5203) g_bot_status = "âš ī¸ Error: Izinkan WebRequest di Tools -> Options -> Expert Advisors"; else g_bot_status = "âš ī¸ WebRequest Error " + IntegerToString(err); return; } string response = CharArrayToString(result_data); if(StringLen(response) < 10) return; int active_cnt = (int)ExtractJsonDouble(response, "active_signals_count"); if(active_cnt == 0) { g_bot_status = "✅ Standby: Memindai Seluruh Pair Potensial (" + InpTimeframe + ")"; return; } // Cari setiap blok sinyal dalam array signals int search_idx = StringFind(response, "\"signals\":["); if(search_idx < 0) return; int obj_start = StringFind(response, "{", search_idx + 10); while(obj_start >= 0) { int obj_end = StringFind(response, "}", obj_start); if(obj_end < 0) break; string chunk = StringSubstr(response, obj_start, obj_end - obj_start + 1); string sig_id = ExtractJsonString(chunk, "id"); string inst_name = ExtractJsonString(chunk, "instrument"); string action = ExtractJsonString(chunk, "action"); string setup = ExtractJsonString(chunk, "setup"); string model = ExtractJsonString(chunk, "model"); double entry = ExtractJsonDouble(chunk, "entry"); double sl = ExtractJsonDouble(chunk, "sl"); double tp1 = ExtractJsonDouble(chunk, "tp1"); double tp2 = ExtractJsonDouble(chunk, "tp2"); if(sig_id != "" && !IsSignalExecuted(sig_id)) { ExecuteOrder(sig_id, inst_name, action, entry, sl, tp1, tp2, setup, model); } obj_start = StringFind(response, "{", obj_end + 1); } } //+------------------------------------------------------------------+ //| Mode 1: Single Chart Saja | //+------------------------------------------------------------------+ void PollSingleChartSignal() { string inst_name = (StringFind(_Symbol, "XAU") >= 0 || StringFind(_Symbol, "GOLD") >= 0) ? "XAU_USD" : _Symbol; double bal = AccountInfoDouble(ACCOUNT_BALANCE); double eq = AccountInfoDouble(ACCOUNT_EQUITY); double fm = AccountInfoDouble(ACCOUNT_MARGIN_FREE); double ml = AccountInfoDouble(ACCOUNT_MARGIN_LEVEL); long login = AccountInfoInteger(ACCOUNT_LOGIN); string company = AccountInfoString(ACCOUNT_COMPANY); string url = InpServerURL + "/api/mt5/auto-trade/signals?instrument=" + inst_name + "&tf=" + InpTimeframe + "&account=" + IntegerToString(login) + "&broker=" + company + "&balance=" + DoubleToString(bal, 2) + "&equity=" + DoubleToString(eq, 2) + "&free_margin=" + DoubleToString(fm, 2) + "&margin_level=" + DoubleToString(ml, 2); char post_data[]; char result_data[]; string result_headers; string headers = "Content-Type: application/json\r\nUser-Agent: PiptologyMT5Client/2.30\r\n"; ResetLastError(); int res = WebRequest("GET", url, headers, 3000, post_data, result_data, result_headers); if(res == -1) return; string response = CharArrayToString(result_data); if(StringLen(response) < 10) return; bool has_active = ExtractJsonBool(response, "has_active_signal"); if(!has_active) { g_bot_status = "✅ Standby: Menunggu Sinyal " + _Symbol + " (" + InpTimeframe + ")"; return; } string sig_id = ExtractJsonString(response, "id"); string action = ExtractJsonString(response, "action"); string setup = ExtractJsonString(response, "setup"); string model = ExtractJsonString(response, "model"); double entry = ExtractJsonDouble(response, "entry"); double sl = ExtractJsonDouble(response, "sl"); double tp1 = ExtractJsonDouble(response, "tp1"); double tp2 = ExtractJsonDouble(response, "tp2"); if(sig_id != "" && !IsSignalExecuted(sig_id)) { ExecuteOrder(sig_id, inst_name, action, entry, sl, tp1, tp2, setup, model); } }