98 lines
4.1 KiB
Python
98 lines
4.1 KiB
Python
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()
|