From 5d9ffd9b823ee163c7b24a536e6c64383c00844c Mon Sep 17 00:00:00 2001 From: Vittus Mikiassen Date: Thu, 18 Jun 2026 12:38:30 +0200 Subject: [PATCH] Update mt5-xau-lstm-ppo-stoch-adx-bot.py --- mt5-xau-lstm-ppo-stoch-adx-bot.py | 402 ++++++++++++++++++++++++++---- 1 file changed, 358 insertions(+), 44 deletions(-) diff --git a/mt5-xau-lstm-ppo-stoch-adx-bot.py b/mt5-xau-lstm-ppo-stoch-adx-bot.py index 321bc0b..42000bc 100644 --- a/mt5-xau-lstm-ppo-stoch-adx-bot.py +++ b/mt5-xau-lstm-ppo-stoch-adx-bot.py @@ -6,6 +6,7 @@ from io import StringIO import random from collections import deque from datetime import datetime, timedelta +import subprocess # import requests # import threading # from multiprocessing import Process @@ -144,6 +145,95 @@ def STOCH(df, period=14, smooth_d=3): def EMA(df, period): return df['Close'].ewm(span=period, adjust=False).mean().round(2) +def Indecision(df, threshold=0.2): + body = (df["Close"] - df["Open"]).abs() + candle_range = (df["High"] - df["Low"]).replace(0, 1e-9) + + return (body / candle_range < threshold).astype(int) + +def EQH(df, lookback=36, tolerance=1): + highs = df["High"] + + return highs.rolling(lookback).apply( + lambda x: int((abs(x[-1] - x[:-1]) <= tolerance).any()), + raw=True + ).fillna(0).astype(int) + +def EQL(df, lookback=36, tolerance=1): + lows = df["Low"] + + return lows.rolling(lookback).apply( + lambda x: int((abs(x[-1] - x[:-1]) <= tolerance).any()), + raw=True + ).fillna(0).astype(int) + +def RejectionBlock(df, wick_ratio=2.0): + body = (df["Close"] - df["Open"]).abs() + + upper = df["High"] - df[["Open","Close"]].max(axis=1) + lower = df[["Open","Close"]].min(axis=1) - df["Low"] + + return ((upper > body * wick_ratio) | + (lower > body * wick_ratio)).astype(int) + +def BullishOB(df, multiplier=1.5): + body = (df["Close"] - df["Open"]).abs() + next_body = body.shift(-1) + + bearish = df["Close"] < df["Open"] + next_bullish = df["Close"].shift(-1) > df["Open"].shift(-1) + + return (bearish & + next_bullish & + (next_body >= body * multiplier)).astype(int) + +def BearishOB(df, multiplier=1.5): + body = (df["Close"] - df["Open"]).abs() + next_body = body.shift(-1) + + bullish = df["Close"] > df["Open"] + next_bearish = df["Close"].shift(-1) < df["Open"].shift(-1) + + return (bullish & + next_bearish & + (next_body >= body * multiplier)).astype(int) + +def BullishFVG(df): + return (df["Low"].shift(-1) > df["High"].shift(1)).astype(int) + +def BearishFVG(df): + return (df["High"].shift(-1) < df["Low"].shift(1)).astype(int) + +def BullishMB(df, multiplier=1.5): + body = (df["Close"] - df["Open"]).abs() + + bearish = df["Close"] < df["Open"] + next_bullish = df["Close"].shift(-1) > df["Open"].shift(-1) + displacement = body.shift(-1) >= body * multiplier + + ob = bearish & next_bullish & displacement + + ob_high = df["High"].where(ob).ffill() + ob_low = df["Low"].where(ob).ffill() + + return ((df["Low"] <= ob_high) & + (df["High"] >= ob_low)).astype(int) + +def BearishMB(df, multiplier=1.5): + body = (df["Close"] - df["Open"]).abs() + + bullish = df["Close"] > df["Open"] + next_bearish = df["Close"].shift(-1) < df["Open"].shift(-1) + displacement = body.shift(-1) >= body * multiplier + + ob = bullish & next_bearish & displacement + + ob_high = df["High"].where(ob).ffill() + ob_low = df["Low"].where(ob).ffill() + + return ((df["Low"] <= ob_high) & + (df["High"] >= ob_low)).astype(int) + def add_indicators(df): df['adx'], df['+di'], df['-di'] = ADX(df) @@ -153,7 +243,23 @@ def add_indicators(df): df['EMA21'] = EMA(df, 21) df['EMA_DIFF'] = df['EMA7'] - df['EMA21'] - df = df[["Open", "High", "Low", "Close", "k", "k_smooth", "adx", "+di", "-di", "EMA7", "EMA21", "EMA_DIFF"]].copy() + df["indecision"] = Indecision(df) + df["rb"] = RejectionBlock(df) + + df["bullish_ob"] = BullishOB(df) + df["bearish_ob"] = BearishOB(df) + + df["bullish_fvg"] = BullishFVG(df) + df["bearish_fvg"] = BearishFVG(df) + + df["eqh"] = EQH(df) + df["eql"] = EQL(df) + + df["bearish_mb"] = BearishMB(df) + df["bullish_mb"] = BullishMB(df) + + df = df[["Open", "High", "Low", "Close", "k", "k_smooth", "adx", "+di", "-di", "EMA7", "EMA21", "EMA_DIFF", + "indecision", "rb", "bullish_ob", "bearish_ob", "bullish_fvg", "bearish_fvg", "eqh", "eql", "bearish_mb", "bullish_mb"]].copy() # df = df[["Open", "High", "Low", "Close", "EMA_crossover", "macd_zone", "macd_line", "macd_signal", "macd_line_diff", "macd_signal_diff", "macd_line_slope", "macd_signal_line_slope" , "macd_osma", "macd_crossover", "bb_sma", "bb_upper", "bb_lower", "RSI_zone", "ADX_zone", "+DI_val", "-DI_val", "ATR", "order_block_type"]].copy() df.dropna(inplace=True) @@ -498,21 +604,17 @@ class LSTMPPOAgent: if not os.path.exists("LSTM-PPO-saves"): return - files = sorted( - [ - f for f in os.listdir("LSTM-PPO-saves") - if f.endswith(".checkpoint.pt") - and symbol in f - ] - ) + files = [ + os.path.join("LSTM-PPO-saves", f) + for f in os.listdir("LSTM-PPO-saves") + if f.endswith(".checkpoint.pt") + and symbol in f + ] if not files: return - latest = os.path.join( - "LSTM-PPO-saves", - files[-1] - ) + latest = max(files, key=os.path.getmtime) checkpoint = torch.load( latest, @@ -528,6 +630,8 @@ class LSTMPPOAgent: checkpoint["optimizer"] ) + print(f"Loaded checkpoint: {latest}") + class WinRateKNN: def __init__(self, symbol, k=10): self.k = k @@ -727,7 +831,7 @@ def train_bot(symbol="XAUUSD"): df = load_last_mb_xauusd() df = add_indicators(df) - SEQ_LEN = 12 * 3 + SEQ_LEN = 12 * 8 FEATURES = [ "Open", @@ -741,7 +845,17 @@ def train_bot(symbol="XAUUSD"): "-di", "EMA7", "EMA21", - "EMA_DIFF" + "EMA_DIFF", + "indecision", + "rb", + "bullish_ob", + "bearish_ob", + "bullish_fvg", + "bearish_fvg", + "eqh", + "eql", + "bearish_mb", + "bullish_mb" ] agent = LSTMPPOAgent( @@ -752,12 +866,14 @@ def train_bot(symbol="XAUUSD"): # knn = WinRateKNN(symbol) + """ try: agent.loadcheckpoint(symbol) # knn.load() print(f"[{symbol}] Loaded checkpoint") except: print(f"[{symbol}] Starting fresh") + """ save_counter = 0 @@ -821,7 +937,7 @@ def train_bot(symbol="XAUUSD"): low = current["Low"] # SL_PIPS = round(current_price * 0.00125 * 10, 0) SL_PIPS = 50 - TP1_PIPS = round(SL_PIPS * 0.2, 0) + TP1_PIPS = round(SL_PIPS * 0.26, 0) # TP2_PIPS = round(SL_PIPS * 2, 0) # TP3_PIPS = round(SL_PIPS * 3, 0) # TP4_PIPS = round(SL_PIPS * 4, 0) @@ -862,8 +978,8 @@ def train_bot(symbol="XAUUSD"): # OPEN LONG # ============================================================== - if action == 1 and not in_position and df["+di"].iloc[i] > df["-di"].iloc[i] and df["EMA_DIFF"].iloc[i] > 0 and df["k"].iloc[i] < 80: - + # if action == 1 and not in_position and df["+di"].iloc[i] > df["-di"].iloc[i] and df["EMA_DIFF"].iloc[i] > 0 and df["k"].iloc[i] < 80: + if action == 1 and not in_position: in_position = True position_type = "long" @@ -903,8 +1019,8 @@ def train_bot(symbol="XAUUSD"): # OPEN SHORT # ============================================================== - elif action == 2 and not in_position and df["-di"].iloc[i] > df["+di"].iloc[i] and df["EMA_DIFF"].iloc[i] < 0 and df["k"].iloc[i] > 20: - + # elif action == 2 and not in_position and df["-di"].iloc[i] > df["+di"].iloc[i] and df["EMA_DIFF"].iloc[i] < 0 and df["k"].iloc[i] > 20: + elif action == 2 and not in_position: in_position = True position_type = "short" @@ -1198,14 +1314,7 @@ def train_bot(symbol="XAUUSD"): if save_counter % 1440 == 0: # if len(agent.trajectory) >= 512: - print( - f"[{symbol}] " - f"[INFO] Training PPO on step " - f"{save_counter}..." - ) - agent.train() - agent.savecheckpoint(symbol) # knn._fit() # knn.save() @@ -1267,6 +1376,14 @@ def train_bot(symbol="XAUUSD"): trade_returns = [] + print( + f"[{symbol}] " + f"[INFO] Training PPO on step " + f"{save_counter}..." + ) + agent.train() + agent.savecheckpoint(symbol) + # ============================================================== # FINAL TRAINING # ============================================================== @@ -1291,7 +1408,7 @@ def open_long(symbol, lot_size): sl = entry - 5 - tp1 = entry + 1.12 + tp1 = entry + 1.3 # tp2 = entry + 10 # tp3 = entry + 15 # tp4 = entry + 20 @@ -1327,7 +1444,7 @@ def open_short(symbol, lot_size): sl = entry + 5 - tp1 = entry - 1.12 + tp1 = entry - 1.3 # tp2 = entry - 10 # tp3 = entry - 15 # tp4 = entry - 20 @@ -1438,7 +1555,7 @@ def manage_positions(symbol, SL_MOVE_BUFFER): move_all_stops(symbol, pos.tp) def test_bot(symbol="XAUUSD"): - SEQ_LEN = 12 * 3 + SEQ_LEN = 12 * 8 mt5.initialize() account = mt5.account_info() @@ -1447,7 +1564,7 @@ def test_bot(symbol="XAUUSD"): # print(mt5.last_error()) # return balance = account.balance - RISK = 0.02 + RISK = 0.005 # risk_per_position = max(balance * RISK / 500 / 4, 0.01) # tick = mt5.symbol_info_tick(symbol) @@ -1472,7 +1589,17 @@ def test_bot(symbol="XAUUSD"): "-di", "EMA7", "EMA21", - "EMA_DIFF" + "EMA_DIFF", + "indecision", + "rb", + "bullish_ob", + "bearish_ob", + "bullish_fvg", + "bearish_fvg", + "eqh", + "eql", + "bearish_mb", + "bullish_mb" ] # last_m15 = None @@ -1535,15 +1662,16 @@ def test_bot(symbol="XAUUSD"): now = datetime.now() seconds_until_next_5m = ( - (5 - now.minte % 5) * 60 + (5 - now.minute % 5) * 60 - now.second - now.microsecond / 1_000_000 ) - + # print(f"sleeping {seconds_until_next_5m:.0f} seconds, current time: {datetime.now()}") if seconds_until_next_5m <= 0: seconds_until_next_5m += 300 time.sleep(seconds_until_next_5m) + # print(f"slept {seconds_until_next_5m:.0f} seconds, current time: {datetime.now()}") tick = mt5.symbol_info_tick(symbol) # SL_PIPS = round(tick.bid * 0.00125 * 10, 0) @@ -1675,9 +1803,14 @@ def test_bot(symbol="XAUUSD"): action, _, _ = agent.select_action( state_seq, open_pos > 0, - training=False + training=True ) + if df["adx"].iloc[-1] < 20: + action = 0 + + # print(f"action: {action}") + # ================================================== # OPEN NEW TRADE # ================================================== @@ -1693,22 +1826,22 @@ def test_bot(symbol="XAUUSD"): # 0.01 # ) - if action == 1 and df["adx"].iloc[-1] > 20 and df["+di"].iloc[-1] > df["-di"].iloc[-1] and df["EMA_DIFF"].iloc[-1] > 0 and df["k"].iloc[-1] < 80: - - # print( - # f"[{symbol}] PPO BUY" - # ) + # if action == 1 and df["adx"].iloc[-1] > 20 and df["+di"].iloc[-1] > df["-di"].iloc[-1] and df["EMA_DIFF"].iloc[-1] > 0 and df["k"].iloc[-1] < 80: + if action == 1: + print( + f"[{symbol}] PPO BUY" + ) open_long( symbol, risk_per_position ) - elif action == 2 and df["adx"].iloc[-1] > 20 and df["-di"].iloc[-1] > df["+di"].iloc[-1] and df["EMA_DIFF"].iloc[-1] < 0 and df["k"].iloc[-1] > 20: - - # print( - # f"[{symbol}] PPO SELL" - # ) + # elif action == 2 and df["adx"].iloc[-1] > 20 and df["-di"].iloc[-1] > df["+di"].iloc[-1] and df["EMA_DIFF"].iloc[-1] < 0 and df["k"].iloc[-1] > 20: + elif action == 2: + print( + f"[{symbol}] PPO SELL" + ) open_short( symbol, @@ -1721,7 +1854,188 @@ def test_bot(symbol="XAUUSD"): # f"[{symbol}] PPO HOLD" # ) +CSV_FILE = "XAU_5m_data.csv" + +def get_last_date(): + + if not os.path.exists(CSV_FILE): + return None + + df = pd.read_csv( + CSV_FILE, + sep=";" + ) + + if df.empty: + return None + + return pd.to_datetime( + df["Date"].iloc[-1] + ) + +def download_xauusd_data(): + + last_date = get_last_date() + + if ( + last_date is not None + and ( + datetime.now().date() + - last_date.date() + ).days <= 90 + ): + + print( + "Data already up to date." + ) + + return None + + if last_date is None: + + start_date = ( + datetime.now() + - timedelta(days=365 * 5) + ).strftime( + "%Y-%m-%d" + ) + + else: + + start_date = ( + last_date + - timedelta(days=1) + ).strftime( + "%Y-%m-%d" + ) + + end_date = ( + datetime.now() + - timedelta(days=1) + ).strftime( + "%Y-%m-%d" + ) + + print( + f"Downloading " + f"{start_date} -> {end_date}" + ) + + subprocess.run( + [ + # "npx", + "dukascopy-node", + "-i", + "xauusd", + "-from", + start_date, + "-to", + end_date, + "-t", + "m5", + "-f", + "csv" + ], + check=True + ) + + files = [ + f + for f in os.listdir(".") + if f.startswith("xauusd") + and f.endswith(".csv") + ] + + if not files: + + raise FileNotFoundError( + "No Dukascopy CSV was downloaded." + ) + + return max( + files, + key=os.path.getmtime + ) + +def append_xauusd_data(downloaded_file): + + if downloaded_file is None: + return + + new_df = pd.read_csv( + downloaded_file + ) + + new_df.rename( + columns={ + "timestamp": "Date", + "open": "Open", + "high": "High", + "low": "Low", + "close": "Close", + "volume": "Volume" + }, + inplace=True + ) + + if os.path.exists(CSV_FILE): + + old_df = pd.read_csv( + CSV_FILE, + sep=";" + ) + + df = pd.concat( + [ + old_df, + new_df + ], + ignore_index=True + ) + + else: + + df = new_df + + df.drop_duplicates( + subset=["Date"], + keep="last", + inplace=True + ) + + df.sort_values( + "Date", + inplace=True + ) + + df.to_csv( + CSV_FILE, + sep=";", + index=False + ) + + os.remove( + downloaded_file + ) + + print( + f"Saved " + f"{len(df)} candles " + f"to {CSV_FILE}" + ) + +def update_xauusd_data(): + + downloaded_file = ( + download_xauusd_data() + ) + + append_xauusd_data( + downloaded_file + ) + def main(): + # update_xauusd_data() train_bot("XAUUSD") # test_bot(symbol="XAUUSD-VIP")