Ddxx
# ============================================
# MY QUANT BOT - Phase 8 (Final Fixed)
# What's new:
# 1. Uses FairEconomy free news API
# 2. Detects HIGH impact news for our pairs
# 3. Pauses trading 30 mins before news
# 4. Resumes 15 mins after news passes
# 5. Everything from Phase 6 still works
# ============================================
import os
import time
import numpy as np
import MetaTrader5 as mt5
import pandas as pd
import requests
from dotenv import load_dotenv
from sklearn.ensemble import RandomForestRegressor, RandomForestClassifier
from sklearn.preprocessing import StandardScaler
from datetime import datetime, timezone, timedelta
# --- Load credentials ---
load_dotenv()
LOGIN = int(os.getenv("MT5_LOGIN"))
PASSWORD = os.getenv("MT5_PASSWORD")
SERVER = os.getenv("MT5_SERVER")
# --- Settings ---
SYMBOL = "EURUSDm"
TIMEFRAME = mt5.TIMEFRAME_M15
RSI_PERIOD = 14
RSI_BUY = 30
RSI_SELL = 70
MACD_FAST = 12
MACD_SLOW = 26
MACD_SIGNAL = 9
ATR_PERIOD = 14
# --- News Filter Settings ---
NEWS_PAUSE_BEFORE = 30
NEWS_PAUSE_AFTER = 15
NEWS_CURRENCIES = ["USD", "EUR", "GBP", "XAU"]
# --- Risk Profiles ---
RISK_PROFILES = {
"LOW": {
"lot_size": 0.01,
"sl_mult": 1.0,
"tp_mult": 1.5,
"min_rsi_gap": 5,
},
"BALANCED": {
"lot_size": 0.01,
"sl_mult": 1.5,
"tp_mult": 3.0,
"min_rsi_gap": 0,
},
}
# --- File paths ---
TRADES_FILE = "trades.csv"
SUMMARY_FILE = "summary.txt"
MODEL_LOG = "model_log.txt"
NEWS_LOG = "news_log.txt"
# ============================================
# NEWS FILTER
# ============================================
def fetch_news_events():
try:
now = datetime.now()
date_str = now.strftime("%Y-%m-%d")
url = "https://cdn-nfs.faireconomy.media/ff_calendar_thisweek.json"
headers = {"User-Agent": "Mozilla/5.0"}
response = requests.get(url, headers=headers, timeout=10)
if response.status_code != 200:
print(f" ⚠️ News fetch failed: {response.status_code}")
return []
data = response.json()
events = []
for item in data:
impact = item.get("impact", "").lower()
currency = item.get("country", "").upper()
date = item.get("date", "")
if impact != "high":
continue
if date_str not in date:
continue
if currency not in NEWS_CURRENCIES:
continue
events.append({
"currency": currency,
"event": item.get("title", "Unknown"),
"time": item.get("date", ""),
"date": date_str,
})
print(f" š° Found {len(events)} high impact events today")
return events
except Exception as e:
print(f" ⚠️ News fetch error: {e}")
return []
def parse_news_time(time_str, date_str):
try:
if not time_str:
return None
dt = datetime.fromisoformat(time_str)
return dt.replace(tzinfo=None)
except Exception:
return None
def is_news_time(events):
now = datetime.now()
for event in events:
event_dt = parse_news_time(event["time"], event["date"])
if not event_dt:
continue
mins_until = (event_dt - now).total_seconds() / 60
mins_since = (now - event_dt).total_seconds() / 60
if 0 <= mins_until <= NEWS_PAUSE_BEFORE:
reason = (f"NEWS in {mins_until:.0f} mins: "
f"{event['currency']} {event['event']}")
return True, reason
if 0 <= mins_since <= NEWS_PAUSE_AFTER:
reason = (f"Post-NEWS pause: "
f"{event['currency']} {event['event']} "
f"({mins_since:.0f} mins ago)")
return True, reason
return False, ""
def log_news_pause(reason):
with open(NEWS_LOG, "a") as f:
f.write(f"{datetime.now()} - PAUSED: {reason}\n")
# ============================================
# INDICATORS
# ============================================
def calculate_rsi(df, period=14):
delta = df["close"].diff()
gain = delta.where(delta > 0, 0.0)
loss = -delta.where(delta < 0, 0.0)
avg_gain = gain.rolling(window=period).mean()
avg_loss = loss.rolling(window=period).mean()
rs = avg_gain / avg_loss
return 100 - (100 / (1 + rs))
def calculate_macd(df, fast=12, slow=26, signal=9):
ema_fast = df["close"].ewm(span=fast, adjust=False).mean()
ema_slow = df["close"].ewm(span=slow, adjust=False).mean()
macd_line = ema_fast - ema_slow
signal_line = macd_line.ewm(span=signal, adjust=False).mean()
return macd_line, signal_line, macd_line - signal_line
def calculate_atr(df, period=14):
df = df.copy()
df["h-l"] = df["high"] - df["low"]
df["h-pc"] = abs(df["high"] - df["close"].shift(1))
df["l-pc"] = abs(df["low"] - df["close"].shift(1))
df["tr"] = df[["h-l", "h-pc", "l-pc"]].max(axis=1)
return df["tr"].rolling(window=period).mean()
def calculate_volatility(df, period=20):
return df["close"].pct_change().rolling(window=period).std()
def calculate_trend_strength(df, period=20):
sma = df["close"].rolling(window=period).mean()
return (df["close"] - sma) / sma * 100
# ============================================
# MARKET FEATURES
# ============================================
def detect_market_features(df):
latest = df.iloc[-1]
recent = df.iloc[-20:]
volatility = df["volatility"].iloc[-1]
trend_strength = abs(df["trend_strength"].iloc[-1])
atr = latest["atr"]
rsi = latest["rsi"]
macd = latest["macd"]
macd_signal = latest["macd_signal"]
avg_body = (abs(recent["close"] - recent["open"])).mean()
momentum = df["close"].iloc[-1] - df["close"].iloc[-5]
hl_range = latest["high"] - latest["low"]
return np.array([[
volatility, trend_strength, atr, rsi,
macd, macd_signal, avg_body, momentum, hl_range,
]])
# ============================================
# BUILD TRAINING DATA
# ============================================
def build_training_data(df):
records = []
for i in range(50, len(df) - 20):
row = df.iloc[i]
future = df.iloc[i+1: i+20]
if pd.isna(row["rsi"]):
continue
max_up = future["high"].max() - row["close"]
max_down = row["close"] - future["low"].min()
vol = row["volatility"] if not pd.isna(row["volatility"]) else 0
trend = abs(row["trend_strength"]) if not pd.isna(row["trend_strength"]) else 0
best_mode = 0 if (vol < df["volatility"].quantile(0.4) and trend < 0.1) else 1
records.append({
"volatility": vol,
"trend_strength": trend,
"atr": row["atr"],
"rsi": row["rsi"],
"macd": row["macd"],
"macd_signal": row["macd_signal"],
"avg_body": abs(row["close"] - row["open"]),
"momentum": df["close"].iloc[i] - df["close"].iloc[i-5],
"hl_range": row["high"] - row["low"],
"best_tp": max_up,
"best_sl": max_down,
"best_mode": best_mode,
})
return pd.DataFrame(records)
# ============================================
# TRAIN ML MODELS
# ============================================
def train_all_models(df):
print(" š§ Training all ML models...")
df = df.copy()
df["volatility"] = calculate_volatility(df)
df["trend_strength"] = calculate_trend_strength(df)
training_data = build_training_data(df)
if len(training_data) < 30:
print(" ⚠️ Not enough data yet")
return None, None, None, None, None
feature_cols = [
"volatility", "trend_strength", "atr", "rsi",
"macd", "macd_signal", "avg_body", "momentum", "hl_range"
]
X = training_data[feature_cols].values
y_tp = training_data["best_tp"].values
y_sl = training_data["best_sl"].values
y_mode = training_data["best_mode"].values
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
model_tp = RandomForestRegressor(n_estimators=100, random_state=42)
model_sl = RandomForestRegressor(n_estimators=100, random_state=42)
model_mode = RandomForestClassifier(n_estimators=100, random_state=42)
model_tp.fit(X_scaled, y_tp)
model_sl.fit(X_scaled, y_sl)
model_mode.fit(X_scaled, y_mode)
with open(MODEL_LOG, "a") as f:
f.write(f"{datetime.now()} - Trained on {len(training_data)} candles\n")
print(f" ✅ All models trained on {len(training_data)} candles!")
return model_tp, model_sl, model_mode, scaler, feature_cols
# ============================================
# PREDICT
# ============================================
def predict_risk_mode(model_mode, scaler, features):
features_scaled = scaler.transform(features)
mode_pred = model_mode.predict(features_scaled)[0]
proba = model_mode.predict_proba(features_scaled)[0]
confidence = max(proba) * 100
return ("BALANCED" if mode_pred == 1 else "LOW"), confidence
def predict_sl_tp(model_tp, model_sl, scaler, features,
price, direction, risk_mode):
features_scaled = scaler.transform(features)
profile = RISK_PROFILES[risk_mode]
pred_tp = model_tp.predict(features_scaled)[0] * profile["tp_mult"]
pred_sl = model_sl.predict(features_scaled)[0] * profile["sl_mult"]
min_dist = features[0][2] * 0.5
pred_tp = max(pred_tp, min_dist)
pred_sl = max(pred_sl, min_dist)
if direction == "BUY":
return round(price - pred_sl, 5), round(price + pred_tp, 5)
else:
return round(price + pred_sl, 5), round(price - pred_tp, 5)
# ============================================
# TRADE LOGGING
# ============================================
def log_trade(direction, price, sl, tp, rsi, macd, risk_mode, confidence):
new_row = pd.DataFrame([{
"time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"direction": direction,
"price": price,
"sl": sl,
"tp": tp,
"rsi": round(rsi, 2),
"macd": round(macd, 6),
"risk_mode": risk_mode,
"confidence": round(confidence, 1),
"result": "OPEN",
"pnl": 0.0,
}])
file_exists = os.path.exists(TRADES_FILE)
new_row.to_csv(TRADES_FILE, mode="a", header=not file_exists, index=False)
print(f" š Logged → Mode: {risk_mode} | Confidence: {confidence:.1f}%")
def update_closed_trades():
if not os.path.exists(TRADES_FILE):
return
df = pd.read_csv(TRADES_FILE)
if df.empty:
return
deals = mt5.history_deals_get(
datetime(2020, 1, 1, tzinfo=timezone.utc),
datetime.now(timezone.utc)
)
if deals is None or len(deals) == 0:
return
deals_df = pd.DataFrame(list(deals), columns=deals[0]._asdict().keys())
deals_df = deals_df[deals_df["symbol"] == SYMBOL]
deals_df = deals_df[deals_df["entry"] == 1]
for idx, row in df[df["result"] == "OPEN"].iterrows():
matched = deals_df[deals_df["comment"].str.contains("ML RSI", na=False)]
if not matched.empty:
last = matched.iloc[-1]
pnl = last["profit"]
df.at[idx, "result"] = "WIN" if pnl > 0 else "LOSS"
df.at[idx, "pnl"] = pnl
df.to_csv(TRADES_FILE, index=False)
def show_stats():
if not os.path.exists(TRADES_FILE):
return
df = pd.read_csv(TRADES_FILE)
if df.empty:
return
closed = df[df["result"] != "OPEN"]
total = len(closed)
wins = len(closed[closed["result"] == "WIN"])
losses = len(closed[closed["result"] == "LOSS"])
pnl = closed["pnl"].sum()
winrate = (wins / total * 100) if total > 0 else 0
print(f"\n{'='*50}")
print(f" š PERFORMANCE SUMMARY")
print(f"{'='*50}")
print(f" Total Trades : {total}")
print(f" Wins : {wins} ✅")
print(f" Losses : {losses} ❌")
print(f" Win Rate : {winrate:.1f}%")
print(f" Total P&L : {pnl:.2f} USD")
for mode in ["LOW", "BALANCED"]:
m = closed[closed["risk_mode"] == mode]
m_wins = len(m[m["result"] == "WIN"])
m_total = len(m)
m_wr = (m_wins / m_total * 100) if m_total > 0 else 0
print(f" {mode} Mode : {m_total} trades | {m_wr:.1f}% win rate")
print(f"{'='*50}\n")
with open(SUMMARY_FILE, "w") as f:
f.write(f"BOT PERFORMANCE SUMMARY\n")
f.write(f"Generated : {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
f.write(f"{'='*40}\n")
f.write(f"Total Trades : {total}\n")
f.write(f"Wins : {wins}\n")
f.write(f"Losses : {losses}\n")
f.write(f"Win Rate : {winrate:.1f}%\n")
f.write(f"Total P&L : {pnl:.2f} USD\n")
# ============================================
# PLACE TRADE
# ============================================
def has_open_trade():
positions = mt5.positions_get(symbol=SYMBOL)
return len(positions) > 0
def place_trade(order_type, sl, tp, rsi, macd, risk_mode, confidence):
tick = mt5.symbol_info_tick(SYMBOL)
price = tick.ask if order_type == "BUY" else tick.bid
order = mt5.ORDER_TYPE_BUY if order_type == "BUY" else mt5.ORDER_TYPE_SELL
lot = RISK_PROFILES[risk_mode]["lot_size"]
request = {
"action": mt5.TRADE_ACTION_DEAL,
"symbol": SYMBOL,
"volume": lot,
"type": order,
"price": price,
"sl": sl,
"tp": tp,
"deviation": 10,
"magic": 123456,
"comment": "ML RSI+MACD Bot",
"type_time": mt5.ORDER_TIME_GTC,
"type_filling": mt5.ORDER_FILLING_FOK,
}
result = mt5.order_send(request)
if result.retcode == mt5.TRADE_RETCODE_DONE:
print(f" ✅ {order_type} placed! [{risk_mode} mode]")
print(f" š Price : {price:.5f}")
print(f" š”️ SL : {sl}")
print(f" šÆ TP : {tp}")
log_trade(order_type, price, sl, tp, rsi, macd, risk_mode, confidence)
else:
print(f" ❌ Failed! {result.retcode} - {result.comment}")
# ============================================
# FETCH & PREPARE DATA
# ============================================
def fetch_and_prepare():
rates = mt5.copy_rates_from_pos(SYMBOL, TIMEFRAME, 0, 500)
df = pd.DataFrame(rates)
df["time"] = pd.to_datetime(df["time"], unit="s")
df["rsi"] = calculate_rsi(df, RSI_PERIOD)
df["macd"], df["macd_signal"], df["macd_hist"] = calculate_macd(
df, MACD_FAST, MACD_SLOW, MACD_SIGNAL)
df["atr"] = calculate_atr(df, ATR_PERIOD)
df["volatility"] = calculate_volatility(df)
df["trend_strength"] = calculate_trend_strength(df)
return df
# ============================================
# MAIN BOT LOOP
# ============================================
def run_bot():
if not mt5.initialize(login=LOGIN, password=PASSWORD, server=SERVER):
print(f"❌ Failed to connect: {mt5.last_error()}")
return
print("=" * 50)
print(" MY QUANT BOT - PHASE 8 RUNNING...")
print(f" Symbol : {SYMBOL}")
print(f" Strategy : RSI + MACD + ML + News Filter")
print(f" Risk : ML decides automatically")
print("=" * 50)
print("\nš Fetching data and training models...")
df = fetch_and_prepare()
model_tp, model_sl, model_mode, scaler, _ = train_all_models(df)
print("\nš° Fetching economic calendar...")
news_events = fetch_news_events()
last_news_fetch = datetime.now()
last_retrain_day = datetime.now().day
cycle_counter = 0
while True:
try:
# Refresh news every 30 minutes
mins_since_news = (datetime.now() - last_news_fetch).seconds / 60
if mins_since_news >= 30:
print("\nš° Refreshing news calendar...")
news_events = fetch_news_events()
last_news_fetch = datetime.now()
# Check news danger
dangerous, reason = is_news_time(news_events)
if dangerous:
print(f"\nš« TRADING PAUSED — {reason}")
log_news_pause(reason)
print(" š¤ Sleeping 60 seconds...")
time.sleep(60)
continue
# Fetch market data
df = fetch_and_prepare()
latest = df.iloc[-1]
prev = df.iloc[-2]
rsi = latest["rsi"]
macd_now = latest["macd"]
signal_now = latest["macd_signal"]
macd_prev = prev["macd"]
sig_prev = prev["macd_signal"]
price = latest["close"]
time_now = latest["time"]
macd_up = (macd_prev < sig_prev) and (macd_now > signal_now)
macd_down = (macd_prev > sig_prev) and (macd_now < signal_now)
market_features = detect_market_features(df)
risk_mode, confidence = predict_risk_mode(
model_mode, scaler, market_features)
cycle_counter += 1
if cycle_counter % 10 == 0:
update_closed_trades()
show_stats()
print(f"\n⏰ {time_now}")
print(f" Price : {price:.5f}")
print(f" RSI : {rsi:.2f}")
print(f" MACD : {macd_now:.6f} | Signal: {signal_now:.6f}")
print(f" ATR : {latest['atr']:.6f}")
print(f" Risk Mode : {risk_mode} ({confidence:.1f}% confident)")
print(f" News : ✅ Clear to trade")
if has_open_trade():
print(" ⏳ Open trade exists, waiting...")
elif model_tp is None:
print(" ⚠️ Models not ready yet...")
else:
profile = RISK_PROFILES[risk_mode]
rsi_gap = profile["min_rsi_gap"]
buy_signal = rsi < (RSI_BUY - rsi_gap) and macd_up
sell_signal = rsi > (RSI_SELL + rsi_gap) and macd_down
if buy_signal:
sl, tp = predict_sl_tp(model_tp, model_sl, scaler,
market_features, price, "BUY", risk_mode)
print(f" š¢ BUY! RSI={rsi:.2f} | {risk_mode} mode")
print(f" š§ ML → SL: {sl} | TP: {tp}")
place_trade("BUY", sl, tp, rsi,
macd_now, risk_mode, confidence)
elif sell_signal:
sl, tp = predict_sl_tp(model_tp, model_sl, scaler,
market_features, price, "SELL", risk_mode)
print(f" š“ SELL! RSI={rsi:.2f} | {risk_mode} mode")
print(f" š§ ML → SL: {sl} | TP: {tp}")
place_trade("SELL", sl, tp, rsi,
macd_now, risk_mode, confidence)
else:
print(f" ⏸️ No signal, waiting...")
# Daily retrain
current_day = datetime.now().day
if current_day != last_retrain_day:
print("\nš Daily retrain starting...")
df = fetch_and_prepare()
model_tp, model_sl, model_mode, scaler, _ = train_all_models(df)
last_retrain_day = current_day
print(" š¤ Sleeping 60 seconds...")
time.sleep(60)
except KeyboardInterrupt:
print("\nš Bot stopped")
show_stats()
break
except Exception as e:
print(f"❌ Error: {e}")
time.sleep(10)
mt5.shutdown()
print("✅ Disconnected cleanly")
# --- Start ---
run_bot()
Comments
Post a Comment