feat: Organize notebooks and add stationarity checks
This commit is contained in:
@@ -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
+778
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user