Initial commit
This commit is contained in:
@@ -0,0 +1,461 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Loaded pipeline from models/saved_models/best_rf_mb_pipeline.pkl\n",
|
||||
"Checking market status...\n",
|
||||
"Market is closed. No actions performed.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# LIVE TRADING CODE FOR MULTI-BAR CLASSIFICATION\n",
|
||||
"\n",
|
||||
"import sys\n",
|
||||
"import os\n",
|
||||
"import warnings\n",
|
||||
"from pathlib import Path\n",
|
||||
"\n",
|
||||
"# ---------------------------------------------------------------------------\n",
|
||||
"# 1) SET PROJECT ROOT AND UPDATE PATH/WORKING DIRECTORY\n",
|
||||
"# ---------------------------------------------------------------------------\n",
|
||||
"project_root = Path.cwd().parent.parent # Adjust if your notebook is in notebooks/time_series\n",
|
||||
"sys.path.append(str(project_root))\n",
|
||||
"os.chdir(str(project_root))\n",
|
||||
"warnings.filterwarnings(\"ignore\")\n",
|
||||
"\n",
|
||||
"import warnings\n",
|
||||
"warnings.filterwarnings(\"ignore\")\n",
|
||||
"import MetaTrader5 as mt5\n",
|
||||
"import pandas as pd\n",
|
||||
"import numpy as np\n",
|
||||
"import ta\n",
|
||||
"from datetime import datetime, timedelta\n",
|
||||
"import time\n",
|
||||
"import logging\n",
|
||||
"import joblib\n",
|
||||
"\n",
|
||||
"# Setup logging\n",
|
||||
"logging.basicConfig(\n",
|
||||
" filename='models/saved_models/trading_app1.log',\n",
|
||||
" level=logging.INFO,\n",
|
||||
" format='%(asctime)s %(levelname)s:%(message)s',\n",
|
||||
" datefmt='%Y-%m-%d %H:%M:%S'\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"def log_and_print(message, is_error=False):\n",
|
||||
" \"\"\"\n",
|
||||
" Logs and prints a message.\n",
|
||||
" If is_error=True, logs at the ERROR level; otherwise logs at INFO level.\n",
|
||||
" \"\"\"\n",
|
||||
" if is_error:\n",
|
||||
" logging.error(message)\n",
|
||||
" else:\n",
|
||||
" logging.info(message)\n",
|
||||
" print(message)\n",
|
||||
"\n",
|
||||
"# Update the login credentials and server information accordingly\n",
|
||||
"name = 66677507\n",
|
||||
"key = 'ST746$nG38'\n",
|
||||
"serv = 'ICMarketsSC-Demo'\n",
|
||||
"\n",
|
||||
"# Global variables\n",
|
||||
"SYMBOL = \"EURUSD\"\n",
|
||||
"LOT_SIZE = 0.01\n",
|
||||
"TIMEFRAME = mt5.TIMEFRAME_D1\n",
|
||||
"N_BARS = 50000\n",
|
||||
"MAGIC_NUMBER = 234003\n",
|
||||
"SLEEP_TIME = 86400 # 24 hours in seconds\n",
|
||||
"COMMENT_ML = \"RFFV-D\"\n",
|
||||
"\n",
|
||||
"# If you still need feature selection, you can keep this helper function:\n",
|
||||
"def select_features_rf_reg(X, y, estimator, max_features=20):\n",
|
||||
" \"\"\"\n",
|
||||
" Example helper function for feature selection using RandomForest.\n",
|
||||
" \"\"\"\n",
|
||||
" from sklearn.feature_selection import SelectFromModel\n",
|
||||
" selector = SelectFromModel(estimator=estimator, threshold=-np.inf, max_features=max_features).fit(X, y)\n",
|
||||
" X_transformed = selector.transform(X)\n",
|
||||
" selected_features_mask = selector.get_support()\n",
|
||||
" return X_transformed, selected_features_mask\n",
|
||||
"\n",
|
||||
"class TradingApp:\n",
|
||||
" def __init__(self, symbol, lot_size, magic_number):\n",
|
||||
" self.symbol = symbol\n",
|
||||
" self.lot_size = lot_size\n",
|
||||
" self.magic_number = magic_number\n",
|
||||
" self.pipeline = None # We'll store the loaded classification pipeline here\n",
|
||||
" self.last_retrain_time = None\n",
|
||||
"\n",
|
||||
" def get_data(self, symbol, n, timeframe):\n",
|
||||
" \"\"\"\n",
|
||||
" Fetch 'n' bars of historical data for the given symbol and timeframe.\n",
|
||||
" \"\"\"\n",
|
||||
" rates = mt5.copy_rates_from_pos(symbol, timeframe, 0, n)\n",
|
||||
" rates_frame = pd.DataFrame(rates)\n",
|
||||
" rates_frame['time'] = pd.to_datetime(rates_frame['time'], unit='s')\n",
|
||||
" rates_frame.set_index('time', inplace=True)\n",
|
||||
" return rates_frame\n",
|
||||
"\n",
|
||||
" def add_all_ta_features(self, df):\n",
|
||||
" \"\"\"\n",
|
||||
" Add technical analysis features to the DataFrame using the 'ta' library.\n",
|
||||
" \"\"\"\n",
|
||||
" df = ta.add_all_ta_features(\n",
|
||||
" df, open=\"open\", high=\"high\", low=\"low\", close=\"close\", volume=\"tick_volume\", fillna=True\n",
|
||||
" )\n",
|
||||
" return df\n",
|
||||
"\n",
|
||||
" def load_pipeline(self, pipeline_path):\n",
|
||||
" \"\"\"\n",
|
||||
" Loads a pre-trained classification pipeline (e.g., 'best_rf_pipeline.pkl').\n",
|
||||
" This pipeline is expected to produce SHIFTED labels [0,1,2].\n",
|
||||
" \"\"\"\n",
|
||||
" self.pipeline = joblib.load(pipeline_path)\n",
|
||||
" logging.info(f\"Loaded pipeline from {pipeline_path}\")\n",
|
||||
" log_and_print(f\"Loaded pipeline from {pipeline_path}\")\n",
|
||||
"\n",
|
||||
" def ml_signal_generation(self, symbol, n_bars, timeframe):\n",
|
||||
" \"\"\"\n",
|
||||
" Generate buy/sell signals using the loaded classification pipeline.\n",
|
||||
" The pipeline outputs SHIFTED labels in {0,1,2} => we SHIFT them back to {-1,0,+1}.\n",
|
||||
" We'll interpret +1 => buy, -1 => sell, 0 => no trade.\n",
|
||||
" \"\"\"\n",
|
||||
" if self.pipeline is None:\n",
|
||||
" logging.error(\"No pipeline loaded. Call load_pipeline(...) first.\")\n",
|
||||
" return False, False, True, True\n",
|
||||
"\n",
|
||||
" # 1) Fetch new data\n",
|
||||
" df = self.get_data(symbol, n_bars, timeframe)\n",
|
||||
"\n",
|
||||
" # 2) Add TA features\n",
|
||||
" df = self.add_all_ta_features(df)\n",
|
||||
" df.fillna(method='ffill', inplace=True)\n",
|
||||
"\n",
|
||||
" # 3) Prepare the features\n",
|
||||
" X_new = df # The pipeline must handle columns in the correct order.\n",
|
||||
"\n",
|
||||
" # 4) Predict SHIFTED classes\n",
|
||||
" preds_shifted = self.pipeline.predict(X_new)\n",
|
||||
" # SHIFT them back: 0->-1, 1->0, 2->+1\n",
|
||||
" preds = preds_shifted - 1\n",
|
||||
"\n",
|
||||
" # Get the latest predicted class\n",
|
||||
" latest_pred = preds[-1]\n",
|
||||
" # If latest_pred == +1 => buy signal\n",
|
||||
" # If latest_pred == -1 => sell signal\n",
|
||||
" # If 0 => do nothing\n",
|
||||
" buy_signal = (latest_pred == 1)\n",
|
||||
" sell_signal = (latest_pred == -1)\n",
|
||||
"\n",
|
||||
" return buy_signal, sell_signal, not buy_signal, not sell_signal\n",
|
||||
"\n",
|
||||
" def orders(self, symbol, lot, is_buy=True, id_position=None, sl=None, tp=None):\n",
|
||||
" \"\"\"\n",
|
||||
" Place an order (BUY or SELL) for the specified symbol and lot size.\n",
|
||||
" \"\"\"\n",
|
||||
" symbol_info = mt5.symbol_info(symbol)\n",
|
||||
" if symbol_info is None:\n",
|
||||
" log_and_print(f\"Symbol {symbol} not found, can't place order.\", is_error=True)\n",
|
||||
" return \"Symbol not found\"\n",
|
||||
"\n",
|
||||
" # Make sure symbol is visible\n",
|
||||
" if not symbol_info.visible:\n",
|
||||
" if not mt5.symbol_select(symbol, True):\n",
|
||||
" log_and_print(f\"Failed to select symbol {symbol}\", is_error=True)\n",
|
||||
" return \"Symbol not visible or could not be selected.\"\n",
|
||||
"\n",
|
||||
" tick_info = mt5.symbol_info_tick(symbol)\n",
|
||||
" if tick_info is None:\n",
|
||||
" log_and_print(f\"Could not get tick info for {symbol}.\", is_error=True)\n",
|
||||
" return \"Tick info unavailable\"\n",
|
||||
"\n",
|
||||
" # Check for valid bid/ask\n",
|
||||
" if tick_info.bid <= 0 or tick_info.ask <= 0:\n",
|
||||
" log_and_print(\n",
|
||||
" f\"Zero or invalid bid/ask for {symbol}: bid={tick_info.bid}, ask={tick_info.ask}\",\n",
|
||||
" is_error=True\n",
|
||||
" )\n",
|
||||
" return \"Invalid prices\"\n",
|
||||
"\n",
|
||||
" # LOT SIZE VALIDATION\n",
|
||||
" lot = max(lot, symbol_info.volume_min)\n",
|
||||
" step = symbol_info.volume_step\n",
|
||||
" if step > 0:\n",
|
||||
" remainder = lot % step\n",
|
||||
" if remainder != 0:\n",
|
||||
" lot = lot - remainder + step\n",
|
||||
" if lot > symbol_info.volume_max:\n",
|
||||
" lot = symbol_info.volume_max\n",
|
||||
"\n",
|
||||
" log_and_print(\n",
|
||||
" f\"Adjusted lot size to {lot} (min={symbol_info.volume_min}, \"\n",
|
||||
" f\"step={symbol_info.volume_step}, max={symbol_info.volume_max})\"\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
" # Force ORDER_FILLING_IOC\n",
|
||||
" filling_mode = 1 # ORDER_FILLING_IOC\n",
|
||||
"\n",
|
||||
" order_type = mt5.ORDER_TYPE_BUY if is_buy else mt5.ORDER_TYPE_SELL\n",
|
||||
" order_price = tick_info.ask if is_buy else tick_info.bid\n",
|
||||
" deviation = 20\n",
|
||||
"\n",
|
||||
" request = {\n",
|
||||
" \"action\": mt5.TRADE_ACTION_DEAL,\n",
|
||||
" \"symbol\": symbol,\n",
|
||||
" \"volume\": lot,\n",
|
||||
" \"type\": order_type,\n",
|
||||
" \"deviation\": deviation,\n",
|
||||
" \"magic\": self.magic_number,\n",
|
||||
" \"comment\": COMMENT_ML,\n",
|
||||
" \"type_time\": mt5.ORDER_TIME_GTC,\n",
|
||||
" \"type_filling\": filling_mode,\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
" if sl is not None:\n",
|
||||
" request[\"sl\"] = sl\n",
|
||||
" if tp is not None:\n",
|
||||
" request[\"tp\"] = tp\n",
|
||||
" if id_position is not None:\n",
|
||||
" request[\"position\"] = id_position\n",
|
||||
"\n",
|
||||
" log_and_print(f\"Sending order request: {request}\")\n",
|
||||
" result = mt5.order_send(request)\n",
|
||||
"\n",
|
||||
" order_type_str = \"BUY\" if is_buy else \"SELL\"\n",
|
||||
" if result is None or result.retcode != mt5.TRADE_RETCODE_DONE:\n",
|
||||
" error_message = f\"Order failed for {symbol}\"\n",
|
||||
" if result:\n",
|
||||
" error_message += f\", retcode={result.retcode}, comment={result.comment}\"\n",
|
||||
" additional_info = (\n",
|
||||
" f\"Date/Time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\\n\"\n",
|
||||
" f\"Order Type: {order_type_str}\\n\"\n",
|
||||
" f\"Lot Size: {lot}\\n\"\n",
|
||||
" f\"SL: {sl if sl else 'None'}\\n\"\n",
|
||||
" f\"TP: {tp if tp else 'None'}\\n\"\n",
|
||||
" f\"Comment: {COMMENT_ML}\\n\"\n",
|
||||
" f\"Request: {request}\\n\"\n",
|
||||
" f\"Result: {result}\"\n",
|
||||
" )\n",
|
||||
" # If you want notifications, you could log or handle them differently here.\n",
|
||||
" log_and_print(f\"Order failed details: {additional_info}\", is_error=True)\n",
|
||||
" else:\n",
|
||||
" success_message = f\"Order successful for {symbol}, comment={result.comment}\"\n",
|
||||
" additional_info = (\n",
|
||||
" f\"Date/Time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\\n\"\n",
|
||||
" f\"Order Type: {order_type_str}\\n\"\n",
|
||||
" f\"Lot Size: {lot}\\n\"\n",
|
||||
" f\"SL: {sl if sl else 'None'}\\n\"\n",
|
||||
" f\"TP: {tp if tp else 'None'}\\n\"\n",
|
||||
" f\"Comment: {COMMENT_ML}\"\n",
|
||||
" )\n",
|
||||
" # If you want notifications, you could log or handle them differently here.\n",
|
||||
" log_and_print(success_message)\n",
|
||||
"\n",
|
||||
" def get_positions_by_magic(self, symbol, magic_number):\n",
|
||||
" \"\"\"\n",
|
||||
" Retrieve positions for a specific symbol and magic number.\n",
|
||||
" \"\"\"\n",
|
||||
" all_positions = mt5.positions_get(symbol=symbol)\n",
|
||||
" if not all_positions:\n",
|
||||
" log_and_print(\"No positions found.\", is_error=False)\n",
|
||||
" return []\n",
|
||||
" return [pos for pos in all_positions if pos.magic == magic_number]\n",
|
||||
"\n",
|
||||
" def run_strategy(self, symbol, lot, buy_signal, sell_signal):\n",
|
||||
" \"\"\"\n",
|
||||
" Run the trading strategy logic based on buy/sell signals.\n",
|
||||
" \"\"\"\n",
|
||||
" log_and_print(\"------------------------------------------------------------------\")\n",
|
||||
" log_and_print(\n",
|
||||
" f\"Date: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}, \"\n",
|
||||
" f\"SYMBOL: {symbol}, BUY SIGNAL: {buy_signal}, SELL SIGNAL: {sell_signal}\"\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
" positions = self.get_positions_by_magic(symbol, self.magic_number)\n",
|
||||
" has_buy = any(pos.type == mt5.POSITION_TYPE_BUY for pos in positions)\n",
|
||||
" has_sell = any(pos.type == mt5.POSITION_TYPE_SELL for pos in positions)\n",
|
||||
"\n",
|
||||
" if buy_signal and not has_buy:\n",
|
||||
" if has_sell:\n",
|
||||
" log_and_print(\"Existing sell positions found. Attempting to close...\")\n",
|
||||
" if self.close_position(symbol, is_buy=True):\n",
|
||||
" log_and_print(\"Sell positions closed. Placing new buy order.\")\n",
|
||||
" self.orders(symbol, lot, is_buy=True)\n",
|
||||
" else:\n",
|
||||
" log_and_print(\"Failed to close sell positions.\")\n",
|
||||
" else:\n",
|
||||
" self.orders(symbol, lot, is_buy=True)\n",
|
||||
" elif sell_signal and not has_sell:\n",
|
||||
" if has_buy:\n",
|
||||
" log_and_print(\"Existing buy positions found. Attempting to close...\")\n",
|
||||
" if self.close_position(symbol, is_buy=False):\n",
|
||||
" log_and_print(\"Buy positions closed. Placing new sell order.\")\n",
|
||||
" self.orders(symbol, lot, is_buy=False)\n",
|
||||
" else:\n",
|
||||
" log_and_print(\"Failed to close buy positions.\")\n",
|
||||
" else:\n",
|
||||
" self.orders(symbol, lot, is_buy=False)\n",
|
||||
" else:\n",
|
||||
" log_and_print(\"Appropriate position already exists or no signal to act on.\")\n",
|
||||
"\n",
|
||||
" def close_position(self, symbol, is_buy):\n",
|
||||
" \"\"\"\n",
|
||||
" Closes positions of the opposite type (BUY/SELL) for this app's magic number.\n",
|
||||
" \"\"\"\n",
|
||||
" positions = mt5.positions_get(symbol=symbol)\n",
|
||||
" if not positions:\n",
|
||||
" log_and_print(f\"No positions to close for symbol: {symbol}\")\n",
|
||||
" return False\n",
|
||||
"\n",
|
||||
" initial_balance = mt5.account_info().balance\n",
|
||||
" closed_any = False\n",
|
||||
"\n",
|
||||
" for position in positions:\n",
|
||||
" # Close positions of the opposite type with the same magic number\n",
|
||||
" if position.magic == self.magic_number and (\n",
|
||||
" (is_buy and position.type == mt5.POSITION_TYPE_SELL) or\n",
|
||||
" (not is_buy and position.type == mt5.POSITION_TYPE_BUY)\n",
|
||||
" ):\n",
|
||||
" close_request = {\n",
|
||||
" \"action\": mt5.TRADE_ACTION_DEAL,\n",
|
||||
" \"symbol\": symbol,\n",
|
||||
" \"volume\": position.volume,\n",
|
||||
" \"type\": mt5.ORDER_TYPE_BUY if position.type == mt5.POSITION_TYPE_SELL else mt5.ORDER_TYPE_SELL,\n",
|
||||
" \"position\": position.ticket,\n",
|
||||
" \"deviation\": 20,\n",
|
||||
" \"magic\": self.magic_number,\n",
|
||||
" \"comment\": COMMENT_ML,\n",
|
||||
" \"type_time\": mt5.ORDER_TIME_GTC,\n",
|
||||
" \"type_filling\": mt5.ORDER_FILLING_RETURN,\n",
|
||||
" }\n",
|
||||
" result = mt5.order_send(close_request)\n",
|
||||
" if result.retcode != mt5.TRADE_RETCODE_DONE:\n",
|
||||
" error_message = f\"Failed to close position {position.ticket} for {symbol}: {result.retcode}\"\n",
|
||||
" log_and_print(error_message, is_error=True)\n",
|
||||
" # If you want notifications, you could log or handle them differently here.\n",
|
||||
" else:\n",
|
||||
" log_and_print(f\"Successfully closed position {position.ticket} for {symbol}\")\n",
|
||||
" closed_any = True\n",
|
||||
"\n",
|
||||
" if closed_any:\n",
|
||||
" final_balance = mt5.account_info().balance\n",
|
||||
" profit = final_balance - initial_balance\n",
|
||||
" success_message = f\"Closed positions successfully, Profit: {profit}\"\n",
|
||||
" log_and_print(success_message)\n",
|
||||
" return True\n",
|
||||
"\n",
|
||||
" return False\n",
|
||||
"\n",
|
||||
" def check_and_execute_trades(self):\n",
|
||||
" \"\"\"\n",
|
||||
" Convenience method to perform the entire flow:\n",
|
||||
" generate signals, run strategy, and deselect symbol.\n",
|
||||
" \"\"\"\n",
|
||||
" mt5.symbol_select(self.symbol, True)\n",
|
||||
" buy, sell, _, _ = self.ml_signal_generation(self.symbol, N_BARS, TIMEFRAME)\n",
|
||||
" self.run_strategy(self.symbol, self.lot_size, buy, sell)\n",
|
||||
" mt5.symbol_select(self.symbol, False)\n",
|
||||
" log_and_print(\"Waiting for new signals...\")\n",
|
||||
"\n",
|
||||
"def is_market_open():\n",
|
||||
" \"\"\"\n",
|
||||
" Check if the current time is within the typical Forex trading session, adjusted for CET/CEST.\n",
|
||||
" Market closes at Friday 10:00 PM CET and opens at Sunday 11:00 PM CET. \n",
|
||||
" It is closed all day Saturday.\n",
|
||||
" \"\"\"\n",
|
||||
" current_time_utc = datetime.utcnow()\n",
|
||||
" # Adjust for Central European Time (UTC+1) or Central European Summer Time (UTC+2)\n",
|
||||
" current_time_cet = (\n",
|
||||
" current_time_utc + timedelta(hours=2) \n",
|
||||
" if time.localtime().tm_isdst \n",
|
||||
" else current_time_utc + timedelta(hours=1)\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
" # Friday after 10 PM CET\n",
|
||||
" if current_time_cet.weekday() == 4 and current_time_cet.hour >= 22:\n",
|
||||
" return False\n",
|
||||
" # Sunday before 11 PM CET\n",
|
||||
" elif current_time_cet.weekday() == 6 and current_time_cet.hour < 23:\n",
|
||||
" return False\n",
|
||||
" # All day Saturday\n",
|
||||
" elif current_time_cet.weekday() == 5:\n",
|
||||
" return False\n",
|
||||
" return True\n",
|
||||
"\n",
|
||||
"if __name__ == \"__main__\":\n",
|
||||
" try:\n",
|
||||
" if not mt5.initialize(login=name, server=serv, password=key):\n",
|
||||
" log_and_print(\"Failed to initialize MetaTrader 5\", is_error=True)\n",
|
||||
" exit()\n",
|
||||
"\n",
|
||||
" app = TradingApp(symbol=SYMBOL, lot_size=LOT_SIZE, magic_number=MAGIC_NUMBER)\n",
|
||||
"\n",
|
||||
" # 1) Load the classification pipeline\n",
|
||||
" pipeline_path = \"models/saved_models/best_rf_mb_pipeline.pkl\"\n",
|
||||
" app.load_pipeline(pipeline_path)\n",
|
||||
"\n",
|
||||
" while True:\n",
|
||||
" log_and_print(\"Checking market status...\")\n",
|
||||
" if is_market_open():\n",
|
||||
" log_and_print(\"Market is open. Executing trades...\")\n",
|
||||
"\n",
|
||||
" # 2) Generate signals using the loaded pipeline\n",
|
||||
" # This pipeline is classification-based => SHIFTED labels [0,1,2]\n",
|
||||
" # ml_signal_generation() SHIFTs them back to [-1,0,+1] for signals\n",
|
||||
" buy_signal, sell_signal, _, _ = app.ml_signal_generation(\n",
|
||||
" symbol=app.symbol,\n",
|
||||
" n_bars=N_BARS,\n",
|
||||
" timeframe=TIMEFRAME\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
" # 3) Run strategy\n",
|
||||
" app.run_strategy(app.symbol, app.lot_size, buy_signal, sell_signal)\n",
|
||||
" else:\n",
|
||||
" log_and_print(\"Market is closed. No actions performed.\")\n",
|
||||
"\n",
|
||||
" time.sleep(SLEEP_TIME)\n",
|
||||
"\n",
|
||||
" except KeyboardInterrupt:\n",
|
||||
" log_and_print(\"Shutdown signal received.\")\n",
|
||||
" # If you need a notification here, handle it (e.g., log, email, etc.).\n",
|
||||
" except Exception as e:\n",
|
||||
" error_message = f\"An error occurred: {e}\"\n",
|
||||
" log_and_print(error_message, is_error=True)\n",
|
||||
" # If you need a notification here, handle it (e.g., log, email, etc.).\n",
|
||||
" finally:\n",
|
||||
" mt5.shutdown()\n",
|
||||
" log_and_print(\"MetaTrader 5 shutdown completed.\")\n",
|
||||
" # If you need a notification here, handle it (e.g., log, email, etc.).\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "ml",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.11"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
Reference in New Issue
Block a user