{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Loaded pipeline from models/saved_models/best_rf_mb_pipeline.pkl\n", "Checking market status...\n", "Market is closed. No actions performed.\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", "\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", "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 = \"RFFV-D\"\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", "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 '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_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., 'best_rf_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} => we 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", "\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 # The pipeline must handle columns in the correct order.\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 => do nothing\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", " 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 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", " 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", " 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 want 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 want 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", " Run the trading strategy logic based on buy/sell 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(\"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 = f\"Failed to close position {position.ticket} for {symbol}: {result.retcode}\"\n", " log_and_print(error_message, is_error=True)\n", " # If you want 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", " 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", "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", " pipeline_path = \"models/saved_models/best_rf_mb_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 }