""" ============================================================================== PIPTOLOGY AUTO-TRADER BOT FOR METATRADER 5 (PYTHON ENGINE) The Science of Market Precision Script ini menghubungkan sinyal live SMC dari Piptology (https://piptology.sakti.asia) langsung ke terminal MetaTrader 5 yang terpasang di komputer Anda. Fitur Unggulan: - Multi-Pair Portfolio Scanner: Otomatis memindai semua pair potensial sekaligus! - Eksekusi Otomatis BUY / SELL saat sinyal ACTIVE muncul - Split Posisi (Order 1 untuk TP1, Order 2 untuk TP2) - Auto-Breakeven (Otomatis kunci SL ke Entry saat TP1 tercapai) - Slippage & Spread Protection ============================================================================== """ import time import sys import httpx from datetime import datetime sys.stdout.reconfigure(encoding='utf-8') # Konfigurasi Bot BASE_SERVER = "https://piptology.sakti.asia" MONITORED_PAIRS = "XAU_USD,EUR_USD,GBP_USD,USD_JPY,BTC_USD" # Pair yang dipantau sekaligus TIMEFRAME = "M15" # M5, M15, M30, atau H1 LOT_SIZE = 0.01 # Ukuran lot per order SPLIT_POSITIONS = True # Buka 2 order (Order 1 TP1, Order 2 TP2) AUTO_BREAKEVEN = True # Kunci SL ke Entry saat TP1 tercapai MAX_OPEN_TRADES = 6 # Maksimal posisi aktif bersamaan (Mendukung 5+ Pair) MAX_SLIPPAGE_PIPS = 25 # Toleransi selisih harga dari entry sinyal MAX_SPREAD_PIPS = 40 # Batas spread maksimal broker MAGIC_NUMBER = 777999 # ID unik transaksi bot POLL_INTERVAL = 3 # Cek sinyal setiap 3 detik try: import MetaTrader5 as mt5 MT5_AVAILABLE = True except ImportError: MT5_AVAILABLE = False def check_mt5_connection(): if not MT5_AVAILABLE: print("āŒ Library MetaTrader5 belum terinstall.") print(" Jalankan: pip install MetaTrader5") return False if not mt5.initialize(): print("āŒ Gagal menginisialisasi MetaTrader 5 terminal.") print(f" Error: {mt5.last_error()}") print(" Pastikan MetaTrader 5 sudah terbuka dan login ke akun broker Anda.") return False acc = mt5.account_info() if acc is None: print("āŒ Gagal mengambil informasi akun MT5.") return False print("====================================================================") print(" šŸš€ PIPTOLOGY MULTI-PAIR AUTO-TRADER BOT MT5 CONNECTED!") print("====================================================================") print(f"• Broker : {acc.company} (Server: {acc.server})") print(f"• Akun Login : {acc.login} ({acc.name})") print(f"• Balance : ${acc.balance:,.2f} | Equity: ${acc.equity:,.2f}") print(f"• Leverage : 1:{acc.leverage}") print(f"• Mode : MULTI-PAIR PORTFOLIO SCANNER") print(f"• Pair Aktif : {MONITORED_PAIRS} ({TIMEFRAME})") print(f"• Lot Size : {LOT_SIZE} per order (Split TP1/TP2: {SPLIT_POSITIONS})") print(f"• Auto-BEP : {'AKTIF' if AUTO_BREAKEVEN else 'NON-AKTIF'}") print("====================================================================") return True def find_matching_symbol(desired_name): """Mencari nama simbol yang sesuai di broker (e.g. XAUUSD, XAUUSDm, GOLD, XAUUSD.raw).""" clean_sym = desired_name.replace("_", "") candidates = [ clean_sym, "XAUUSD", "GOLD", clean_sym + "m", clean_sym + ".a", clean_sym + ".raw", clean_sym + ".pro", clean_sym + "#" ] for sym in candidates: info = mt5.symbol_info(sym) if info is not None: if not info.visible: mt5.symbol_select(sym, True) return sym return clean_sym def check_and_apply_auto_breakeven(): """Memeriksa semua posisi terbuka dan menerapkan Smart Runner Tiered Profit Locking.""" positions = mt5.positions_get() if not positions: return for pos in positions: if pos.magic != MAGIC_NUMBER: continue symbol = pos.symbol tick = mt5.symbol_info_tick(symbol) if not tick: continue sym_info = mt5.symbol_info(symbol) point = sym_info.point if sym_info else 0.00001 digits = sym_info.digits if sym_info else 5 open_price = pos.price_open current_sl = pos.sl pos_type = pos.type comment = pos.comment or "" is_runner = ("TP2" in comment or "Runner" in comment) # Hitung jarak keuntungan harga if pos_type == mt5.ORDER_TYPE_BUY: profit_dist = tick.bid - open_price elif pos_type == mt5.ORDER_TYPE_SELL: profit_dist = open_price - tick.ask else: continue if profit_dist <= 0: continue # Belum profit atau masih minus, SL diam di tempat! is_gold = ("XAU" in symbol or "GOLD" in symbol) is_btc = ("BTC" in symbol) target_offset = 0.0 tier_label = "" # 1. EMAS (XAUUSD) - Acuan Nilai Dollar ($) if is_gold: if is_runner: if profit_dist >= 14.0: target_offset = 8.0; tier_label = "Gold Tier 3 (+$8.00)" elif profit_dist >= 8.0: target_offset = 4.0; tier_label = "Gold Tier 2 (+$4.00)" elif profit_dist >= 3.5: target_offset = 0.50; tier_label = "Gold Tier 1 BEP (+$0.50)" else: if profit_dist >= 8.0: target_offset = 4.0; tier_label = "Gold Tier 2 (+$4.00)" elif profit_dist >= 3.5: target_offset = 1.50; tier_label = "Gold TP1 Lock (+$1.50)" # 2. BITCOIN (BTCUSD) - Acuan Nilai Dollar ($) elif is_btc: if is_runner: if profit_dist >= 900.0: target_offset = 600.0; tier_label = "BTC Tier 3 (+$600)" elif profit_dist >= 500.0: target_offset = 300.0; tier_label = "BTC Tier 2 (+$300)" elif profit_dist >= 200.0: target_offset = 25.0; tier_label = "BTC Tier 1 BEP (+$25)" else: if profit_dist >= 500.0: target_offset = 300.0; tier_label = "BTC Tier 2 (+$300)" elif profit_dist >= 200.0: target_offset = 100.0; tier_label = "BTC TP1 Lock (+$100)" # 3. FOREX (EURUSD, GBPUSD, USDJPY, dll.) - Acuan Pips else: pip_size = (10.0 * point) if digits in (3, 5) else point profit_pips = profit_dist / pip_size if is_runner: if profit_pips >= 80.0: target_offset = 50.0 * pip_size; tier_label = "Forex Tier 3 (+50p)" elif profit_pips >= 50.0: target_offset = 30.0 * pip_size; tier_label = "Forex Tier 2 (+30p)" elif profit_pips >= 20.0: target_offset = 2.0 * pip_size; tier_label = "Forex Tier 1 BEP (+2p)" else: if profit_pips >= 50.0: target_offset = 30.0 * pip_size; tier_label = "Forex Tier 2 (+30p)" elif profit_pips >= 20.0: target_offset = 10.0 * pip_size; tier_label = "Forex TP1 Lock (+10p)" if target_offset <= 0: continue # Calon SL baru should_modify = False if pos_type == mt5.ORDER_TYPE_BUY: candidate_sl = round(open_price + target_offset, digits) should_modify = candidate_sl > (current_sl + point * 0.5) elif pos_type == mt5.ORDER_TYPE_SELL: candidate_sl = round(open_price - target_offset, digits) should_modify = (current_sl == 0.0) or (candidate_sl < current_sl - point * 0.5) if should_modify: req = { "action": mt5.TRADE_ACTION_SLTP, "position": pos.ticket, "symbol": symbol, "sl": candidate_sl, "tp": pos.tp } res = mt5.order_send(req) if res.retcode == mt5.TRADE_RETCODE_DONE: ord_role = "Runner (TP2)" if is_runner else "Target 1 (TP1)" print(f"šŸŽÆ [Tiered-Lock] {ord_role} {symbol} Ticket #{pos.ticket} SL dikunci ke {candidate_sl} ({tier_label})!") def count_bot_open_positions(): positions = mt5.positions_get() if not positions: return 0 return sum(1 for p in positions if p.magic == MAGIC_NUMBER) def execute_trade(action, symbol, entry_sig, sl, tp1, tp2, sig_id, setup): if count_bot_open_positions() >= MAX_OPEN_TRADES * 2: print(f"āš ļø Batas maksimal posisi terbuka ({MAX_OPEN_TRADES}) tercapai. Sinyal {symbol} dilewati.") return False tick = mt5.symbol_info_tick(symbol) if not tick: print(f"āŒ Gagal mengambil tick pasar untuk {symbol}") return False sym_info = mt5.symbol_info(symbol) point = sym_info.point curr_price = tick.ask if action == "BUY" else tick.bid diff_pips = abs(curr_price - entry_sig) / (point * 10) if diff_pips > MAX_SLIPPAGE_PIPS: print(f"āš ļø Sinyal dilewati: Harga {symbol} sudah bergerak {diff_pips:.1f} pips dari entry {entry_sig}.") return False spread_pips = (sym_info.spread * point) * 10 if spread_pips > MAX_SPREAD_PIPS: print(f"āš ļø Spread {symbol} terlalu tinggi ({spread_pips:.1f} pips). Eksekusi ditunda.") return False order_type = mt5.ORDER_TYPE_BUY if action == "BUY" else mt5.ORDER_TYPE_SELL price = tick.ask if action == "BUY" else tick.bid print(f"\nšŸŽÆ [EKSEKUSI MT5] Membuka posisi {action} pada {symbol} @ {price} | Setup: {setup}") if SPLIT_POSITIONS: req1 = { "action": mt5.TRADE_ACTION_DEAL, "symbol": symbol, "volume": LOT_SIZE, "type": order_type, "price": price, "sl": sl, "tp": tp1, "deviation": 20, "magic": MAGIC_NUMBER, "comment": f"Piptology_TP1", "type_time": mt5.ORDER_TIME_GTC, "type_filling": mt5.ORDER_FILLING_IOC, } res1 = mt5.order_send(req1) if res1.retcode != mt5.TRADE_RETCODE_DONE: req1["type_filling"] = mt5.ORDER_FILLING_RETURN res1 = mt5.order_send(req1) req2 = { "action": mt5.TRADE_ACTION_DEAL, "symbol": symbol, "volume": LOT_SIZE, "type": order_type, "price": price, "sl": sl, "tp": tp2, "deviation": 20, "magic": MAGIC_NUMBER, "comment": f"Piptology_TP2", "type_time": mt5.ORDER_TIME_GTC, "type_filling": mt5.ORDER_FILLING_IOC, } res2 = mt5.order_send(req2) if res2.retcode != mt5.TRADE_RETCODE_DONE: req2["type_filling"] = mt5.ORDER_FILLING_RETURN res2 = mt5.order_send(req2) if (res1 and res1.retcode == mt5.TRADE_RETCODE_DONE) or (res2 and res2.retcode == mt5.TRADE_RETCODE_DONE): print(f"āœ… {symbol} Order 1 (TP1 {tp1}) Ticket: #{res1.order if res1 else '-'}") print(f"āœ… {symbol} Order 2 (TP2 {tp2}) Ticket: #{res2.order if res2 else '-'}") return True else: print(f"āŒ Gagal membuka order {symbol}: {res1.comment if res1 else 'Error'}") return False else: req = { "action": mt5.TRADE_ACTION_DEAL, "symbol": symbol, "volume": LOT_SIZE, "type": order_type, "price": price, "sl": sl, "tp": tp2, "deviation": 20, "magic": MAGIC_NUMBER, "comment": f"Piptology_Runner", "type_time": mt5.ORDER_TIME_GTC, "type_filling": mt5.ORDER_FILLING_IOC, } res = mt5.order_send(req) if res.retcode == mt5.TRADE_RETCODE_DONE: print(f"āœ… {symbol} Order Runner Berhasil: Ticket #{res.order}") try: client = httpx.Client(timeout=3) client.post(f"{BASE_SERVER}/api/mt5/investor-view/log", json={"msg": f"šŸš€ Order Terbuka: {action} {LOT_SIZE} {symbol} @ {price} | Runner", "type": "ORDER"}) client.close() except Exception: pass return True else: print(f"āŒ Gagal order: {res.comment}") return False def send_telemetry_heartbeat(client): """Mengirim telemetri terminal MT5 ke Halaman Investor Web Piptology secara realtime.""" try: acc = mt5.account_info() if not acc: return positions = mt5.positions_get() pos_list = [] total_pnl = 0.0 if positions: for p in positions: if p.magic == MAGIC_NUMBER: is_bep = (p.type == mt5.ORDER_TYPE_BUY and p.sl >= p.price_open - 0.05) or \ (p.type == mt5.ORDER_TYPE_SELL and p.sl <= p.price_open + 0.05 and p.sl > 0) pos_list.append({ "ticket": p.ticket, "symbol": p.symbol, "type": "BUY" if p.type == mt5.ORDER_TYPE_BUY else "SELL", "lots": round(p.volume, 2), "open_price": round(p.price_open, 5), "current_price": round(p.price_current, 5), "sl": round(p.sl, 5), "tp": round(p.tp, 5), "profit": round(p.profit, 2), "comment": p.comment, "auto_bep": is_bep, "status": "Auto-BE Locked (0% Risk)" if is_bep else "Running to Target" }) total_pnl += p.profit payload = { "account_login": acc.login, "account_name": acc.name, "broker": acc.company, "server": acc.server, "currency": acc.currency, "leverage": acc.leverage, "balance": round(acc.balance, 2), "equity": round(acc.equity, 2), "floating_profit": round(total_pnl, 2), "margin": round(acc.margin, 2), "free_margin": round(acc.margin_free, 2), "margin_level": round(acc.margin_level, 2) if acc.margin_level else 0.0, "bot_mode": "MULTI-PAIR PORTFOLIO SCANNER (Python Engine)", "monitored_pairs": MONITORED_PAIRS, "timeframe": TIMEFRAME, "magic_number": MAGIC_NUMBER, "auto_breakeven": AUTO_BREAKEVEN, "open_positions": pos_list } client.post(f"{BASE_SERVER}/api/mt5/heartbeat", json=payload, timeout=3.0) except Exception: pass def run_bot_loop(): if not check_mt5_connection(): return executed_ids = set() client = httpx.Client(timeout=10) batch_url = f"{BASE_SERVER}/api/mt5/auto-trade/batch?instruments={MONITORED_PAIRS}&tf={TIMEFRAME}" print(f"Memulai pemantauan multi-pair di {batch_url}...") print(f"šŸ“” Telemetri Halaman Investor aktif dikirim ke {BASE_SERVER}/app") while True: try: # Kirim detak telemetri akun ke Halaman Investor send_telemetry_heartbeat(client) if AUTO_BREAKEVEN: check_and_apply_auto_breakeven() r = client.get(batch_url) if r.status_code == 200: data = r.json() active_sigs = data.get("signals", []) for sig in active_sigs: sig_id = sig["id"] if sig_id not in executed_ids: inst = sig["instrument"] mt5_sym = find_matching_symbol(inst) print(f"\nšŸ“¢ [{datetime.now().strftime('%H:%M:%S')}] SINYAL ACTIVE PADA {mt5_sym} ({inst})!") print(f" Aksi : {sig['action']} | {sig['model']}") print(f" Entry : {sig['entry']} | SL: {sig['sl']}") print(f" TP1 : {sig['tp1']} (+{sig['tp1_pips']} p) | TP2: {sig['tp2']} (+{sig['tp2_pips']} p)") success = execute_trade( action=sig["action"], symbol=mt5_sym, entry_sig=sig["entry"], sl=sig["sl"], tp1=sig["tp1"], tp2=sig["tp2"], sig_id=sig_id, setup=sig["setup"] ) executed_ids.add(sig_id) time.sleep(POLL_INTERVAL) except KeyboardInterrupt: print("\nšŸ›‘ Bot dihentikan.") break except Exception as e: print(f"āš ļø Error loop: {e}") time.sleep(POLL_INTERVAL) mt5.shutdown() if __name__ == "__main__": run_bot_loop()