feat: Organize notebooks and add stationarity checks

This commit is contained in:
Mohammad Aghdam
2025-10-02 00:00:16 +02:00
parent 0a304ba1de
commit 6939daaeb4
43 changed files with 5462246 additions and 98992 deletions
@@ -0,0 +1,451 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Failed to initialize MetaTrader 5\n",
"Loaded pipeline from models/saved_models/best_rf_db_pipeline.pkl\n",
"Checking market status...\n",
"Market is closed. No actions performed.\n"
]
}
],
"source": [
"# LIVE TRADING CODE FOR DOUBLE-BARRIER CLASSIFICATION\n",
"\n",
"import sys\n",
"import os\n",
"import warnings\n",
"from pathlib import Path\n",
"\n",
"# ---------------------------------------------------------------------------\n",
"# 1) SET PROJECT ROOT AND UPDATE PATH/WORKING DIRECTORY\n",
"# ---------------------------------------------------------------------------\n",
"project_root = Path.cwd().parent.parent # Adjust if your notebook is in notebooks/time_series\n",
"sys.path.append(str(project_root))\n",
"os.chdir(str(project_root))\n",
"warnings.filterwarnings(\"ignore\")\n",
"\n",
"import MetaTrader5 as mt5\n",
"import pandas as pd\n",
"import numpy as np\n",
"import ta\n",
"from datetime import datetime, timedelta\n",
"import time\n",
"import logging\n",
"import joblib\n",
"\n",
"# Setup logging\n",
"logging.basicConfig(\n",
" filename='models/saved_models/trading_app_db.log',\n",
" level=logging.INFO,\n",
" format='%(asctime)s %(levelname)s:%(message)s',\n",
" datefmt='%Y-%m-%d %H:%M:%S'\n",
")\n",
"\n",
"def log_and_print(message, is_error=False):\n",
" \"\"\"\n",
" Logs and prints a message.\n",
" If is_error=True, logs at the ERROR level; otherwise logs at INFO level.\n",
" \"\"\"\n",
" if is_error:\n",
" logging.error(message)\n",
" else:\n",
" logging.info(message)\n",
" print(message)\n",
"\n",
"# Update the login credentials and server information accordingly\n",
"name = 66677507\n",
"key = 'ST746$nG38'\n",
"serv = 'ICMarketsSC-Demo'\n",
"\n",
"# Global variables\n",
"SYMBOL = \"EURUSD\"\n",
"LOT_SIZE = 0.01\n",
"TIMEFRAME = mt5.TIMEFRAME_D1\n",
"N_BARS = 50000\n",
"MAGIC_NUMBER = 234003\n",
"SLEEP_TIME = 86400 # e.g. 24 hours\n",
"COMMENT_ML = \"DoubleBarrier-ML\"\n",
"\n",
"class TradingApp:\n",
" def __init__(self, symbol, lot_size, magic_number):\n",
" self.symbol = symbol\n",
" self.lot_size = lot_size\n",
" self.magic_number = magic_number\n",
" self.pipeline = None # We'll store the loaded classification pipeline here\n",
" self.last_retrain_time = None\n",
"\n",
" def get_data(self, symbol, n, timeframe):\n",
" \"\"\"\n",
" Fetch the last 'n' bars from MetaTrader 5 for the given timeframe.\n",
" \"\"\"\n",
" rates = mt5.copy_rates_from_pos(symbol, timeframe, 0, n)\n",
" rates_frame = pd.DataFrame(rates)\n",
" rates_frame['time'] = pd.to_datetime(rates_frame['time'], unit='s')\n",
" rates_frame.set_index('time', inplace=True)\n",
" return rates_frame\n",
"\n",
" def add_all_ta_features(self, df):\n",
" \"\"\"\n",
" Add technical analysis features to the DataFrame using 'ta' library.\n",
" \"\"\"\n",
" df = ta.add_all_ta_features(\n",
" df, open=\"open\", high=\"high\", low=\"low\", close=\"close\", volume=\"tick_volume\", fillna=True\n",
" )\n",
" return df\n",
"\n",
" def load_pipeline(self, pipeline_path):\n",
" \"\"\"\n",
" Loads a pre-trained classification pipeline (e.g., final_production_pipeline.pkl)\n",
" that was trained on SHIFTED double-barrier labels in {0,1,2}.\n",
" \"\"\"\n",
" self.pipeline = joblib.load(pipeline_path)\n",
" logging.info(f\"Loaded pipeline from {pipeline_path}\")\n",
" log_and_print(f\"Loaded pipeline from {pipeline_path}\")\n",
"\n",
" def ml_signal_generation(self, symbol, n_bars, timeframe):\n",
" \"\"\"\n",
" Generate buy/sell signals using the loaded classification pipeline.\n",
" The pipeline outputs SHIFTED labels in {0,1,2} => SHIFT them back to {-1,0,+1}.\n",
" We'll interpret +1 => buy, -1 => sell, 0 => no trade.\n",
"\n",
" Double-Barrier labeling was used offline to train this pipeline,\n",
" so we just replicate the same feature engineering steps and let the model predict.\n",
" \"\"\"\n",
" if self.pipeline is None:\n",
" logging.error(\"No pipeline loaded. Call load_pipeline(...) first.\")\n",
" return False, False, True, True\n",
"\n",
" # 1) Fetch new data\n",
" df = self.get_data(symbol, n_bars, timeframe)\n",
" # 2) Add TA features\n",
" df = self.add_all_ta_features(df)\n",
" df.fillna(method='ffill', inplace=True)\n",
"\n",
" # 3) Prepare the features\n",
" X_new = df\n",
"\n",
" # 4) Predict SHIFTED classes\n",
" preds_shifted = self.pipeline.predict(X_new)\n",
" # SHIFT them back: 0->-1, 1->0, 2->+1\n",
" preds = preds_shifted - 1\n",
"\n",
" # Get the latest predicted class\n",
" latest_pred = preds[-1]\n",
" # If latest_pred == +1 => buy signal\n",
" # If latest_pred == -1 => sell signal\n",
" # If 0 => no trade\n",
" buy_signal = (latest_pred == 1)\n",
" sell_signal = (latest_pred == -1)\n",
"\n",
" return buy_signal, sell_signal, not buy_signal, not sell_signal\n",
"\n",
" def orders(self, symbol, lot, is_buy=True, id_position=None, sl=None, tp=None):\n",
" \"\"\"\n",
" Send an order (buy/sell) to MetaTrader 5.\n",
" \"\"\"\n",
" symbol_info = mt5.symbol_info(symbol)\n",
" if symbol_info is None:\n",
" log_and_print(f\"Symbol {symbol} not found, can't place order.\", is_error=True)\n",
" return \"Symbol not found\"\n",
"\n",
" # Make sure symbol is visible\n",
" if not symbol_info.visible:\n",
" if not mt5.symbol_select(symbol, True):\n",
" log_and_print(f\"Failed to select symbol {symbol}\", is_error=True)\n",
" return \"Symbol not visible or could not be selected.\"\n",
"\n",
" tick_info = mt5.symbol_info_tick(symbol)\n",
" if tick_info is None:\n",
" log_and_print(f\"Could not get tick info for {symbol}.\", is_error=True)\n",
" return \"Tick info unavailable\"\n",
"\n",
" # Check for valid bid/ask\n",
" if tick_info.bid <= 0 or tick_info.ask <= 0:\n",
" log_and_print(\n",
" f\"Zero or invalid bid/ask for {symbol}: bid={tick_info.bid}, ask={tick_info.ask}\",\n",
" is_error=True\n",
" )\n",
" return \"Invalid prices\"\n",
"\n",
" # LOT SIZE VALIDATION\n",
" lot = max(lot, symbol_info.volume_min)\n",
" step = symbol_info.volume_step\n",
" if step > 0:\n",
" remainder = lot % step\n",
" if remainder != 0:\n",
" lot = lot - remainder + step\n",
" if lot > symbol_info.volume_max:\n",
" lot = symbol_info.volume_max\n",
"\n",
" log_and_print(\n",
" f\"Adjusted lot size to {lot} (min={symbol_info.volume_min}, \"\n",
" f\"step={symbol_info.volume_step}, max={symbol_info.volume_max})\"\n",
" )\n",
"\n",
" # Force ORDER_FILLING_IOC\n",
" filling_mode = mt5.ORDER_FILLING_IOC\n",
"\n",
" order_type = mt5.ORDER_TYPE_BUY if is_buy else mt5.ORDER_TYPE_SELL\n",
" deviation = 20\n",
"\n",
" request = {\n",
" \"action\": mt5.TRADE_ACTION_DEAL,\n",
" \"symbol\": symbol,\n",
" \"volume\": lot,\n",
" \"type\": order_type,\n",
" \"deviation\": deviation,\n",
" \"magic\": self.magic_number,\n",
" \"comment\": COMMENT_ML,\n",
" \"type_time\": mt5.ORDER_TIME_GTC,\n",
" \"type_filling\": filling_mode,\n",
" }\n",
"\n",
" if sl is not None:\n",
" request[\"sl\"] = sl\n",
" if tp is not None:\n",
" request[\"tp\"] = tp\n",
" if id_position is not None:\n",
" request[\"position\"] = id_position\n",
"\n",
" log_and_print(f\"Sending order request: {request}\")\n",
" result = mt5.order_send(request)\n",
"\n",
" order_type_str = \"BUY\" if is_buy else \"SELL\"\n",
" if result is None or result.retcode != mt5.TRADE_RETCODE_DONE:\n",
" error_message = f\"Order failed for {symbol}\"\n",
" if result:\n",
" error_message += f\", retcode={result.retcode}, comment={result.comment}\"\n",
" additional_info = (\n",
" f\"Date/Time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\\n\"\n",
" f\"Order Type: {order_type_str}\\n\"\n",
" f\"Lot Size: {lot}\\n\"\n",
" f\"SL: {sl if sl else 'None'}\\n\"\n",
" f\"TP: {tp if tp else 'None'}\\n\"\n",
" f\"Comment: {COMMENT_ML}\\n\"\n",
" f\"Request: {request}\\n\"\n",
" f\"Result: {result}\"\n",
" )\n",
" # If you need notifications, you could log or handle them differently here.\n",
" log_and_print(f\"Order failed details: {additional_info}\", is_error=True)\n",
" else:\n",
" success_message = f\"Order successful for {symbol}, comment={result.comment}\"\n",
" additional_info = (\n",
" f\"Date/Time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\\n\"\n",
" f\"Order Type: {order_type_str}\\n\"\n",
" f\"Lot Size: {lot}\\n\"\n",
" f\"SL: {sl if sl else 'None'}\\n\"\n",
" f\"TP: {tp if tp else 'None'}\\n\"\n",
" f\"Comment: {COMMENT_ML}\"\n",
" )\n",
" # If you need notifications, you could log or handle them differently here.\n",
" log_and_print(success_message)\n",
"\n",
" def get_positions_by_magic(self, symbol, magic_number):\n",
" \"\"\"\n",
" Retrieve positions for a specific symbol and magic number.\n",
" \"\"\"\n",
" all_positions = mt5.positions_get(symbol=symbol)\n",
" if not all_positions:\n",
" log_and_print(\"No positions found.\", is_error=False)\n",
" return []\n",
" return [pos for pos in all_positions if pos.magic == magic_number]\n",
"\n",
" def run_strategy(self, symbol, lot, buy_signal, sell_signal):\n",
" \"\"\"\n",
" Decide whether to open a buy or sell order based on signals,\n",
" close opposite positions if needed, etc.\n",
" \"\"\"\n",
" log_and_print(\"------------------------------------------------------------------\")\n",
" log_and_print(\n",
" f\"Date: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}, \"\n",
" f\"SYMBOL: {symbol}, BUY SIGNAL: {buy_signal}, SELL SIGNAL: {sell_signal}\"\n",
" )\n",
"\n",
" positions = self.get_positions_by_magic(symbol, self.magic_number)\n",
" has_buy = any(pos.type == mt5.POSITION_TYPE_BUY for pos in positions)\n",
" has_sell = any(pos.type == mt5.POSITION_TYPE_SELL for pos in positions)\n",
"\n",
" if buy_signal and not has_buy:\n",
" if has_sell:\n",
" log_and_print(\"Existing sell positions found. Attempting to close...\")\n",
" if self.close_position(symbol, is_buy=True):\n",
" log_and_print(\"Sell positions closed. Placing new buy order.\")\n",
" self.orders(symbol, lot, is_buy=True)\n",
" else:\n",
" log_and_print(\"Failed to close sell positions.\")\n",
" else:\n",
" self.orders(symbol, lot, is_buy=True)\n",
" elif sell_signal and not has_sell:\n",
" if has_buy:\n",
" log_and_print(\"Existing buy positions found. Attempting to close...\")\n",
" if self.close_position(symbol, is_buy=False):\n",
" log_and_print(\"Buy positions closed. Placing new sell order.\")\n",
" self.orders(symbol, lot, is_buy=False)\n",
" else:\n",
" log_and_print(\"Failed to close buy positions.\")\n",
" else:\n",
" self.orders(symbol, lot, is_buy=False)\n",
" else:\n",
" log_and_print(\"Appropriate position already exists or no signal to act on.\")\n",
"\n",
" def close_position(self, symbol, is_buy):\n",
" \"\"\"\n",
" Close all positions of the opposite type for the given symbol & magic.\n",
" \"\"\"\n",
" positions = mt5.positions_get(symbol=symbol)\n",
" if not positions:\n",
" log_and_print(f\"No positions to close for symbol: {symbol}\")\n",
" return False\n",
"\n",
" initial_balance = mt5.account_info().balance\n",
" closed_any = False\n",
"\n",
" for position in positions:\n",
" if position.magic == self.magic_number:\n",
" # if is_buy==True => we want to close SELL positions\n",
" # if is_buy==False => we want to close BUY positions\n",
" if ((is_buy and position.type == mt5.POSITION_TYPE_SELL) or\n",
" (not is_buy and position.type == mt5.POSITION_TYPE_BUY)):\n",
" close_request = {\n",
" \"action\": mt5.TRADE_ACTION_DEAL,\n",
" \"symbol\": symbol,\n",
" \"volume\": position.volume,\n",
" \"type\": mt5.ORDER_TYPE_BUY if position.type == mt5.POSITION_TYPE_SELL else mt5.ORDER_TYPE_SELL,\n",
" \"position\": position.ticket,\n",
" \"deviation\": 20,\n",
" \"magic\": self.magic_number,\n",
" \"comment\": COMMENT_ML,\n",
" \"type_time\": mt5.ORDER_TIME_GTC,\n",
" \"type_filling\": mt5.ORDER_FILLING_RETURN,\n",
" }\n",
" result = mt5.order_send(close_request)\n",
" if result.retcode != mt5.TRADE_RETCODE_DONE:\n",
" error_message = (\n",
" f\"Failed to close position {position.ticket} for {symbol}: {result.retcode}\"\n",
" )\n",
" log_and_print(error_message, is_error=True)\n",
" # If you need notifications, you could log or handle them differently here.\n",
" else:\n",
" log_and_print(f\"Successfully closed position {position.ticket} for {symbol}\")\n",
" closed_any = True\n",
"\n",
" if closed_any:\n",
" final_balance = mt5.account_info().balance\n",
" profit = final_balance - initial_balance\n",
" success_message = f\"Closed positions successfully, Profit: {profit}\"\n",
" log_and_print(success_message)\n",
" return True\n",
"\n",
" return False\n",
"\n",
" def check_and_execute_trades(self):\n",
" \"\"\"\n",
" Called in the main loop: generate signals, run strategy, etc.\n",
" \"\"\"\n",
" mt5.symbol_select(self.symbol, True)\n",
" buy, sell, _, _ = self.ml_signal_generation(self.symbol, N_BARS, TIMEFRAME)\n",
" self.run_strategy(self.symbol, self.lot_size, buy, sell)\n",
" mt5.symbol_select(self.symbol, False)\n",
" log_and_print(\"Waiting for new signals...\")\n",
"\n",
"def is_market_open():\n",
" \"\"\"\n",
" Check if the current time is within the typical Forex trading session, adjusted for CET/CEST.\n",
" Market closes at Friday 10:00 PM CET and opens at Sunday 11:00 PM CET.\n",
" \"\"\"\n",
" current_time_utc = datetime.utcnow()\n",
" current_time_cet = (\n",
" current_time_utc + timedelta(hours=2) \n",
" if time.localtime().tm_isdst \n",
" else current_time_utc + timedelta(hours=1)\n",
" )\n",
"\n",
" # Market closes Friday after 10 PM CET\n",
" if current_time_cet.weekday() == 4 and current_time_cet.hour >= 22:\n",
" return False\n",
" # Market opens Sunday after 11 PM CET\n",
" elif current_time_cet.weekday() == 6 and current_time_cet.hour < 23:\n",
" return False\n",
" # Closed all day Saturday\n",
" elif current_time_cet.weekday() == 5:\n",
" return False\n",
" return True\n",
"\n",
"if __name__ == \"__main__\":\n",
" try:\n",
" if not mt5.initialize(login=name, server=serv, password=key):\n",
" log_and_print(\"Failed to initialize MetaTrader 5\", is_error=True)\n",
" exit()\n",
"\n",
" app = TradingApp(symbol=SYMBOL, lot_size=LOT_SIZE, magic_number=MAGIC_NUMBER)\n",
"\n",
" # 1) Load the classification pipeline\n",
" # Make sure this pipeline is a classification model expecting SHIFTED double-barrier labels in {0,1,2}\n",
" pipeline_path = \"models/saved_models/best_rf_db_pipeline.pkl\"\n",
" app.load_pipeline(pipeline_path)\n",
"\n",
" while True:\n",
" log_and_print(\"Checking market status...\")\n",
" if is_market_open():\n",
" log_and_print(\"Market is open. Executing trades...\")\n",
"\n",
" # 2) Generate signals using the loaded pipeline\n",
" # This pipeline is classification-based => SHIFTED labels {0,1,2}\n",
" # ml_signal_generation() SHIFTs them back to [-1,0,+1]\n",
" buy_signal, sell_signal, _, _ = app.ml_signal_generation(\n",
" symbol=app.symbol,\n",
" n_bars=N_BARS,\n",
" timeframe=TIMEFRAME\n",
" )\n",
"\n",
" # 3) Run strategy\n",
" app.run_strategy(app.symbol, app.lot_size, buy_signal, sell_signal)\n",
" else:\n",
" log_and_print(\"Market is closed. No actions performed.\")\n",
"\n",
" time.sleep(SLEEP_TIME)\n",
"\n",
" except KeyboardInterrupt:\n",
" log_and_print(\"Shutdown signal received.\")\n",
" # If you need a notification here, handle it (e.g., log, email, etc.).\n",
" except Exception as e:\n",
" error_message = f\"An error occurred: {e}\"\n",
" log_and_print(error_message, is_error=True)\n",
" # If you need a notification here, handle it (e.g., log, email, etc.).\n",
" finally:\n",
" mt5.shutdown()\n",
" log_and_print(\"MetaTrader 5 shutdown completed.\")\n",
" # If you need a notification here, handle it (e.g., log, email, etc.).\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "ml",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.11"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,778 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Multi-Symbol Version"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Loaded pipeline from models/h1_models/US500_H1_best_model.pkl\n",
"Loaded pipeline from models/h1_models/US2000_H1_best_model.pkl\n",
"Loaded pipeline from models/h1_models/UK100_H1_best_model.pkl\n",
"Loaded pipeline from models/h1_models/JP225_H1_best_model.pkl\n",
"Loaded pipeline from models/h1_models/XAUUSD_H1_best_model.pkl\n",
"Loaded pipeline from models/h1_models/DE40_H1_best_model.pkl\n",
"Loaded pipeline from models/h1_models/US30_H1_best_model.pkl\n",
"Loaded pipeline from models/h1_models/USTEC_H1_best_model.pkl\n",
"Loaded pipeline from models/h1_models/BTCUSD_H1_best_model.pkl\n",
"Loaded pipeline from models/h1_models/AAPL.NAS_H1_best_model.pkl\n",
"Loaded pipeline from models/h1_models/MSFT.NAS_H1_best_model.pkl\n",
"Loaded pipeline from models/h1_models/GOOG.NAS_H1_best_model.pkl\n",
"Loaded pipeline from models/h1_models/AMZN.NAS_H1_best_model.pkl\n",
"Loaded pipeline from models/h1_models/TSLA.NAS_H1_best_model.pkl\n",
"Checking market status...\n",
"Market is open. Executing trades...\n",
"🟠 Skipping AAPL.NAS: US market not open yet.\n",
"🔵 Checking Symbol: US30\n",
"------------------------------------------------------------------\n",
"🟡 Date: 2025-05-26 23:19:56, SYMBOL: US30, BUY SIGNAL: False, SELL SIGNAL: False\n",
"No positions found.\n",
"🟤 US30: Flat signal. No position open.\n",
"🔵 Checking Symbol: US2000\n",
"------------------------------------------------------------------\n",
"🟡 Date: 2025-05-26 23:20:03, SYMBOL: US2000, BUY SIGNAL: False, SELL SIGNAL: False\n",
"No positions found.\n",
"🟤 US2000: Flat signal. No position open.\n",
"🔵 Checking Symbol: DE40\n",
"------------------------------------------------------------------\n",
"🟡 Date: 2025-05-26 23:20:09, SYMBOL: DE40, BUY SIGNAL: False, SELL SIGNAL: True\n",
"No positions found.\n",
"🔴 DE40: Placing new SELL order.\n",
"Adjusted lot size to 0.1 (min=0.1, step=0.1, max=250.0)\n",
"Sending order request: {'action': 1, 'symbol': 'DE40', 'volume': 0.1, 'type': 1, 'deviation': 20, 'magic': 234003, 'comment': 'RFFV-D', 'type_time': 0, 'type_filling': 1}\n",
"Order failed details: Date/Time: 2025-05-26 23:20:10\n",
"Order Type: SELL\n",
"Lot Size: 0.1\n",
"SL: None\n",
"TP: None\n",
"Comment: RFFV-D\n",
"Request: {'action': 1, 'symbol': 'DE40', 'volume': 0.1, 'type': 1, 'deviation': 20, 'magic': 234003, 'comment': 'RFFV-D', 'type_time': 0, 'type_filling': 1}\n",
"Result: OrderSendResult(retcode=10018, deal=0, order=0, volume=0.0, price=0.0, bid=0.0, ask=0.0, comment='Market closed', request_id=1768314453, retcode_external=0, request=TradeRequest(action=1, magic=234003, order=0, symbol='DE40', volume=0.1, price=0.0, stoplimit=0.0, sl=0.0, tp=0.0, deviation=20, type=1, type_filling=1, type_time=0, expiration=0, comment='RFFV-D', position=0, position_by=0))\n",
"Symbol DE40 added to retry queue for later attempt.\n",
"🔵 Checking Symbol: USTEC\n",
"------------------------------------------------------------------\n",
"🟡 Date: 2025-05-26 23:20:17, SYMBOL: USTEC, BUY SIGNAL: False, SELL SIGNAL: False\n",
"No positions found.\n",
"🟤 USTEC: Flat signal. No position open.\n",
"🔵 Checking Symbol: XAUUSD\n",
"------------------------------------------------------------------\n",
"🟡 Date: 2025-05-26 23:20:23, SYMBOL: XAUUSD, BUY SIGNAL: False, SELL SIGNAL: True\n",
"No positions found.\n",
"🔴 XAUUSD: Placing new SELL order.\n",
"Adjusted lot size to 0.01 (min=0.01, step=0.01, max=100.0)\n",
"Sending order request: {'action': 1, 'symbol': 'XAUUSD', 'volume': 0.01, 'type': 1, 'deviation': 20, 'magic': 234003, 'comment': 'RFFV-D', 'type_time': 0, 'type_filling': 1}\n",
"Order failed details: Date/Time: 2025-05-26 23:20:26\n",
"Order Type: SELL\n",
"Lot Size: 0.01\n",
"SL: None\n",
"TP: None\n",
"Comment: RFFV-D\n",
"Request: {'action': 1, 'symbol': 'XAUUSD', 'volume': 0.01, 'type': 1, 'deviation': 20, 'magic': 234003, 'comment': 'RFFV-D', 'type_time': 0, 'type_filling': 1}\n",
"Result: OrderSendResult(retcode=10018, deal=0, order=0, volume=0.0, price=0.0, bid=0.0, ask=0.0, comment='Market closed', request_id=1768314454, retcode_external=0, request=TradeRequest(action=1, magic=234003, order=0, symbol='XAUUSD', volume=0.01, price=0.0, stoplimit=0.0, sl=0.0, tp=0.0, deviation=20, type=1, type_filling=1, type_time=0, expiration=0, comment='RFFV-D', position=0, position_by=0))\n",
"Symbol XAUUSD added to retry queue for later attempt.\n",
"🟠 Skipping AMZN.NAS: US market not open yet.\n",
"🔵 Checking Symbol: BTCUSD\n",
"------------------------------------------------------------------\n",
"🟡 Date: 2025-05-26 23:20:32, SYMBOL: BTCUSD, BUY SIGNAL: True, SELL SIGNAL: False\n",
"No positions found.\n",
"🟢 BTCUSD: Placing new BUY order.\n",
"Adjusted lot size to 0.01 (min=0.01, step=0.01, max=10.0)\n",
"Sending order request: {'action': 1, 'symbol': 'BTCUSD', 'volume': 0.01, 'type': 0, 'deviation': 20, 'magic': 234003, 'comment': 'RFFV-D', 'type_time': 0, 'type_filling': 1}\n",
"Order successful for BTCUSD, comment=Request executed\n",
"🟠 Skipping TSLA.NAS: US market not open yet.\n",
"🟠 Skipping GOOG.NAS: US market not open yet.\n",
"🔵 Checking Symbol: UK100\n",
"------------------------------------------------------------------\n",
"🟡 Date: 2025-05-26 23:20:42, SYMBOL: UK100, BUY SIGNAL: False, SELL SIGNAL: False\n",
"No positions found.\n",
"🟤 UK100: Flat signal. No position open.\n",
"🟠 Skipping MSFT.NAS: US market not open yet.\n",
"🔵 Checking Symbol: US500\n",
"------------------------------------------------------------------\n",
"🟡 Date: 2025-05-26 23:20:48, SYMBOL: US500, BUY SIGNAL: False, SELL SIGNAL: True\n",
"No positions found.\n",
"🔴 US500: Placing new SELL order.\n",
"Adjusted lot size to 0.1 (min=0.1, step=0.1, max=250.0)\n",
"Sending order request: {'action': 1, 'symbol': 'US500', 'volume': 0.1, 'type': 1, 'deviation': 20, 'magic': 234003, 'comment': 'RFFV-D', 'type_time': 0, 'type_filling': 1}\n",
"Order failed details: Date/Time: 2025-05-26 23:20:48\n",
"Order Type: SELL\n",
"Lot Size: 0.1\n",
"SL: None\n",
"TP: None\n",
"Comment: RFFV-D\n",
"Request: {'action': 1, 'symbol': 'US500', 'volume': 0.1, 'type': 1, 'deviation': 20, 'magic': 234003, 'comment': 'RFFV-D', 'type_time': 0, 'type_filling': 1}\n",
"Result: OrderSendResult(retcode=10018, deal=0, order=0, volume=0.0, price=0.0, bid=0.0, ask=0.0, comment='Market closed', request_id=1768314456, retcode_external=0, request=TradeRequest(action=1, magic=234003, order=0, symbol='US500', volume=0.1, price=0.0, stoplimit=0.0, sl=0.0, tp=0.0, deviation=20, type=1, type_filling=1, type_time=0, expiration=0, comment='RFFV-D', position=0, position_by=0))\n",
"Symbol US500 added to retry queue for later attempt.\n",
"🔵 Checking Symbol: JP225\n",
"------------------------------------------------------------------\n",
"🟡 Date: 2025-05-26 23:20:53, SYMBOL: JP225, BUY SIGNAL: False, SELL SIGNAL: True\n",
"No positions found.\n",
"🔴 JP225: Placing new SELL order.\n",
"Adjusted lot size to 1.0 (min=1.0, step=1.0, max=250.0)\n",
"Sending order request: {'action': 1, 'symbol': 'JP225', 'volume': 1.0, 'type': 1, 'deviation': 20, 'magic': 234003, 'comment': 'RFFV-D', 'type_time': 0, 'type_filling': 1}\n",
"Order failed details: Date/Time: 2025-05-26 23:20:55\n",
"Order Type: SELL\n",
"Lot Size: 1.0\n",
"SL: None\n",
"TP: None\n",
"Comment: RFFV-D\n",
"Request: {'action': 1, 'symbol': 'JP225', 'volume': 1.0, 'type': 1, 'deviation': 20, 'magic': 234003, 'comment': 'RFFV-D', 'type_time': 0, 'type_filling': 1}\n",
"Result: OrderSendResult(retcode=10018, deal=0, order=0, volume=0.0, price=0.0, bid=0.0, ask=0.0, comment='Market closed', request_id=1768314457, retcode_external=0, request=TradeRequest(action=1, magic=234003, order=0, symbol='JP225', volume=1.0, price=0.0, stoplimit=0.0, sl=0.0, tp=0.0, deviation=20, type=1, type_filling=1, type_time=0, expiration=0, comment='RFFV-D', position=0, position_by=0))\n",
"Symbol JP225 added to retry queue for later attempt.\n",
"✅ Successful Orders: 1\n",
"❌ Failed Orders: 4\n",
"⏳ Retry Queue: ['DE40', 'XAUUSD', 'US500', 'JP225']\n"
]
}
],
"source": [
"# LIVE TRADING CODE FOR MULTI-BAR CLASSIFICATION\n",
"\n",
"import sys\n",
"import os\n",
"import warnings\n",
"from pathlib import Path\n",
"\n",
"# ---------------------------------------------------------------------------\n",
"# 1) SET PROJECT ROOT AND UPDATE PATH/WORKING DIRECTORY\n",
"# ---------------------------------------------------------------------------\n",
"project_root = Path.cwd().parent.parent # Adjust if your notebook is in notebooks/time_series\n",
"sys.path.append(str(project_root))\n",
"os.chdir(str(project_root))\n",
"warnings.filterwarnings(\"ignore\")\n",
"\n",
"import warnings\n",
"warnings.filterwarnings(\"ignore\")\n",
"import MetaTrader5 as mt5\n",
"import pandas as pd\n",
"import numpy as np\n",
"import ta\n",
"from datetime import datetime, timedelta\n",
"import time\n",
"import logging\n",
"import joblib\n",
"from features.feature_engineering import add_core_features\n",
"\n",
"import sqlite3\n",
"import pandas as pd\n",
"from datetime import datetime\n",
"\n",
"\n",
"\n",
"# Setup logging\n",
"logging.basicConfig(\n",
" filename='models/saved_models/trading_app1.log',\n",
" level=logging.INFO,\n",
" format='%(asctime)s %(levelname)s:%(message)s',\n",
" datefmt='%Y-%m-%d %H:%M:%S'\n",
")\n",
"\n",
"def log_and_print(message, is_error=False):\n",
" \"\"\"\n",
" Logs and prints a message.\n",
" If is_error=True, logs at the ERROR level; otherwise logs at INFO level.\n",
" \"\"\"\n",
" if is_error:\n",
" logging.error(message)\n",
" else:\n",
" logging.info(message)\n",
" print(message)\n",
"\n",
"# Update the login credentials and server information accordingly\n",
"#name = 66677507\n",
"#key = 'ST746$nG38'\n",
"#serv = 'ICMarketsSC-Demo'\n",
"\n",
"# Global variables\n",
"symbols = [\n",
" \"US500\", \"US2000\", \"UK100\", \"JP225\", \"XAUUSD\", \"DE40\", \"US30\",\n",
" \"USTEC\", \"BTCUSD\", \"AAPL.NAS\", \"MSFT.NAS\", \"GOOG.NAS\", \"AMZN.NAS\", \"TSLA.NAS\"\n",
"]\n",
"\n",
"lot_sizes = {\n",
" \n",
" \"US500\": 0.1,\n",
" \"US2000\": 0.1,\n",
" \"UK100\": 0.1,\n",
" \"DE40\": 0.1,\n",
" \"USTEC\": 0.1,\n",
" \"JP225\": 1.00,\n",
"\n",
"}\n",
"\n",
"# Add Forex and Stocks with 0.01 lot size\n",
"for symbol in symbols:\n",
" if symbol not in lot_sizes:\n",
" lot_sizes[symbol] = 0.01\n",
"\n",
"model_paths = {\n",
" symbol: f\"models/h1_models/{symbol}_H1_best_model.pkl\" for symbol in symbols\n",
"}\n",
"\n",
"\n",
"TIMEFRAME = mt5.TIMEFRAME_H1\n",
"N_BARS = 1000\n",
"MAGIC_NUMBER = 234003\n",
"SLEEP_TIME = 3600 # 1 hour\n",
"COMMENT_ML = \"RFFV-D\"\n",
"\n",
"success_count = 0\n",
"fail_count = 0\n",
"retry_queue = []\n",
"# Global setting for whether to close open positions on flat (neutral) signal\n",
"CLOSE_ON_FLAT = True\n",
"N_FORWARD = 3 # Number of bars ahead to predict and save\n",
"\n",
"# If you still need feature selection, you can keep this helper function:\n",
"def select_features_rf_reg(X, y, estimator, max_features=20):\n",
" \"\"\"\n",
" Example helper function for feature selection using RandomForest.\n",
" \"\"\"\n",
" from sklearn.feature_selection import SelectFromModel\n",
" selector = SelectFromModel(estimator=estimator, threshold=-np.inf, max_features=max_features).fit(X, y)\n",
" X_transformed = selector.transform(X)\n",
" selected_features_mask = selector.get_support()\n",
" return X_transformed, selected_features_mask\n",
"\n",
"def is_us_stock(symbol):\n",
" return symbol.endswith(\".NAS\") or symbol.endswith(\".NYSE\")\n",
"\n",
"def is_us_market_open():\n",
" \"\"\"\n",
" Check if US stock market is open based on Switzerland time (CET/CEST).\n",
" \"\"\"\n",
" now = datetime.now()\n",
"\n",
" if now.weekday() >= 5: # Saturday or Sunday\n",
" return False\n",
"\n",
" market_open = now.replace(hour=15, minute=30, second=0, microsecond=0)\n",
" market_close = now.replace(hour=22, minute=0, second=0, microsecond=0)\n",
"\n",
" return market_open <= now <= market_close\n",
"\n",
"\n",
"\n",
"\n",
"class TradingApp:\n",
"\n",
" def __init__(self, symbol, lot_size, magic_number):\n",
" self.symbol = symbol\n",
" self.lot_size = lot_size\n",
" self.magic_number = magic_number\n",
" self.model = None # previously self.pipeline\n",
" self.scaler = None\n",
" self.features = None\n",
" self.last_retrain_time = None\n",
"\n",
" def get_data(self, symbol, n, timeframe):\n",
" \"\"\"\n",
" Fetch 'n' bars of historical data for the given symbol and timeframe.\n",
" \"\"\"\n",
" rates = mt5.copy_rates_from_pos(symbol, timeframe, 0, n)\n",
" rates_frame = pd.DataFrame(rates)\n",
" rates_frame['time'] = pd.to_datetime(rates_frame['time'], unit='s')\n",
" rates_frame.set_index('time', inplace=True)\n",
" return rates_frame\n",
"\n",
" def add_core_features(self, df):\n",
" \"\"\"\n",
" Add only the core features to the DataFrame (the same ones used in model training).\n",
" \"\"\"\n",
" df = add_core_features(df)\n",
" return df\n",
"\n",
" def load_pipeline(self, pipeline_path):\n",
" pipeline_loaded = joblib.load(pipeline_path)\n",
"\n",
" if isinstance(pipeline_loaded, dict):\n",
" self.model = pipeline_loaded.get(\"model\")\n",
" self.scaler = pipeline_loaded.get(\"scaler\", None)\n",
" self.features = pipeline_loaded.get(\"features\", None)\n",
" else:\n",
" self.model = pipeline_loaded\n",
" self.scaler = None\n",
" self.features = None\n",
"\n",
" logging.info(f\"Loaded model from {pipeline_path}\")\n",
" log_and_print(f\"✅ Loaded model from {pipeline_path}\")\n",
"\n",
"\n",
"\n",
"\n",
"\n",
" def ml_signal_generation(self, symbol, n_bars, timeframe):\n",
" if self.model is None:\n",
" logging.error(\"❌ No model loaded.\")\n",
" return None\n",
"\n",
" df = self.get_data(symbol, n_bars, timeframe)\n",
" df = self.add_core_features(df)\n",
" df.fillna(method='ffill', inplace=True)\n",
"\n",
" features = self.features if self.features else [\n",
" \"sma_20\", \"ema_20\", \"kama_10\", \"rsi_14\", \"macd_diff\",\n",
" \"atr_14\", \"obv\", \"rolling_std_20\", \"spread\", \"fill\", \"amplitude\",\n",
" \"autocorr_1\", \"autocorr_5\", \"autocorr_10\", \"market_regime\", \"stationary_flag\"\n",
" ]\n",
"\n",
" X_new = df[features].dropna()\n",
"\n",
" if X_new.empty:\n",
" logging.error(f\"❌ No valid feature rows for prediction on {symbol}.\")\n",
" return None\n",
"\n",
" if self.scaler:\n",
" X_scaled = self.scaler.transform(X_new)\n",
" else:\n",
" X_scaled = X_new\n",
"\n",
" preds_shifted = self.model.predict(X_scaled)\n",
" preds = preds_shifted - 1\n",
"\n",
" if all(p == 0 for p in preds[-N_FORWARD:]):\n",
" log_and_print(f\"⚪ {symbol}: All {N_FORWARD} predictions are flat (0).\")\n",
"\n",
" return preds[-N_FORWARD:]\n",
"\n",
"\n",
"\n",
"\n",
" def orders(self, symbol, lot, is_buy=True, id_position=None, sl=None, tp=None):\n",
" \"\"\"\n",
" Place an order (BUY or SELL) for the specified symbol and lot size.\n",
" \"\"\"\n",
" global success_count, fail_count, retry_queue\n",
"\n",
" symbol_info = mt5.symbol_info(symbol)\n",
" if symbol_info is None:\n",
" log_and_print(f\"Symbol {symbol} not found, can't place order.\", is_error=True)\n",
" fail_count += 1\n",
" return \"Symbol not found\"\n",
"\n",
" # Make sure symbol is visible\n",
" if not symbol_info.visible:\n",
" if not mt5.symbol_select(symbol, True):\n",
" log_and_print(f\"Failed to select symbol {symbol}\", is_error=True)\n",
" fail_count += 1\n",
" return \"Symbol not visible or could not be selected.\"\n",
"\n",
" tick_info = mt5.symbol_info_tick(symbol)\n",
" if tick_info is None:\n",
" log_and_print(f\"Could not get tick info for {symbol}.\", is_error=True)\n",
" fail_count += 1\n",
" return \"Tick info unavailable\"\n",
"\n",
" # Check for valid bid/ask\n",
" if tick_info.bid <= 0 or tick_info.ask <= 0:\n",
" log_and_print(\n",
" f\"Zero or invalid bid/ask for {symbol}: bid={tick_info.bid}, ask={tick_info.ask}\",\n",
" is_error=True\n",
" )\n",
" fail_count += 1\n",
" return \"Invalid prices\"\n",
"\n",
" # LOT SIZE VALIDATION\n",
" lot = max(lot, symbol_info.volume_min)\n",
" step = symbol_info.volume_step\n",
" if step > 0:\n",
" remainder = lot % step\n",
" if remainder != 0:\n",
" lot = lot - remainder + step\n",
" if lot > symbol_info.volume_max:\n",
" lot = symbol_info.volume_max\n",
"\n",
" log_and_print(\n",
" f\"Adjusted lot size to {lot} (min={symbol_info.volume_min}, \"\n",
" f\"step={symbol_info.volume_step}, max={symbol_info.volume_max})\"\n",
" )\n",
"\n",
" # Force ORDER_FILLING_IOC\n",
" filling_mode = 1 # ORDER_FILLING_IOC\n",
"\n",
" order_type = mt5.ORDER_TYPE_BUY if is_buy else mt5.ORDER_TYPE_SELL\n",
" order_price = tick_info.ask if is_buy else tick_info.bid\n",
" deviation = 20\n",
"\n",
" request = {\n",
" \"action\": mt5.TRADE_ACTION_DEAL,\n",
" \"symbol\": symbol,\n",
" \"volume\": lot,\n",
" \"type\": order_type,\n",
" \"deviation\": deviation,\n",
" \"magic\": self.magic_number,\n",
" \"comment\": COMMENT_ML,\n",
" \"type_time\": mt5.ORDER_TIME_GTC,\n",
" \"type_filling\": filling_mode,\n",
" }\n",
"\n",
" if sl is not None:\n",
" request[\"sl\"] = sl\n",
" if tp is not None:\n",
" request[\"tp\"] = tp\n",
" if id_position is not None:\n",
" request[\"position\"] = id_position\n",
"\n",
" log_and_print(f\"Sending order request: {request}\")\n",
" result = mt5.order_send(request)\n",
"\n",
" order_type_str = \"BUY\" if is_buy else \"SELL\"\n",
"\n",
" if result is None or result.retcode != mt5.TRADE_RETCODE_DONE:\n",
" fail_count += 1\n",
" error_message = f\"Order failed for {symbol}\"\n",
" if result:\n",
" error_message += f\", retcode={result.retcode}, comment={result.comment}\"\n",
" additional_info = (\n",
" f\"Date/Time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\\n\"\n",
" f\"Order Type: {order_type_str}\\n\"\n",
" f\"Lot Size: {lot}\\n\"\n",
" f\"SL: {sl if sl else 'None'}\\n\"\n",
" f\"TP: {tp if tp else 'None'}\\n\"\n",
" f\"Comment: {COMMENT_ML}\\n\"\n",
" f\"Request: {request}\\n\"\n",
" f\"Result: {result}\"\n",
" )\n",
" log_and_print(f\"Order failed details: {additional_info}\", is_error=True)\n",
"\n",
" # If market closed or only closing allowed => add to retry queue\n",
" if result is not None and result.retcode in [10018, 10044]:\n",
" retry_queue.append((symbol, datetime.now() + timedelta(minutes=15)))\n",
" log_and_print(f\"Symbol {symbol} added to retry queue for later attempt.\", is_error=False)\n",
"\n",
" else:\n",
" success_count += 1\n",
" success_message = f\"Order successful for {symbol}, comment={result.comment}\"\n",
" additional_info = (\n",
" f\"Date/Time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\\n\"\n",
" f\"Order Type: {order_type_str}\\n\"\n",
" f\"Lot Size: {lot}\\n\"\n",
" f\"SL: {sl if sl else 'None'}\\n\"\n",
" f\"TP: {tp if tp else 'None'}\\n\"\n",
" f\"Comment: {COMMENT_ML}\"\n",
" )\n",
" log_and_print(success_message)\n",
"\n",
"\n",
" def get_positions_by_magic(self, symbol, magic_number):\n",
" \"\"\"\n",
" Retrieve positions for a specific symbol and magic number.\n",
" \"\"\"\n",
" all_positions = mt5.positions_get(symbol=symbol)\n",
" if not all_positions:\n",
" log_and_print(\"No positions found.\", is_error=False)\n",
" return []\n",
" return [pos for pos in all_positions if pos.magic == magic_number]\n",
"\n",
"\n",
"\n",
" def run_strategy(self, symbol, lot, buy_signal, sell_signal):\n",
" \"\"\"\n",
" Run the trading strategy logic based on buy/sell signals, including flat signals.\n",
" \"\"\"\n",
" log_and_print(\"------------------------------------------------------------------\")\n",
" log_and_print(\n",
" f\"🟡 Date: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}, \"\n",
" f\"SYMBOL: {symbol}, BUY SIGNAL: {buy_signal}, SELL SIGNAL: {sell_signal}\"\n",
" )\n",
"\n",
" positions = self.get_positions_by_magic(symbol, self.magic_number)\n",
" has_buy = any(pos.type == mt5.POSITION_TYPE_BUY for pos in positions)\n",
" has_sell = any(pos.type == mt5.POSITION_TYPE_SELL for pos in positions)\n",
"\n",
" if buy_signal and not has_buy:\n",
" if has_sell:\n",
" log_and_print(f\"🔄 {symbol}: Existing SELL position found. Attempting to close it...\")\n",
" if self.close_position(symbol, is_buy=True):\n",
" log_and_print(f\"✅ {symbol}: Closed SELL. Placing new BUY order.\")\n",
" self.orders(symbol, lot, is_buy=True)\n",
" else:\n",
" log_and_print(f\"❌ {symbol}: Failed to close SELL position.\")\n",
" else:\n",
" log_and_print(f\"🟢 {symbol}: Placing new BUY order.\")\n",
" self.orders(symbol, lot, is_buy=True)\n",
"\n",
" elif sell_signal and not has_sell:\n",
" if has_buy:\n",
" log_and_print(f\"🔄 {symbol}: Existing BUY position found. Attempting to close it...\")\n",
" if self.close_position(symbol, is_buy=False):\n",
" log_and_print(f\"✅ {symbol}: Closed BUY. Placing new SELL order.\")\n",
" self.orders(symbol, lot, is_buy=False)\n",
" else:\n",
" log_and_print(f\"❌ {symbol}: Failed to close BUY position.\")\n",
" else:\n",
" log_and_print(f\"🔴 {symbol}: Placing new SELL order.\")\n",
" self.orders(symbol, lot, is_buy=False)\n",
"\n",
" elif not buy_signal and not sell_signal:\n",
" if has_buy or has_sell:\n",
" if CLOSE_ON_FLAT:\n",
" log_and_print(f\"⚪ {symbol}: Flat signal. CLOSE_ON_FLAT is True, attempting to close open position.\")\n",
" success = self.close_position(symbol, is_buy=has_sell)\n",
" if success:\n",
" log_and_print(f\"✅ {symbol}: Flat signal - Position closed.\")\n",
" else:\n",
" log_and_print(f\"❌ {symbol}: Flat signal - Failed to close position.\")\n",
" else:\n",
" log_and_print(f\"⚪ {symbol}: Flat signal but CLOSE_ON_FLAT is False. Holding current position.\")\n",
" else:\n",
" log_and_print(f\"🟤 {symbol}: Flat signal. No position open.\")\n",
"\n",
"\n",
"\n",
" def close_position(self, symbol, is_buy):\n",
" \"\"\"\n",
" Closes positions of the opposite type (BUY/SELL) for this app's magic number.\n",
" \"\"\"\n",
" positions = mt5.positions_get(symbol=symbol)\n",
" if not positions:\n",
" log_and_print(f\"No positions to close for symbol: {symbol}\")\n",
" return False\n",
"\n",
" initial_balance = mt5.account_info().balance\n",
" closed_any = False\n",
"\n",
" for position in positions:\n",
" if position.magic == self.magic_number and (\n",
" (is_buy and position.type == mt5.POSITION_TYPE_SELL) or\n",
" (not is_buy and position.type == mt5.POSITION_TYPE_BUY)\n",
" ):\n",
" # First try ORDER_FILLING_RETURN\n",
" close_request = {\n",
" \"action\": mt5.TRADE_ACTION_DEAL,\n",
" \"symbol\": symbol,\n",
" \"volume\": position.volume,\n",
" \"type\": mt5.ORDER_TYPE_BUY if position.type == mt5.POSITION_TYPE_SELL else mt5.ORDER_TYPE_SELL,\n",
" \"position\": position.ticket,\n",
" \"deviation\": 20,\n",
" \"magic\": self.magic_number,\n",
" \"comment\": COMMENT_ML,\n",
" \"type_time\": mt5.ORDER_TIME_GTC,\n",
" \"type_filling\": mt5.ORDER_FILLING_RETURN, # Try RETURN first\n",
" }\n",
" result = mt5.order_send(close_request)\n",
"\n",
" if result is None or result.retcode != mt5.TRADE_RETCODE_DONE:\n",
" # Retry WITH ORDER_FILLING_IOC instead of removing filling type\n",
" log_and_print(f\"⚠️ Filling mode error for {symbol} position {position.ticket}, retrying with ORDER_FILLING_IOC...\")\n",
" \n",
" close_request[\"type_filling\"] = mt5.ORDER_FILLING_IOC # Force IOC\n",
" result_retry = mt5.order_send(close_request)\n",
"\n",
" if result_retry is None or result_retry.retcode != mt5.TRADE_RETCODE_DONE:\n",
" log_and_print(f\"❌ Retry failed to close {symbol} — retcode: {result_retry.retcode}, comment: {result_retry.comment}\", is_error=True)\n",
" else:\n",
" log_and_print(f\"✅ Retry succeeded in closing {symbol} position {position.ticket}\")\n",
" closed_any = True\n",
" else:\n",
" log_and_print(f\"✅ Successfully closed position {position.ticket} for {symbol}\")\n",
" closed_any = True\n",
"\n",
" if closed_any:\n",
" final_balance = mt5.account_info().balance\n",
" profit = final_balance - initial_balance\n",
" log_and_print(f\"✅ Closed positions successfully, Profit: {profit}\")\n",
" return True\n",
" else:\n",
" return False\n",
"\n",
"\n",
"\n",
"\n",
"\n",
" def check_and_execute_trades(self):\n",
" \"\"\"\n",
" Convenience method to perform the entire flow:\n",
" generate signals, run strategy, and deselect symbol.\n",
" \"\"\"\n",
" mt5.symbol_select(self.symbol, True)\n",
" buy, sell, _, _ = self.ml_signal_generation(self.symbol, N_BARS, TIMEFRAME)\n",
" self.run_strategy(self.symbol, self.lot_size, buy, sell)\n",
" mt5.symbol_select(self.symbol, False)\n",
" log_and_print(\"Waiting for new signals...\")\n",
"\n",
"def is_market_open():\n",
" \"\"\"\n",
" Check if the current time is within the typical Forex trading session, adjusted for CET/CEST.\n",
" Market closes at Friday 10:00 PM CET and opens at Sunday 11:00 PM CET. \n",
" It is closed all day Saturday.\n",
" \"\"\"\n",
" current_time_utc = datetime.utcnow()\n",
" # Adjust for Central European Time (UTC+1) or Central European Summer Time (UTC+2)\n",
" current_time_cet = (\n",
" current_time_utc + timedelta(hours=2) \n",
" if time.localtime().tm_isdst \n",
" else current_time_utc + timedelta(hours=1)\n",
" )\n",
"\n",
" # Friday after 10 PM CET\n",
" if current_time_cet.weekday() == 4 and current_time_cet.hour >= 22:\n",
" return False\n",
" # Sunday before 11 PM CET\n",
" elif current_time_cet.weekday() == 6 and current_time_cet.hour < 23:\n",
" return False\n",
" # All day Saturday\n",
" elif current_time_cet.weekday() == 5:\n",
" return False\n",
" return True\n",
"\n",
"\n",
"\n",
"\n",
"def save_signal_to_db(symbol, prediction, timestamp=None):\n",
" conn = sqlite3.connect('live_signals.db')\n",
" c = conn.cursor()\n",
" c.execute('''\n",
" CREATE TABLE IF NOT EXISTS signals (\n",
" id INTEGER PRIMARY KEY AUTOINCREMENT,\n",
" symbol TEXT,\n",
" prediction INTEGER,\n",
" timestamp TEXT,\n",
" UNIQUE(symbol, prediction, timestamp)\n",
" )\n",
" ''')\n",
" if timestamp is None:\n",
" timestamp = datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S')\n",
" try:\n",
" c.execute(\n",
" 'INSERT OR IGNORE INTO signals (symbol, prediction, timestamp) VALUES (?, ?, ?)',\n",
" (symbol, prediction, timestamp)\n",
" )\n",
" except Exception as e:\n",
" print(f\"Failed to save to db: {e}\")\n",
" conn.commit()\n",
" conn.close()\n",
"\n",
"\n",
"\n",
"if __name__ == \"__main__\":\n",
" try:\n",
" if not mt5.initialize():\n",
" log_and_print(\"Failed to initialize MetaTrader 5\", is_error=True)\n",
" exit()\n",
"\n",
" # 1) Create a TradingApp per symbol\n",
" apps = {}\n",
" for symbol in symbols:\n",
" app = TradingApp(symbol=symbol, lot_size=lot_sizes[symbol], magic_number=MAGIC_NUMBER)\n",
" app.load_pipeline(model_paths[symbol])\n",
" apps[symbol] = app\n",
"\n",
" # 2) Initialize tracking variables\n",
" success_count = 0\n",
" fail_count = 0\n",
" retry_queue = []\n",
"\n",
" while True:\n",
" log_and_print(\"Checking market status...\")\n",
" if is_market_open():\n",
" log_and_print(\"Market is open. Executing trades...\")\n",
"\n",
" # Handle retry queue\n",
" now = datetime.now()\n",
" retry_symbols_ready = [item for item in retry_queue if item[1] <= now]\n",
" retry_queue = [item for item in retry_queue if item[1] > now]\n",
"\n",
" symbols_to_process = set(apps.keys())\n",
"\n",
" # Add retry symbols first\n",
" symbols_ready_to_retry = [item[0] for item in retry_symbols_ready]\n",
" if symbols_ready_to_retry:\n",
" log_and_print(f\"🔁 Retrying symbols: {symbols_ready_to_retry}\")\n",
" symbols_to_process = set(symbols_ready_to_retry) | symbols_to_process\n",
"\n",
" for symbol in symbols_to_process:\n",
" app = apps[symbol]\n",
" try:\n",
" if is_us_stock(symbol) and not is_us_market_open():\n",
" log_and_print(f\"🟠 Skipping {symbol}: US market not open yet.\")\n",
" continue\n",
"\n",
" log_and_print(f\"🔵 Checking Symbol: {symbol}\")\n",
"\n",
" # --- Get N-step-ahead predictions ---\n",
" multi_preds = app.ml_signal_generation(\n",
" symbol=app.symbol,\n",
" n_bars=N_BARS,\n",
" timeframe=TIMEFRAME\n",
" )\n",
" if multi_preds is None:\n",
" continue\n",
"\n",
" # --- Get last bar time for timestamping ---\n",
" df_data = app.get_data(app.symbol, N_BARS, TIMEFRAME)\n",
" last_bar_time = df_data.index[-1]\n",
"\n",
" # --- Save all N_FORWARD predictions ---\n",
" for i, pred in enumerate(multi_preds):\n",
" future_time = (last_bar_time + pd.Timedelta(hours=i+1)).strftime('%Y-%m-%d %H:%M:%S')\n",
" save_signal_to_db(app.symbol, int(pred), timestamp=future_time)\n",
"\n",
" # --- Use first prediction for live trading ---\n",
" buy_signal = (multi_preds[0] == 1)\n",
" sell_signal = (multi_preds[0] == -1)\n",
" app.run_strategy(app.symbol, app.lot_size, buy_signal, sell_signal)\n",
"\n",
" except Exception as e:\n",
" log_and_print(f\"⚠️ Error processing {symbol}: {e}\", is_error=True)\n",
"\n",
"\n",
" # After the trading round, log results\n",
" log_and_print(f\"✅ Successful Orders: {success_count}\")\n",
" log_and_print(f\"❌ Failed Orders: {fail_count}\")\n",
" log_and_print(f\"⏳ Retry Queue: {[item[0] for item in retry_queue]}\")\n",
"\n",
" else:\n",
" log_and_print(\"Market is closed. Waiting...\")\n",
"\n",
" time.sleep(SLEEP_TIME)\n",
"\n",
" except KeyboardInterrupt:\n",
" log_and_print(\"Shutdown signal received.\")\n",
"\n",
" except Exception as e:\n",
" error_message = f\"An error occurred: {e}\"\n",
" log_and_print(error_message, is_error=True)\n",
"\n",
" finally:\n",
" mt5.shutdown()\n",
" log_and_print(\"MetaTrader 5 shutdown completed.\")\n",
"\n",
"\n",
"\n",
"\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "ml",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.16"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
@@ -0,0 +1,218 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import sys\n",
"import os\n",
"import warnings\n",
"from pathlib import Path\n",
"import MetaTrader5 as mt5\n",
"import pandas as pd\n",
"import numpy as np\n",
"from statsmodels.tsa.stattools import coint\n",
"import time\n",
"import logging\n",
"\n",
"# ---------------------------------------------------------------------------\n",
"# 1) SETUP LOGGING & ENVIRONMENT\n",
"# ---------------------------------------------------------------------------\n",
"warnings.filterwarnings(\"ignore\")\n",
"\n",
"# Logging setup\n",
"logging.basicConfig(\n",
" filename=\"models/saved_models/pair_trading.log\",\n",
" level=logging.INFO,\n",
" format=\"%(asctime)s %(levelname)s:%(message)s\",\n",
" datefmt=\"%Y-%m-%d %H:%M:%S\",\n",
")\n",
"\n",
"def log_and_print(message, is_error=False):\n",
" if is_error:\n",
" logging.error(message)\n",
" else:\n",
" logging.info(message)\n",
" print(message)\n",
"\n",
"# ---------------------------------------------------------------------------\n",
"# 2) ACCOUNT CONFIGURATION\n",
"# ---------------------------------------------------------------------------\n",
"name = 7889999\n",
"key = \"hdgdggFxEG38\"\n",
"serv = \"ICMarketsSC-Demo\"\n",
"\n",
"# Global parameters\n",
"PAIR1 = \"AUDUSD\"\n",
"PAIR2 = \"NZDUSD\"\n",
"LOT_SIZE = 0.01\n",
"TIMEFRAME = mt5.TIMEFRAME_H1 # Adjust as needed\n",
"N_BARS = 5000 # More data for stable pair trading\n",
"MAGIC_NUMBER = 77777\n",
"SLEEP_TIME = 3600 # 1 Hour sleep cycle\n",
"\n",
"# ---------------------------------------------------------------------------\n",
"# 3) MT5 FUNCTIONS\n",
"# ---------------------------------------------------------------------------\n",
"def get_data(symbol, n, timeframe):\n",
" \"\"\"Fetches historical price data from MT5.\"\"\"\n",
" rates = mt5.copy_rates_from_pos(symbol, timeframe, 0, n)\n",
" if rates is None:\n",
" raise ValueError(f\"Could not retrieve data for {symbol}\")\n",
" df = pd.DataFrame(rates)\n",
" df[\"time\"] = pd.to_datetime(df[\"time\"], unit=\"s\")\n",
" df.set_index(\"time\", inplace=True)\n",
" return df[[\"close\"]]\n",
"\n",
"def check_cointegration(symbol1, symbol2, n_bars):\n",
" \"\"\"Performs cointegration test between two assets.\"\"\"\n",
" df1 = get_data(symbol1, n_bars, TIMEFRAME)\n",
" df2 = get_data(symbol2, n_bars, TIMEFRAME)\n",
"\n",
" score, p_value, _ = coint(df1[\"close\"], df2[\"close\"])\n",
" log_and_print(f\"⚖️ Cointegration Test p-value ({symbol1} & {symbol2}): {p_value:.4f}\")\n",
" \n",
" return p_value < 0.05 # True if cointegrated\n",
"\n",
"def compute_z_score(spread, lookback=60):\n",
" \"\"\"Calculates the rolling Z-score of the spread.\"\"\"\n",
" mean = spread.rolling(lookback).mean()\n",
" std = spread.rolling(lookback).std()\n",
" return (spread - mean) / std\n",
"\n",
"def generate_pair_signals(pair1, pair2, lookback=60, entry_threshold=1.5, exit_threshold=0.5):\n",
" \"\"\"Computes Z-score signals for pair trading.\"\"\"\n",
" df1 = get_data(pair1, N_BARS, TIMEFRAME)\n",
" df2 = get_data(pair2, N_BARS, TIMEFRAME)\n",
"\n",
" # Compute spread\n",
" spread = df1[\"close\"] - df2[\"close\"]\n",
" z_score = compute_z_score(spread, lookback)\n",
"\n",
" df = pd.DataFrame({\"spread\": spread, \"z_score\": z_score})\n",
" \n",
" # Trading signals\n",
" df[\"long_signal\"] = df[\"z_score\"] < -entry_threshold # Buy Pair1, Sell Pair2\n",
" df[\"short_signal\"] = df[\"z_score\"] > entry_threshold # Sell Pair1, Buy Pair2\n",
" df[\"exit_signal\"] = df[\"z_score\"].abs() < exit_threshold # Exit trade\n",
"\n",
" return df\n",
"\n",
"def place_order(symbol, lot, is_buy, magic):\n",
" \"\"\"Places an order on MT5.\"\"\"\n",
" order_type = mt5.ORDER_TYPE_BUY if is_buy else mt5.ORDER_TYPE_SELL\n",
" deviation = 20\n",
"\n",
" request = {\n",
" \"action\": mt5.TRADE_ACTION_DEAL,\n",
" \"symbol\": symbol,\n",
" \"volume\": lot,\n",
" \"type\": order_type,\n",
" \"deviation\": deviation,\n",
" \"magic\": magic,\n",
" \"comment\": \"Pair Trading Bot\",\n",
" \"type_time\": mt5.ORDER_TIME_GTC,\n",
" \"type_filling\": mt5.ORDER_FILLING_IOC,\n",
" }\n",
"\n",
" result = mt5.order_send(request)\n",
" if result is None or result.retcode != mt5.TRADE_RETCODE_DONE:\n",
" log_and_print(f\"❌ Order failed for {symbol}: {result.retcode}\", is_error=True)\n",
" else:\n",
" log_and_print(f\"✅ Order placed for {symbol}\")\n",
"\n",
"def manage_trades(pair1, pair2, lot_size, signals):\n",
" \"\"\"Executes pair trading strategy based on signals.\"\"\"\n",
" log_and_print(f\"📈 Running Pair Trading Strategy for {pair1} & {pair2}\")\n",
"\n",
" latest_signal = signals.iloc[-1]\n",
"\n",
" if latest_signal[\"long_signal\"]:\n",
" log_and_print(f\"📉 Enter SHORT {pair2} & LONG {pair1}\")\n",
" place_order(pair1, lot_size, is_buy=True, magic=MAGIC_NUMBER)\n",
" place_order(pair2, lot_size, is_buy=False, magic=MAGIC_NUMBER)\n",
"\n",
" elif latest_signal[\"short_signal\"]:\n",
" log_and_print(f\"📈 Enter LONG {pair2} & SHORT {pair1}\")\n",
" place_order(pair1, lot_size, is_buy=False, magic=MAGIC_NUMBER)\n",
" place_order(pair2, lot_size, is_buy=True, magic=MAGIC_NUMBER)\n",
"\n",
" elif latest_signal[\"exit_signal\"]:\n",
" log_and_print(f\"❌ Exiting positions for {pair1} & {pair2}\")\n",
" close_positions(pair1, MAGIC_NUMBER)\n",
" close_positions(pair2, MAGIC_NUMBER)\n",
"\n",
"def close_positions(symbol, magic):\n",
" \"\"\"Closes all open positions for a given symbol and magic number.\"\"\"\n",
" positions = mt5.positions_get(symbol=symbol)\n",
" if positions:\n",
" for pos in positions:\n",
" if pos.magic == magic:\n",
" close_request = {\n",
" \"action\": mt5.TRADE_ACTION_DEAL,\n",
" \"symbol\": symbol,\n",
" \"volume\": pos.volume,\n",
" \"type\": mt5.ORDER_TYPE_BUY if pos.type == mt5.ORDER_TYPE_SELL else mt5.ORDER_TYPE_SELL,\n",
" \"position\": pos.ticket,\n",
" \"magic\": magic,\n",
" \"comment\": \"Closing Pair Trade\",\n",
" }\n",
" result = mt5.order_send(close_request)\n",
" if result.retcode == mt5.TRADE_RETCODE_DONE:\n",
" log_and_print(f\"✅ Closed position for {symbol}\")\n",
" else:\n",
" log_and_print(f\"❌ Failed to close position for {symbol}\", is_error=True)\n",
"\n",
"# ---------------------------------------------------------------------------\n",
"# 4) MAIN TRADING LOOP\n",
"# ---------------------------------------------------------------------------\n",
"if __name__ == \"__main__\":\n",
" try:\n",
" if not mt5.initialize(login=name, server=serv, password=key):\n",
" log_and_print(\"Failed to initialize MetaTrader 5\", is_error=True)\n",
" exit()\n",
"\n",
" # Validate Cointegration\n",
" if not check_cointegration(PAIR1, PAIR2, N_BARS):\n",
" log_and_print(f\"⚠️ {PAIR1} & {PAIR2} are NOT cointegrated. Exiting.\", is_error=True)\n",
" mt5.shutdown()\n",
" exit()\n",
"\n",
" while True:\n",
" log_and_print(\"🔄 Fetching New Data & Running Strategy...\")\n",
"\n",
" # Generate signals\n",
" signals = generate_pair_signals(PAIR1, PAIR2)\n",
"\n",
" # Execute trades\n",
" manage_trades(PAIR1, PAIR2, LOT_SIZE, signals)\n",
"\n",
" # Sleep before next check\n",
" time.sleep(SLEEP_TIME)\n",
"\n",
" except KeyboardInterrupt:\n",
" log_and_print(\"Shutdown signal received.\")\n",
" except Exception as e:\n",
" log_and_print(f\"An error occurred: {e}\", is_error=True)\n",
" finally:\n",
" mt5.shutdown()\n",
" log_and_print(\"MetaTrader 5 shutdown completed.\")\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "ml",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python",
"version": "3.11.11"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
@@ -0,0 +1,451 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Loaded pipeline from models/saved_models/best_rf_rd_pipeline.pkl\n",
"Checking market status...\n",
"Market is closed. No actions performed.\n"
]
}
],
"source": [
"# LIVE TRADING CODE FOR REGIME DETECTION CLASSIFICATION\n",
"\n",
"import sys\n",
"import os\n",
"import warnings\n",
"from pathlib import Path\n",
"\n",
"# ---------------------------------------------------------------------------\n",
"# 1) SET PROJECT ROOT AND UPDATE PATH/WORKING DIRECTORY\n",
"# ---------------------------------------------------------------------------\n",
"project_root = Path.cwd().parent.parent # Adjust if your notebook is in notebooks/time_series\n",
"sys.path.append(str(project_root))\n",
"os.chdir(str(project_root))\n",
"warnings.filterwarnings(\"ignore\")\n",
"\n",
"import warnings\n",
"warnings.filterwarnings(\"ignore\")\n",
"\n",
"import MetaTrader5 as mt5\n",
"import pandas as pd\n",
"import numpy as np\n",
"import ta\n",
"from datetime import datetime, timedelta\n",
"import time\n",
"import logging\n",
"import joblib\n",
"\n",
"# Setup logging\n",
"logging.basicConfig(\n",
" filename='models/saved_models/trading_app.log',\n",
" level=logging.INFO,\n",
" format='%(asctime)s %(levelname)s:%(message)s',\n",
" datefmt='%Y-%m-%d %H:%M:%S'\n",
")\n",
"\n",
"def log_and_print(message, is_error=False):\n",
" \"\"\"\n",
" Logs and prints a message.\n",
" If is_error=True, logs at the ERROR level; otherwise logs at INFO level.\n",
" \"\"\"\n",
" if is_error:\n",
" logging.error(message)\n",
" else:\n",
" logging.info(message)\n",
" print(message)\n",
"\n",
"# Update the login credentials and server information accordingly\n",
"name = 66677507\n",
"key = 'ST746$nG38'\n",
"serv = 'ICMarketsSC-Demo'\n",
"\n",
"# Global variables\n",
"SYMBOL = \"EURUSD\"\n",
"LOT_SIZE = 0.01\n",
"TIMEFRAME = mt5.TIMEFRAME_D1\n",
"N_BARS = 50000\n",
"MAGIC_NUMBER = 234003\n",
"SLEEP_TIME = 86400 # 24 hours in seconds\n",
"COMMENT_ML = \"Regime-Detection\"\n",
"\n",
"class TradingApp:\n",
" def __init__(self, symbol, lot_size, magic_number):\n",
" self.symbol = symbol\n",
" self.lot_size = lot_size\n",
" self.magic_number = magic_number\n",
" self.pipeline = None # We'll store the loaded classification pipeline here\n",
" self.last_retrain_time = None\n",
"\n",
" def get_data(self, symbol, n, timeframe):\n",
" \"\"\"\n",
" Fetch the last 'n' bars from MetaTrader 5 for the given timeframe.\n",
" \"\"\"\n",
" rates = mt5.copy_rates_from_pos(symbol, timeframe, 0, n)\n",
" rates_frame = pd.DataFrame(rates)\n",
" rates_frame['time'] = pd.to_datetime(rates_frame['time'], unit='s')\n",
" rates_frame.set_index('time', inplace=True)\n",
" return rates_frame\n",
"\n",
" def add_all_ta_features(self, df):\n",
" \"\"\"\n",
" Add technical analysis features to the DataFrame using the 'ta' library.\n",
" \"\"\"\n",
" df = ta.add_all_ta_features(\n",
" df, open=\"open\", high=\"high\", low=\"low\", close=\"close\", volume=\"tick_volume\", fillna=True\n",
" )\n",
" return df\n",
"\n",
" def load_pipeline(self, pipeline_path):\n",
" \"\"\"\n",
" Loads a pre-trained classification pipeline (e.g., final_production_pipeline.pkl).\n",
" This pipeline is expected to produce SHIFTED labels [0,1,2].\n",
" \"\"\"\n",
" self.pipeline = joblib.load(pipeline_path)\n",
" logging.info(f\"Loaded pipeline from {pipeline_path}\")\n",
" log_and_print(f\"Loaded pipeline from {pipeline_path}\")\n",
"\n",
" def ml_signal_generation(self, symbol, n_bars, timeframe):\n",
" \"\"\"\n",
" Generate buy/sell signals using the loaded classification pipeline.\n",
" The pipeline outputs SHIFTED labels in {0,1,2} => SHIFT them back to {-1,0,+1}.\n",
" We'll interpret +1 => buy, -1 => sell, 0 => no trade.\n",
" \"\"\"\n",
" if self.pipeline is None:\n",
" logging.error(\"No pipeline loaded. Call load_pipeline(...) first.\")\n",
" return False, False, True, True\n",
"\n",
" # 1) Fetch new data\n",
" df = self.get_data(symbol, n_bars, timeframe)\n",
" # 2) Add TA features\n",
" df = self.add_all_ta_features(df)\n",
" df.fillna(method='ffill', inplace=True)\n",
"\n",
" # 3) Prepare the features (the pipeline must handle columns in correct order)\n",
" X_new = df\n",
"\n",
" # 4) Predict SHIFTED classes\n",
" preds_shifted = self.pipeline.predict(X_new)\n",
" # SHIFT them back: 0->-1, 1->0, 2->+1\n",
" preds = preds_shifted - 1\n",
"\n",
" # Get the latest predicted class\n",
" latest_pred = preds[-1]\n",
" # If latest_pred == +1 => buy signal\n",
" # If latest_pred == -1 => sell signal\n",
" # If 0 => no trade\n",
" buy_signal = (latest_pred == 1)\n",
" sell_signal = (latest_pred == -1)\n",
"\n",
" return buy_signal, sell_signal, not buy_signal, not sell_signal\n",
"\n",
" def orders(self, symbol, lot, is_buy=True, id_position=None, sl=None, tp=None):\n",
" \"\"\"\n",
" Send an order (buy/sell) to MetaTrader 5.\n",
" \"\"\"\n",
" symbol_info = mt5.symbol_info(symbol)\n",
" if symbol_info is None:\n",
" log_and_print(f\"Symbol {symbol} not found, can't place order.\", is_error=True)\n",
" return \"Symbol not found\"\n",
"\n",
" # Make sure symbol is visible\n",
" if not symbol_info.visible:\n",
" if not mt5.symbol_select(symbol, True):\n",
" log_and_print(f\"Failed to select symbol {symbol}\", is_error=True)\n",
" return \"Symbol not visible or could not be selected.\"\n",
"\n",
" tick_info = mt5.symbol_info_tick(symbol)\n",
" if tick_info is None:\n",
" log_and_print(f\"Could not get tick info for {symbol}.\", is_error=True)\n",
" return \"Tick info unavailable\"\n",
"\n",
" # Check for valid bid/ask\n",
" if tick_info.bid <= 0 or tick_info.ask <= 0:\n",
" log_and_print(\n",
" f\"Zero or invalid bid/ask for {symbol}: bid={tick_info.bid}, ask={tick_info.ask}\",\n",
" is_error=True\n",
" )\n",
" return \"Invalid prices\"\n",
"\n",
" # LOT SIZE VALIDATION\n",
" lot = max(lot, symbol_info.volume_min)\n",
" step = symbol_info.volume_step\n",
" if step > 0:\n",
" remainder = lot % step\n",
" if remainder != 0:\n",
" lot = lot - remainder + step\n",
" if lot > symbol_info.volume_max:\n",
" lot = symbol_info.volume_max\n",
"\n",
" log_and_print(\n",
" f\"Adjusted lot size to {lot} (min={symbol_info.volume_min}, \"\n",
" f\"step={symbol_info.volume_step}, max={symbol_info.volume_max})\"\n",
" )\n",
"\n",
" # Force ORDER_FILLING_IOC\n",
" filling_mode = mt5.ORDER_FILLING_IOC\n",
"\n",
" order_type = mt5.ORDER_TYPE_BUY if is_buy else mt5.ORDER_TYPE_SELL\n",
" deviation = 20\n",
" order_price = tick_info.ask if is_buy else tick_info.bid\n",
"\n",
" request = {\n",
" \"action\": mt5.TRADE_ACTION_DEAL,\n",
" \"symbol\": symbol,\n",
" \"volume\": lot,\n",
" \"type\": order_type,\n",
" \"deviation\": deviation,\n",
" \"magic\": self.magic_number,\n",
" \"comment\": COMMENT_ML,\n",
" \"type_time\": mt5.ORDER_TIME_GTC,\n",
" \"type_filling\": filling_mode,\n",
" }\n",
"\n",
" if sl is not None:\n",
" request[\"sl\"] = sl\n",
" if tp is not None:\n",
" request[\"tp\"] = tp\n",
" if id_position is not None:\n",
" request[\"position\"] = id_position\n",
"\n",
" log_and_print(f\"Sending order request: {request}\")\n",
" result = mt5.order_send(request)\n",
"\n",
" order_type_str = \"BUY\" if is_buy else \"SELL\"\n",
" if result is None or result.retcode != mt5.TRADE_RETCODE_DONE:\n",
" error_message = f\"Order failed for {symbol}\"\n",
" if result:\n",
" error_message += f\", retcode={result.retcode}, comment={result.comment}\"\n",
" additional_info = (\n",
" f\"Date/Time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\\n\"\n",
" f\"Order Type: {order_type_str}\\n\"\n",
" f\"Lot Size: {lot}\\n\"\n",
" f\"SL: {sl if sl else 'None'}\\n\"\n",
" f\"TP: {tp if tp else 'None'}\\n\"\n",
" f\"Comment: {COMMENT_ML}\\n\"\n",
" f\"Request: {request}\\n\"\n",
" f\"Result: {result}\"\n",
" )\n",
" # If you need notifications, you could log or handle them differently here.\n",
" log_and_print(f\"Order failed details: {additional_info}\", is_error=True)\n",
" else:\n",
" success_message = f\"Order successful for {symbol}, comment={result.comment}\"\n",
" additional_info = (\n",
" f\"Date/Time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\\n\"\n",
" f\"Order Type: {order_type_str}\\n\"\n",
" f\"Lot Size: {lot}\\n\"\n",
" f\"SL: {sl if sl else 'None'}\\n\"\n",
" f\"TP: {tp if tp else 'None'}\\n\"\n",
" f\"Comment: {COMMENT_ML}\"\n",
" )\n",
" # If you need notifications, you could log or handle them differently here.\n",
" log_and_print(success_message)\n",
"\n",
" def get_positions_by_magic(self, symbol, magic_number):\n",
" \"\"\"\n",
" Retrieve positions for a specific symbol and magic number.\n",
" \"\"\"\n",
" all_positions = mt5.positions_get(symbol=symbol)\n",
" if not all_positions:\n",
" log_and_print(\"No positions found.\", is_error=False)\n",
" return []\n",
" return [pos for pos in all_positions if pos.magic == magic_number]\n",
"\n",
" def run_strategy(self, symbol, lot, buy_signal, sell_signal):\n",
" \"\"\"\n",
" Decide whether to open a buy or sell order based on signals,\n",
" close opposite positions if needed, etc.\n",
" \"\"\"\n",
" log_and_print(\"------------------------------------------------------------------\")\n",
" log_and_print(\n",
" f\"Date: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}, \"\n",
" f\"SYMBOL: {symbol}, BUY SIGNAL: {buy_signal}, SELL SIGNAL: {sell_signal}\"\n",
" )\n",
"\n",
" positions = self.get_positions_by_magic(symbol, self.magic_number)\n",
" has_buy = any(pos.type == mt5.POSITION_TYPE_BUY for pos in positions)\n",
" has_sell = any(pos.type == mt5.POSITION_TYPE_SELL for pos in positions)\n",
"\n",
" if buy_signal and not has_buy:\n",
" if has_sell:\n",
" log_and_print(\"Existing sell positions found. Attempting to close...\")\n",
" if self.close_position(symbol, is_buy=True):\n",
" log_and_print(\"Sell positions closed. Placing new buy order.\")\n",
" self.orders(symbol, lot, is_buy=True)\n",
" else:\n",
" log_and_print(\"Failed to close sell positions.\")\n",
" else:\n",
" self.orders(symbol, lot, is_buy=True)\n",
" elif sell_signal and not has_sell:\n",
" if has_buy:\n",
" log_and_print(\"Existing buy positions found. Attempting to close...\")\n",
" if self.close_position(symbol, is_buy=False):\n",
" log_and_print(\"Buy positions closed. Placing new sell order.\")\n",
" self.orders(symbol, lot, is_buy=False)\n",
" else:\n",
" log_and_print(\"Failed to close buy positions.\")\n",
" else:\n",
" self.orders(symbol, lot, is_buy=False)\n",
" else:\n",
" log_and_print(\"Appropriate position already exists or no signal to act on.\")\n",
"\n",
" def close_position(self, symbol, is_buy):\n",
" \"\"\"\n",
" Close all positions of the opposite type for the given symbol & magic.\n",
" \"\"\"\n",
" positions = mt5.positions_get(symbol=symbol)\n",
" if not positions:\n",
" log_and_print(f\"No positions to close for symbol: {symbol}\")\n",
" return False\n",
"\n",
" initial_balance = mt5.account_info().balance\n",
" closed_any = False\n",
"\n",
" for position in positions:\n",
" if position.magic == self.magic_number:\n",
" # if is_buy==True => we want to close SELL positions\n",
" # if is_buy==False => we want to close BUY positions\n",
" if ((is_buy and position.type == mt5.POSITION_TYPE_SELL) or\n",
" (not is_buy and position.type == mt5.POSITION_TYPE_BUY)):\n",
" close_request = {\n",
" \"action\": mt5.TRADE_ACTION_DEAL,\n",
" \"symbol\": symbol,\n",
" \"volume\": position.volume,\n",
" \"type\": mt5.ORDER_TYPE_BUY if position.type == mt5.POSITION_TYPE_SELL else mt5.ORDER_TYPE_SELL,\n",
" \"position\": position.ticket,\n",
" \"deviation\": 20,\n",
" \"magic\": self.magic_number,\n",
" \"comment\": COMMENT_ML,\n",
" \"type_time\": mt5.ORDER_TIME_GTC,\n",
" \"type_filling\": mt5.ORDER_FILLING_RETURN,\n",
" }\n",
" result = mt5.order_send(close_request)\n",
" if result.retcode != mt5.TRADE_RETCODE_DONE:\n",
" error_message = (\n",
" f\"Failed to close position {position.ticket} for {symbol}: {result.retcode}\"\n",
" )\n",
" log_and_print(error_message, is_error=True)\n",
" # If you need notifications, you could log or handle them differently here.\n",
" else:\n",
" log_and_print(f\"Successfully closed position {position.ticket} for {symbol}\")\n",
" closed_any = True\n",
"\n",
" if closed_any:\n",
" final_balance = mt5.account_info().balance\n",
" profit = final_balance - initial_balance\n",
" success_message = f\"Closed positions successfully, Profit: {profit}\"\n",
" log_and_print(success_message)\n",
" return True\n",
"\n",
" return False\n",
"\n",
" def check_and_execute_trades(self):\n",
" \"\"\"\n",
" Called in the main loop: generate signals, run strategy, etc.\n",
" \"\"\"\n",
" mt5.symbol_select(self.symbol, True)\n",
" buy, sell, _, _ = self.ml_signal_generation(self.symbol, N_BARS, TIMEFRAME)\n",
" self.run_strategy(self.symbol, self.lot_size, buy, sell)\n",
" mt5.symbol_select(self.symbol, False)\n",
" log_and_print(\"Waiting for new signals...\")\n",
"\n",
"def is_market_open():\n",
" \"\"\"\n",
" Check if the current time is within the typical Forex trading session, adjusted for CET/CEST.\n",
" Market closes at Friday 10:00 PM CET and opens at Sunday 11:00 PM CET.\n",
" \"\"\"\n",
" current_time_utc = datetime.utcnow()\n",
" current_time_cet = (\n",
" current_time_utc + timedelta(hours=2) \n",
" if time.localtime().tm_isdst \n",
" else current_time_utc + timedelta(hours=1)\n",
" )\n",
"\n",
" # Market closes Friday after 10 PM CET\n",
" if current_time_cet.weekday() == 4 and current_time_cet.hour >= 22:\n",
" return False\n",
" # Market opens Sunday after 11 PM CET\n",
" elif current_time_cet.weekday() == 6 and current_time_cet.hour < 23:\n",
" return False\n",
" # Closed all day Saturday\n",
" elif current_time_cet.weekday() == 5:\n",
" return False\n",
" return True\n",
"\n",
"if __name__ == \"__main__\":\n",
" try:\n",
" if not mt5.initialize(login=name, server=serv, password=key):\n",
" log_and_print(\"Failed to initialize MetaTrader 5\", is_error=True)\n",
" exit()\n",
"\n",
" app = TradingApp(symbol=SYMBOL, lot_size=LOT_SIZE, magic_number=MAGIC_NUMBER)\n",
"\n",
" # 1) Load the classification pipeline\n",
" # Make sure this pipeline is a classification model expecting SHIFTED labels [0,1,2]\n",
" pipeline_path = \"models/saved_models/best_rf_rd_pipeline.pkl\"\n",
" app.load_pipeline(pipeline_path)\n",
"\n",
" while True:\n",
" log_and_print(\"Checking market status...\")\n",
" if is_market_open():\n",
" log_and_print(\"Market is open. Executing trades...\")\n",
"\n",
" # 2) Generate signals using the loaded pipeline\n",
" # This pipeline is classification-based => SHIFTED labels [0,1,2]\n",
" # ml_signal_generation() SHIFTs them back to [-1,0,+1] for signals\n",
" buy_signal, sell_signal, _, _ = app.ml_signal_generation(\n",
" symbol=app.symbol,\n",
" n_bars=N_BARS,\n",
" timeframe=TIMEFRAME\n",
" )\n",
"\n",
" # 3) Run strategy\n",
" app.run_strategy(app.symbol, app.lot_size, buy_signal, sell_signal)\n",
" else:\n",
" log_and_print(\"Market is closed. No actions performed.\")\n",
"\n",
" time.sleep(SLEEP_TIME)\n",
"\n",
" except KeyboardInterrupt:\n",
" log_and_print(\"Shutdown signal received.\")\n",
" # If you need a notification here, handle it (e.g., log, email, etc.).\n",
" except Exception as e:\n",
" error_message = f\"An error occurred: {e}\"\n",
" log_and_print(error_message, is_error=True)\n",
" # If you need a notification here, handle it (e.g., log, email, etc.).\n",
" finally:\n",
" mt5.shutdown()\n",
" log_and_print(\"MetaTrader 5 shutdown completed.\")\n",
" # If you need a notification here, handle it (e.g., log, email, etc.).\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "ml",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.11"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
@@ -0,0 +1,451 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Loaded final pipeline for EURUSD\n",
"Checking market status...\n",
"Market is closed. No actions performed.\n"
]
}
],
"source": [
"import sys\n",
"import os\n",
"import warnings\n",
"from pathlib import Path\n",
"\n",
"# ---------------------------------------------------------------------------\n",
"# 1) SET PROJECT ROOT AND UPDATE PATH/WORKING DIRECTORY\n",
"# ---------------------------------------------------------------------------\n",
"project_root = Path.cwd().parent.parent # Adjust if your notebook is in notebooks/time_series\n",
"sys.path.append(str(project_root))\n",
"os.chdir(str(project_root))\n",
"warnings.filterwarnings(\"ignore\")\n",
"\n",
"import warnings\n",
"warnings.filterwarnings(\"ignore\")\n",
"\n",
"import MetaTrader5 as mt5\n",
"import pandas as pd\n",
"import numpy as np\n",
"import ta\n",
"from datetime import datetime, timedelta\n",
"from sklearn.model_selection import train_test_split\n",
"from sklearn.preprocessing import StandardScaler\n",
"from sklearn.ensemble import RandomForestRegressor\n",
"from sklearn.feature_selection import SelectFromModel\n",
"import time\n",
"import logging\n",
"import joblib\n",
"from data.data_loader import get_data_mt5\n",
"\n",
"# Setup logging\n",
"logging.basicConfig(\n",
" filename='models/saved_models/trading_app.log',\n",
" level=logging.INFO,\n",
" format='%(asctime)s %(levelname)s:%(message)s',\n",
" datefmt='%Y-%m-%d %H:%M:%S'\n",
")\n",
"\n",
"def log_and_print(message, is_error=False):\n",
" \"\"\"\n",
" Logs and prints a message. \n",
" If is_error=True, logs at the ERROR level; otherwise logs at INFO level.\n",
" \"\"\"\n",
" if is_error:\n",
" logging.error(message)\n",
" else:\n",
" logging.info(message)\n",
" print(message)\n",
"\n",
"# Update the login credentials and server information accordingly\n",
"name = 7889999\n",
"key = 'hdgdggFxEG38'\n",
"serv = 'ICMarketsSC-Demo'\n",
"\n",
"# Global variables\n",
"SYMBOL = \"EURUSD\"\n",
"LOT_SIZE = 0.01\n",
"TIMEFRAME = mt5.TIMEFRAME_D1\n",
"N_BARS = 50000\n",
"MAGIC_NUMBER = 234003\n",
"SLEEP_TIME = 86400 # 4 hours in seconds\n",
"COMMENT_ML = \"regression return\"\n",
"\n",
"def select_features_rf_reg(X, y, estimator, max_features=20):\n",
" \"\"\"\n",
" Use a RandomForest (or similar) to select top 'max_features' features.\n",
" \"\"\"\n",
" selector = SelectFromModel(estimator=estimator, threshold=-np.inf, max_features=max_features).fit(X, y)\n",
" X_transformed = selector.transform(X)\n",
" selected_features_mask = selector.get_support()\n",
" return X_transformed, selected_features_mask\n",
"\n",
"class TradingApp:\n",
" def __init__(self, symbol, lot_size, magic_number):\n",
" self.symbol = symbol\n",
" self.lot_size = lot_size\n",
" self.magic_number = magic_number\n",
" self.pipeline = None # We'll store the loaded pipeline here\n",
" self.last_retrain_time = None\n",
"\n",
" def add_all_ta_features(self, df):\n",
" \"\"\"\n",
" Add technical analysis features to the DataFrame using the 'ta' library.\n",
" \"\"\"\n",
" df = ta.add_all_ta_features(\n",
" df,\n",
" open=\"open\",\n",
" high=\"high\",\n",
" low=\"low\",\n",
" close=\"close\",\n",
" volume=\"tick_volume\",\n",
" fillna=True\n",
" )\n",
" return df\n",
"\n",
" def load_pipeline(self, pipeline_path):\n",
" \"\"\"\n",
" Load a pre-trained pipeline (scaler + model + possibly feature selection)\n",
" from disk, e.g. 'best_rf_pipeline.pkl'.\n",
" \"\"\"\n",
" self.pipeline = joblib.load(pipeline_path)\n",
" logging.info(f\"Loaded pipeline from {pipeline_path}\")\n",
"\n",
" def ml_signal_generation(self, symbol, n_bars, timeframe):\n",
" \"\"\"\n",
" Generate buy/sell signals using the loaded pipeline.\n",
" Make sure the pipeline expects the same features as we create below.\n",
" \"\"\"\n",
" if self.pipeline is None:\n",
" logging.error(\"No pipeline loaded. Call load_pipeline(...) first.\")\n",
" return False, False, True, True\n",
"\n",
" # 1) Fetch new data\n",
" df = get_data_mt5(symbol, n_bars, timeframe)\n",
" # 2) Add TA features (if your pipeline doesn't handle feature eng, do it here)\n",
" df = self.add_all_ta_features(df)\n",
" df.fillna(method='ffill', inplace=True)\n",
"\n",
" # 3) Prepare the features (the pipeline will do scaling/selection if included)\n",
" X_new = df # If your pipeline expects specific columns, subset accordingly.\n",
"\n",
" # 4) Predict with the pipeline\n",
" predictions = self.pipeline.predict(X_new)\n",
" latest_pred = predictions[-1] # Get the most recent bar's prediction\n",
"\n",
" buy_signal = latest_pred > 0\n",
" sell_signal = latest_pred < 0\n",
"\n",
" return buy_signal, sell_signal, not buy_signal, not sell_signal\n",
"\n",
" def calculate_future_returns(self, df):\n",
" \"\"\"\n",
" (Optional) Example function to calculate future returns for labeling.\n",
" \"\"\"\n",
" df[\"future_returns\"] = df[\"close\"].pct_change().shift(-1)\n",
" return df.dropna()\n",
"\n",
" def orders(self, symbol, lot, is_buy=True, id_position=None, sl=None, tp=None):\n",
" \"\"\"\n",
" Place an order (BUY or SELL) for the specified symbol and lot size.\n",
" \"\"\"\n",
" symbol_info = mt5.symbol_info(symbol)\n",
" if symbol_info is None:\n",
" log_and_print(f\"Symbol {symbol} not found, can't place order.\", is_error=True)\n",
" return \"Symbol not found\"\n",
"\n",
" # Make sure symbol is selected/visible\n",
" if not symbol_info.visible:\n",
" if not mt5.symbol_select(symbol, True):\n",
" log_and_print(f\"Failed to select symbol {symbol}\", is_error=True)\n",
" return \"Symbol not visible or could not be selected.\"\n",
"\n",
" tick_info = mt5.symbol_info_tick(symbol)\n",
" if tick_info is None:\n",
" log_and_print(f\"Could not get tick info for {symbol}.\", is_error=True)\n",
" return \"Tick info unavailable\"\n",
"\n",
" # Check for valid bid/ask\n",
" if tick_info.bid <= 0 or tick_info.ask <= 0:\n",
" log_and_print(\n",
" f\"Zero or invalid bid/ask for {symbol}: bid={tick_info.bid}, ask={tick_info.ask}\",\n",
" is_error=True\n",
" )\n",
" return \"Invalid prices\"\n",
"\n",
" # ----------- LOT SIZE VALIDATION -----------\n",
" lot = max(lot, symbol_info.volume_min)\n",
" step = symbol_info.volume_step\n",
" if step > 0:\n",
" remainder = lot % step\n",
" if remainder != 0:\n",
" lot = lot - remainder + step\n",
" if lot > symbol_info.volume_max:\n",
" lot = symbol_info.volume_max\n",
"\n",
" log_and_print(\n",
" f\"Adjusted lot size to {lot} (min={symbol_info.volume_min}, \"\n",
" f\"step={symbol_info.volume_step}, max={symbol_info.volume_max})\"\n",
" )\n",
"\n",
" # ----------- FORCE ORDER_FILLING_IOC -----------\n",
" filling_mode = 1 # ORDER_FILLING_IOC\n",
"\n",
" order_type = mt5.ORDER_TYPE_BUY if is_buy else mt5.ORDER_TYPE_SELL\n",
" deviation = 20\n",
"\n",
" request = {\n",
" \"action\": mt5.TRADE_ACTION_DEAL,\n",
" \"symbol\": symbol,\n",
" \"volume\": lot,\n",
" \"type\": order_type,\n",
" \"deviation\": deviation,\n",
" \"magic\": self.magic_number,\n",
" \"comment\": COMMENT_ML,\n",
" \"type_time\": mt5.ORDER_TIME_GTC,\n",
" \"type_filling\": filling_mode,\n",
" }\n",
"\n",
" if sl is not None:\n",
" request[\"sl\"] = sl\n",
" if tp is not None:\n",
" request[\"tp\"] = tp\n",
" if id_position is not None:\n",
" request[\"position\"] = id_position\n",
"\n",
" log_and_print(f\"Sending order request: {request}\")\n",
" result = mt5.order_send(request)\n",
"\n",
" order_type_str = \"BUY\" if is_buy else \"SELL\"\n",
" if result is None or result.retcode != mt5.TRADE_RETCODE_DONE:\n",
" error_message = f\"Order failed for {symbol}\"\n",
" if result:\n",
" error_message += f\", retcode={result.retcode}, comment={result.comment}\"\n",
" additional_info = (\n",
" f\"Date/Time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\\n\"\n",
" f\"Order Type: {order_type_str}\\n\"\n",
" f\"Lot Size: {lot}\\n\"\n",
" f\"SL: {sl if sl else 'None'}\\n\"\n",
" f\"TP: {tp if tp else 'None'}\\n\"\n",
" f\"Comment: {COMMENT_ML}\\n\"\n",
" f\"Request: {request}\\n\"\n",
" f\"Result: {result}\"\n",
" )\n",
" log_and_print(f\"Order failed details: {additional_info}\", is_error=True)\n",
" else:\n",
" success_message = f\"Order successful for {symbol}, comment={result.comment}\"\n",
" additional_info = (\n",
" f\"Date/Time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\\n\"\n",
" f\"Order Type: {order_type_str}\\n\"\n",
" f\"Lot Size: {lot}\\n\"\n",
" f\"SL: {sl if sl else 'None'}\\n\"\n",
" f\"TP: {tp if tp else 'None'}\\n\"\n",
" f\"Comment: {COMMENT_ML}\"\n",
" )\n",
" log_and_print(success_message)\n",
"\n",
" def get_positions_by_magic(self, symbol, magic_number):\n",
" \"\"\"\n",
" Retrieve open positions for the specified symbol and magic number.\n",
" \"\"\"\n",
" all_positions = mt5.positions_get(symbol=symbol)\n",
" if not all_positions:\n",
" log_and_print(\"No positions found.\", is_error=False)\n",
" return []\n",
" return [pos for pos in all_positions if pos.magic == magic_number]\n",
"\n",
" def run_strategy(self, symbol, lot, buy_signal, sell_signal):\n",
" \"\"\"\n",
" Based on buy/sell signals, decide whether to open or close positions.\n",
" \"\"\"\n",
" log_and_print(\"------------------------------------------------------------------\")\n",
" log_and_print(\n",
" f\"Date: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}, \"\n",
" f\"SYMBOL: {symbol}, BUY SIGNAL: {buy_signal}, SELL SIGNAL: {sell_signal}\"\n",
" )\n",
"\n",
" # Retrieve positions based on the magic number to manage trades specific to this instance\n",
" positions = self.get_positions_by_magic(symbol, self.magic_number)\n",
" has_buy = any(pos.type == mt5.POSITION_TYPE_BUY for pos in positions)\n",
" has_sell = any(pos.type == mt5.POSITION_TYPE_SELL for pos in positions)\n",
"\n",
" # Decision making based on current signals and existing positions\n",
" if buy_signal and not has_buy:\n",
" if has_sell:\n",
" log_and_print(\"Existing sell positions found. Attempting to close...\")\n",
" if self.close_position(symbol, is_buy=True):\n",
" log_and_print(\"Sell positions closed. Placing new buy order.\")\n",
" self.orders(symbol, lot, is_buy=True)\n",
" else:\n",
" log_and_print(\"Failed to close sell positions.\")\n",
" else:\n",
" self.orders(symbol, lot, is_buy=True)\n",
" elif sell_signal and not has_sell:\n",
" if has_buy:\n",
" log_and_print(\"Existing buy positions found. Attempting to close...\")\n",
" if self.close_position(symbol, is_buy=False):\n",
" log_and_print(\"Buy positions closed. Placing new sell order.\")\n",
" self.orders(symbol, lot, is_buy=False)\n",
" else:\n",
" log_and_print(\"Failed to close buy positions.\")\n",
" else:\n",
" self.orders(symbol, lot, is_buy=False)\n",
" else:\n",
" log_and_print(\"Appropriate position already exists or no signal to act on.\")\n",
"\n",
" def close_position(self, symbol, is_buy):\n",
" \"\"\"\n",
" Closes positions of the opposite type (BUY/SELL) for this app's magic number.\n",
" \"\"\"\n",
" positions = mt5.positions_get(symbol=symbol)\n",
" if not positions:\n",
" log_and_print(f\"No positions to close for symbol: {symbol}\")\n",
" return False\n",
"\n",
" initial_balance = mt5.account_info().balance\n",
" closed_any = False\n",
"\n",
" for position in positions:\n",
" # Close positions of the opposite type with the same magic number\n",
" if position.magic == self.magic_number and (\n",
" (is_buy and position.type == mt5.POSITION_TYPE_SELL) or\n",
" (not is_buy and position.type == mt5.POSITION_TYPE_BUY)\n",
" ):\n",
" close_request = {\n",
" \"action\": mt5.TRADE_ACTION_DEAL,\n",
" \"symbol\": symbol,\n",
" \"volume\": position.volume,\n",
" \"type\": mt5.ORDER_TYPE_BUY if position.type == mt5.POSITION_TYPE_SELL else mt5.ORDER_TYPE_SELL,\n",
" \"position\": position.ticket,\n",
" \"deviation\": 20,\n",
" \"magic\": self.magic_number,\n",
" \"comment\": COMMENT_ML,\n",
" \"type_time\": mt5.ORDER_TIME_GTC,\n",
" \"type_filling\": mt5.ORDER_FILLING_RETURN,\n",
" }\n",
" result = mt5.order_send(close_request)\n",
" if result.retcode != mt5.TRADE_RETCODE_DONE:\n",
" error_message = (\n",
" f\"Failed to close position {position.ticket} for {symbol}: {result.retcode}\"\n",
" )\n",
" log_and_print(error_message, is_error=True)\n",
" else:\n",
" log_and_print(f\"Successfully closed position {position.ticket} for {symbol}\")\n",
" closed_any = True\n",
"\n",
" if closed_any:\n",
" final_balance = mt5.account_info().balance\n",
" profit = final_balance - initial_balance\n",
" success_message = f\"Closed positions successfully, Profit: {profit}\"\n",
" log_and_print(success_message)\n",
" return True\n",
"\n",
" return False\n",
"\n",
" def check_and_execute_trades(self):\n",
" \"\"\"\n",
" Convenience method to perform the entire flow: \n",
" generate signals, run strategy, and deselect symbol.\n",
" \"\"\"\n",
" mt5.symbol_select(self.symbol, True)\n",
" buy, sell, _, _ = self.ml_signal_generation(self.symbol, N_BARS, TIMEFRAME)\n",
" self.run_strategy(self.symbol, self.lot_size, buy, sell)\n",
" mt5.symbol_select(self.symbol, False)\n",
" log_and_print(\"Waiting for new signals...\")\n",
"\n",
"def is_market_open():\n",
" \"\"\"\n",
" Check if the current time is within typical Forex trading session hours (CET/CEST).\n",
" - Closes: Friday 10:00 PM CET\n",
" - Opens: Sunday 11:00 PM CET\n",
" - Closed all day Saturday\n",
" \"\"\"\n",
" current_time_utc = datetime.utcnow()\n",
" # Adjust for CET (UTC+1) or CEST (UTC+2)\n",
" current_time_cet = current_time_utc + timedelta(hours=2) if time.localtime().tm_isdst else current_time_utc + timedelta(hours=1)\n",
"\n",
" # Friday after 10 PM CET\n",
" if current_time_cet.weekday() == 4 and current_time_cet.hour >= 22:\n",
" return False\n",
" # Sunday before 11 PM CET\n",
" elif current_time_cet.weekday() == 6 and current_time_cet.hour < 23:\n",
" return False\n",
" # All day Saturday\n",
" elif current_time_cet.weekday() == 5:\n",
" return False\n",
" return True\n",
"\n",
"if __name__ == \"__main__\":\n",
" try:\n",
" if not mt5.initialize(login=name, server=serv, password=key):\n",
" log_and_print(\"Failed to initialize MetaTrader 5\", is_error=True)\n",
" exit()\n",
"\n",
" app = TradingApp(symbol=SYMBOL, lot_size=LOT_SIZE, magic_number=MAGIC_NUMBER)\n",
"\n",
" # Load a previously trained pipeline (scaler + model, etc.)\n",
" pipeline_path = \"models/saved_models/best_rf_pipeline.pkl\"\n",
" app.load_pipeline(pipeline_path)\n",
" log_and_print(f\"Loaded final pipeline for {app.symbol}\")\n",
"\n",
" while True:\n",
" log_and_print(\"Checking market status...\")\n",
" if is_market_open():\n",
" log_and_print(\"Market is open. Executing trades...\")\n",
"\n",
" # Generate signals using the loaded pipeline\n",
" buy_signal, sell_signal, _, _ = app.ml_signal_generation(\n",
" symbol=app.symbol,\n",
" n_bars=N_BARS,\n",
" timeframe=TIMEFRAME\n",
" )\n",
"\n",
" # Run strategy\n",
" app.run_strategy(app.symbol, app.lot_size, buy_signal, sell_signal)\n",
" else:\n",
" log_and_print(\"Market is closed. No actions performed.\")\n",
"\n",
" # Sleep for the configured interval (e.g., 4 hours)\n",
" time.sleep(SLEEP_TIME)\n",
"\n",
" except KeyboardInterrupt:\n",
" log_and_print(\"Shutdown signal received.\")\n",
" except Exception as e:\n",
" error_message = f\"An error occurred: {e}\"\n",
" log_and_print(error_message, is_error=True)\n",
" finally:\n",
" mt5.shutdown()\n",
" log_and_print(\"MetaTrader 5 shutdown completed.\")\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "ml",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.11"
}
},
"nbformat": 4,
"nbformat_minor": 2
}