import MetaTrader5 as mt5 import logging import time from datetime import datetime from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart import smtplib import json # ================================ # Logging setup # ================================ logging.basicConfig(level=logging.INFO, format="%(asctime)s | %(levelname)s | %(message)s") log = logging.getLogger() # ================================ # Load config # ================================ def load_config(filepath="config.json"): with open(filepath, "r") as f: return json.load(f) # ================================ # Send Email (Multiple Recipients) # ================================ def send_email(subject, body, smtp_config): recipients = smtp_config["recipient_email"] if isinstance(recipients, str): recipients = [recipients] msg = MIMEMultipart() msg["From"] = smtp_config["sender_email"] msg["To"] = ", ".join(recipients) msg["Subject"] = subject msg.attach(MIMEText(body, "plain")) try: with smtplib.SMTP(smtp_config["smtp_server"], smtp_config["smtp_port"]) as server: server.starttls() server.login(smtp_config["sender_email"], smtp_config["sender_password"]) server.send_message(msg) log.info(f"Email sent to: {', '.join(recipients)}") except Exception as e: log.error(f"Error sending email: {e}") # ================================ # Fetch Position Data # ================================ def fetch_open_positions(): positions = mt5.positions_get() if positions is None or len(positions) == 0: log.info("No open positions") return [] data = [] for pos in positions: tick = mt5.symbol_info_tick(pos.symbol) if tick is None: current_price = None else: current_price = tick.last distance_sl = (current_price - pos.sl) if pos.sl else None distance_tp = (pos.tp - current_price) if pos.tp else None profit = pos.profit relative = "N/A" if current_price and pos.volume: if pos.type == mt5.ORDER_TYPE_BUY: relative = "Above Open" if current_price > pos.price_open else ("Below Open" if current_price < pos.price_open else "At Open") elif pos.type == mt5.ORDER_TYPE_SELL: relative = "Below Open" if current_price < pos.price_open else ("Above Open" if current_price > pos.price_open else "At Open") data.append({ "ticket": pos.ticket, "symbol": pos.symbol, "type": "BUY" if pos.type == mt5.ORDER_TYPE_BUY else "SELL", "volume": pos.volume, "open_price": pos.price_open, "current_price": current_price, "sl": pos.sl, "tp": pos.tp, "distance_sl": distance_sl, "distance_tp": distance_tp, "profit": profit, "relative_to_open": relative }) return data # ================================ # Main monitoring loop # ================================ def main(): config = load_config() smtp_config = config["email"] update_interval = config.get("update_interval_sec", 60) # default 60 sec # Connect to running MT5 terminal if not mt5.initialize(): log.error(f"MT5 initialize failed: {mt5.last_error()}") return log.info("Connected to MT5 terminal") try: while True: positions_data = fetch_open_positions() if positions_data: report_lines = [f"Open Positions Report - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"] for p in positions_data: report_lines.append(f"Ticket: {p['ticket']}, Symbol: {p['symbol']}, Type: {p['type']}, Volume: {p['volume']}") report_lines.append(f"Open Price: {p['open_price']}, Current Price: {p['current_price']}") report_lines.append(f"SL: {p['sl']}, TP: {p['tp']}") report_lines.append(f"Distance to SL: {p['distance_sl']}, Distance to TP: {p['distance_tp']}") report_lines.append(f"Profit: {p['profit']}, Relative to Open: {p['relative_to_open']}\n") log.info("\n".join(report_lines)) send_email(subject="Live Positions Update", body="\n".join(report_lines), smtp_config=smtp_config) else: log.info("No positions to report") time.sleep(update_interval) except KeyboardInterrupt: log.info("Monitoring stopped by user") finally: mt5.shutdown() log.info("MT5 connection closed") # ================================ # Run # ================================ if __name__ == "__main__": main()