Update mt5-xau-lstm-ppo-stoch-adx-bot.py

This commit is contained in:
Vittus Mikiassen
2026-06-18 12:38:30 +02:00
committed by GitHub
parent 2dd52e8063
commit 5d9ffd9b82
+358 -44
View File
@@ -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")