import MetaTrader5 as mt5 import pytz import logging import json import os import smtplib from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart from datetime import datetime, timedelta # ====================================================== # Logging # ====================================================== logging.basicConfig( level=logging.INFO, format="%(asctime)s | %(levelname)s | %(message)s", handlers=[logging.StreamHandler()] ) log = logging.getLogger() # ====================================================== # Load Config # ====================================================== def load_config(filepath="config.json"): if not os.path.exists(filepath): raise FileNotFoundError(f"Config file not found: {filepath}") with open(filepath, "r") as f: return json.load(f) # ====================================================== # MT5 Initialization # ====================================================== def initialize_mt5(config): log.info("Initializing MetaTrader 5...") if not mt5.initialize( path=config["mt5Pathway"], login=int(config["username"]), password=config["password"], server=config["server"] ): raise RuntimeError(f"MT5 initialize failed: {mt5.last_error()}") log.info("MT5 connected successfully") # ====================================================== # Symbol Management # ====================================================== def ensure_symbol_visible(symbol): info = mt5.symbol_info(symbol) if info is None: log.error(f"Symbol not found: {symbol}") return False if not info.visible: if mt5.symbol_select(symbol, True): log.info(f"Symbol {symbol} made visible") else: log.error(f"Failed to make symbol visible: {symbol}") return False return True # ====================================================== # Price Fetching Helpers # ====================================================== def get_current_price(symbol): """Fetch current midpoint price""" if not ensure_symbol_visible(symbol): return None tick = mt5.symbol_info_tick(symbol) if tick: bid = getattr(tick, "bid", 0.0) or 0.0 ask = getattr(tick, "ask", 0.0) or 0.0 if bid > 0 and ask > 0: return (bid + ask) / 2.0 if getattr(tick, "last", 0.0) > 0: return float(tick.last) # fallback utc_now = datetime.utcnow() ticks = mt5.copy_ticks_from(symbol, utc_now - timedelta(seconds=30), 50, mt5.COPY_TICKS_ALL) if ticks is not None and len(ticks) > 0: last_tick = ticks[-1] if "last" in last_tick.dtype.names and last_tick["last"] > 0: return float(last_tick["last"]) return None def get_price_at_time(symbol, target_dt_ny): """Get open price of candle at specific NY time""" if not ensure_symbol_visible(symbol): return None utc_from = target_dt_ny.astimezone(pytz.utc) utc_to = utc_from + timedelta(minutes=1) rates = mt5.copy_rates_range(symbol, mt5.TIMEFRAME_M1, utc_from, utc_to) if rates is None or len(rates) == 0: return None return float(rates[0]["open"]) # ====================================================== # Weekly Open Calculation # ====================================================== def get_weekly_open(symbol): """Monday 00:00 NY open price""" tz = pytz.timezone("America/New_York") now_ny = datetime.now(tz) monday = now_ny - timedelta(days=now_ny.weekday()) # Monday of current week monday_open = tz.localize(datetime.combine(monday.date(), datetime.min.time())) return get_price_at_time(symbol, monday_open) # ====================================================== # Previous Month High/Low # ====================================================== def get_previous_month_high_low(symbol): """Fetch previous month's high and low using D1 candles""" if not ensure_symbol_visible(symbol): return None, None tz = pytz.timezone("America/New_York") now = datetime.now(tz) first_day_this_month = tz.localize(datetime(now.year, now.month, 1)) last_day_prev_month = first_day_this_month - timedelta(days=1) first_day_prev_month = tz.localize(datetime(last_day_prev_month.year, last_day_prev_month.month, 1)) rates = mt5.copy_rates_range(symbol, mt5.TIMEFRAME_D1, first_day_prev_month.astimezone(pytz.utc), first_day_this_month.astimezone(pytz.utc)) if rates is None or len(rates) == 0: return None, None highs = [r['high'] for r in rates] lows = [r['low'] for r in rates] return max(highs), min(lows) # ====================================================== # Email Sending # ====================================================== def send_email(config, subject, body): smtp_config = config["email"] msg = MIMEMultipart() msg['From'] = smtp_config["sender_email"] msg['To'] = smtp_config["recipient_email"] 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 {smtp_config['recipient_email']}") except Exception as e: log.error(f"Email sending failed: {e}") # ====================================================== # Main # ====================================================== def main(): log.info("===== Weekly & Monthly Levels Monitor =====") # Load configuration config = load_config() # Initialize MT5 initialize_mt5(config) # Symbols to monitor symbols = ["XAUUSD", "USTEC", "US30"] tz = pytz.timezone("America/New_York") now_ny = datetime.now(tz) email_body = [f"Report Generated at NY Time: {now_ny.strftime('%Y-%m-%d %H:%M:%S')}", ""] for symbol in symbols: log.info(f"Processing {symbol}...") # Current price current_price = get_current_price(symbol) if current_price is None: log.error(f"Cannot fetch current price for {symbol}") continue # Weekly open weekly_open = get_weekly_open(symbol) # Daily open and close today_open_time = tz.localize(datetime.combine(now_ny.date(), datetime.min.time())) daily_open = get_price_at_time(symbol, today_open_time) # Use last completed D1 candle for daily close daily_rates = mt5.copy_rates_from_pos(symbol, mt5.TIMEFRAME_D1, 1, 2) daily_close = float(daily_rates[-2]['close']) if daily_rates is not None and len(daily_rates) >= 2 else None # Previous month high/low prev_month_high, prev_month_low = get_previous_month_high_low(symbol) # Build report report = [ f"Symbol: {symbol}", f"Current Price: {current_price:.2f}", f"Weekly Open (Mon 00:00 NY): {weekly_open if weekly_open else 'N/A'}", f"Daily Open: {daily_open if daily_open else 'N/A'}", f"Previous Daily Close: {daily_close if daily_close else 'N/A'}", f"Prev Month High: {prev_month_high if prev_month_high else 'N/A'}", f"Prev Month Low: {prev_month_low if prev_month_low else 'N/A'}", ] # Relative calculations if weekly_open and current_price: report.append(f"Δ Current vs Weekly Open: {current_price - weekly_open:.2f}") if daily_open and current_price: report.append(f"Δ Current vs Daily Open: {current_price - daily_open:.2f}") if daily_close and current_price: report.append(f"Δ Current vs Daily Close: {current_price - daily_close:.2f}") email_body.extend(report) email_body.append("") # spacer # Send email send_email(config, subject="Weekly & Monthly Market Report", body="\n".join(email_body)) # Shutdown MT5 mt5.shutdown() log.info("===== Script Finished =====") # ====================================================== # Run # ====================================================== if __name__ == "__main__": try: main() except Exception as e: log.error(f"Fatal error: {e}") mt5.shutdown()