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

Popular posts from this blog