169 lines
5.8 KiB
Python
169 lines
5.8 KiB
Python
|
import MetaTrader5 as mt5
|
||
|
import logging
|
||
|
import json
|
||
|
from datetime import datetime
|
||
|
from email.mime.text import MIMEText
|
||
|
from email.mime.multipart import MIMEMultipart
|
||
|
import smtplib
|
||
|
|
||
|
# ================================
|
||
|
# Logging
|
||
|
# ================================
|
||
|
logging.basicConfig(
|
||
|
level=logging.INFO,
|
||
|
format="%(asctime)s | %(levelname)s | %(message)s"
|
||
|
)
|
||
|
log = logging.getLogger()
|
||
|
|
||
|
# ================================
|
||
|
# Load Config
|
||
|
# ================================
|
||
|
def load_config(filepath="config.json"):
|
||
|
log.info(f"Loading configuration file: {filepath}")
|
||
|
with open(filepath, "r") as f:
|
||
|
config = json.load(f)
|
||
|
log.info("Configuration loaded successfully")
|
||
|
return config
|
||
|
|
||
|
# ================================
|
||
|
# 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 Yearly Data
|
||
|
# ================================
|
||
|
def get_yearly_data(symbol):
|
||
|
log.info(f"Fetching yearly data for {symbol}")
|
||
|
|
||
|
# Ensure symbol is selected in Market Watch
|
||
|
if not mt5.symbol_select(symbol, True):
|
||
|
log.warning(f"Could not select symbol {symbol}")
|
||
|
return None
|
||
|
|
||
|
now = datetime.now()
|
||
|
year_start = datetime(now.year, 1, 1)
|
||
|
|
||
|
rates = mt5.copy_rates_range(symbol, mt5.TIMEFRAME_D1, year_start, now)
|
||
|
if rates is None or len(rates) == 0:
|
||
|
log.warning(f"No historical data for {symbol}")
|
||
|
return None
|
||
|
|
||
|
# OHLC
|
||
|
open_price = rates[0]['open']
|
||
|
high_price = max(r['high'] for r in rates)
|
||
|
low_price = min(r['low'] for r in rates)
|
||
|
close_price = rates[-1]['close']
|
||
|
range_points = high_price - low_price
|
||
|
|
||
|
# Current price
|
||
|
tick = mt5.symbol_info_tick(symbol)
|
||
|
if tick is None:
|
||
|
log.warning(f"No tick data for {symbol}")
|
||
|
current_price = None
|
||
|
rel_open = rel_high = rel_low = rel_close = None
|
||
|
else:
|
||
|
# Use last price, fallback to bid if last is zero
|
||
|
current_price = tick.last if tick.last != 0 else tick.bid
|
||
|
multiplier = 100 if symbol.lower().startswith("xau") else 1
|
||
|
rel_open = (current_price - open_price) * multiplier
|
||
|
rel_high = (current_price - high_price) * multiplier
|
||
|
rel_low = (current_price - low_price) * multiplier
|
||
|
rel_close = (current_price - close_price) * multiplier
|
||
|
|
||
|
range_pips = range_points * (100 if symbol.lower().startswith("xau") else 1)
|
||
|
|
||
|
log.info(f"{symbol} → Open: {open_price}, High: {high_price}, Low: {low_price}, Close: {close_price}, Current: {current_price}")
|
||
|
|
||
|
return {
|
||
|
"Open": open_price,
|
||
|
"High": high_price,
|
||
|
"Low": low_price,
|
||
|
"Close": close_price,
|
||
|
"RangePoints": range_points,
|
||
|
"RangePips": range_pips,
|
||
|
"Current": current_price,
|
||
|
"RelOpen": rel_open,
|
||
|
"RelHigh": rel_high,
|
||
|
"RelLow": rel_low,
|
||
|
"RelClose": rel_close
|
||
|
}
|
||
|
|
||
|
# ================================
|
||
|
# Main Function
|
||
|
# ================================
|
||
|
def main():
|
||
|
log.info("===== Yearly Data Script Started =====")
|
||
|
config = load_config()
|
||
|
|
||
|
# Connect to running MT5 (do not reinitialize)
|
||
|
if not mt5.initialize():
|
||
|
log.warning(f"MT5 not running or cannot connect: {mt5.last_error()}")
|
||
|
else:
|
||
|
log.info("Connected to running MT5 terminal")
|
||
|
|
||
|
symbols = ["XAUUSD", "USTEC", "US30"]
|
||
|
report_lines = ["Yearly Data Report\n"]
|
||
|
|
||
|
for sym in symbols:
|
||
|
log.info(f"Processing symbol: {sym}")
|
||
|
data = get_yearly_data(sym)
|
||
|
if data:
|
||
|
report_lines.append(f"--- {sym} ---")
|
||
|
report_lines.append(f"Yearly Open: {data['Open']}")
|
||
|
report_lines.append(f"Yearly High: {data['High']}")
|
||
|
report_lines.append(f"Yearly Low: {data['Low']}")
|
||
|
report_lines.append(f"Yearly Close: {data['Close']}")
|
||
|
report_lines.append(f"Range (Points): {data['RangePoints']}")
|
||
|
report_lines.append(f"Range (Pips): {data['RangePips']}")
|
||
|
report_lines.append(f"Current Price: {data['Current'] if data['Current'] is not None else 'N/A'}")
|
||
|
|
||
|
if data['Current'] is not None:
|
||
|
if data['RelOpen'] is not None:
|
||
|
report_lines.append(f"Relative to Open: {data['RelOpen']:.2f}")
|
||
|
if data['RelHigh'] is not None:
|
||
|
report_lines.append(f"Relative to High: {data['RelHigh']:.2f}")
|
||
|
if data['RelLow'] is not None:
|
||
|
report_lines.append(f"Relative to Low: {data['RelLow']:.2f}")
|
||
|
if data['RelClose'] is not None:
|
||
|
report_lines.append(f"Relative to Close: {data['RelClose']:.2f}")
|
||
|
else:
|
||
|
report_lines.append("Current price not available")
|
||
|
report_lines.append("\n")
|
||
|
else:
|
||
|
report_lines.append(f"{sym}: No data available\n")
|
||
|
|
||
|
# Send email
|
||
|
send_email(
|
||
|
subject="Yearly OHLC + Current Position Report",
|
||
|
body="\n".join(report_lines),
|
||
|
smtp_config=config["email"]
|
||
|
)
|
||
|
|
||
|
mt5.shutdown()
|
||
|
log.info("===== Yearly Data Script Finished =====")
|
||
|
|
||
|
# ================================
|
||
|
# Run Script
|
||
|
# ================================
|
||
|
if __name__ == "__main__":
|
||
|
main()
|