import time, yaml from core.connector import MT5 from risk.manager import RiskManager from filters.filters import Filters from strategies import REGISTRY from notifier.telegram import Telegram, BotState from utils.indicators import atr from utils.logger import get_logger log = get_logger() def main(): cfg = yaml.safe_load(open("config.yaml", encoding="utf-8")) bot = cfg["bot"] mt = MT5(cfg["account"], bot["magic"], bot["deviation"]) if not mt.connect(): raise SystemExit("Impossible de se connecter à MT5") risk, filt, state = RiskManager(cfg["risk"]), Filters(cfg["filters"]), BotState() def status(): a = mt.account() pos = mt.positions() pnl = sum(p.profit for p in pos) return (f"Solde {a.balance:.2f} | Equity {a.equity:.2f}\n" f"Positions: {len(pos)} | PnL flottant {pnl:.2f}\n" f"{'⏸ PAUSE' if state.paused else '▶️ ACTIF'}" f"{' | 🛑 ' + risk.halted_reason if risk.halted_reason else ''}") tg = Telegram(cfg["telegram"], state, status) strategies = [] for s in cfg["strategies"]: for sym in s["symbols"]: strategies.append((sym, REGISTRY[s["name"]](s["timeframe"], s["params"]))) last_bar = {} tg.send("🚀 Bot démarré") try: while True: if not mt.ensure(): time.sleep(10); continue a = mt.account() risk.update(a.balance, a.equity) if state.close_all: for p in mt.positions(): mt.close_position(p) state.close_all, state.paused = False, True tg.send("✅ Positions fermées, bot en pause") ok, reason = risk.check(a.equity) if not ok and not risk.halted_reason: risk.halted_reason = reason log.error(f"KILL-SWITCH: {reason}") for p in mt.positions(): mt.close_position(p) tg.send(f"🛑 KILL-SWITCH: {reason}\nPositions fermées jusqu'à demain.") # --- gestion des positions ouvertes (break-even / trailing) for p in mt.positions(): df = mt.rates(p.symbol, "M15", 100) t = mt.tick(p.symbol) if df is None or t is None: continue price = t.bid if p.type == 0 else t.ask sl = risk.new_sl(p, atr(df.iloc[:-1], 14).iloc[-1], price) if sl: mt.modify_sl(p, sl) # --- recherche de signaux if not (state.paused or risk.halted_reason): for sym, strat in strategies: df = mt.rates(sym, strat.timeframe, 500) if df is None or len(df) < 250: continue df = df.iloc[:-1] # barres clôturées uniquement key = (sym, strat.name) if last_bar.get(key) == df.time.iloc[-1]: continue last_bar[key] = df.time.iloc[-1] sig = strat.on_bar(sym, df) if not sig: continue info, tick = mt.info(sym), mt.tick(sym) good, why = filt.all_ok(sym, info, tick, df) if not good: log.info(f"Signal {sym} {strat.name} ignoré: {why}"); continue if not risk.can_open(mt.positions(), mt.positions(sym)): continue lots = risk.lot_size(a.equity, sig.sl_distance, info) if lots <= 0: continue px = tick.ask if sig.side == "buy" else tick.bid sl = px - sig.sl_distance if sig.side == "buy" else px + sig.sl_distance tp = px + sig.tp_distance if sig.side == "buy" else px - sig.tp_distance if mt.open_trade(sym, sig.side, lots, sl, tp, f"{strat.name}"): tg.send(f"📈 {sig.side.upper()} {sym} {lots} lots | {strat.name}\nSL {sl:.5f} TP {tp:.5f}") time.sleep(bot["poll_seconds"]) except KeyboardInterrupt: log.info("Arrêt demandé") finally: mt.shutdown() if __name__ == "__main__": main()