{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.11","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":96164,"databundleVersionId":11418275,"sourceType":"competition"}],"dockerImageVersionId":31040,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Import libraries","metadata":{}},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport gc\n\nfrom lightgbm import LGBMRegressor, early_stopping, log_evaluation\nfrom sklearn.decomposition import PCA\nfrom sklearn.model_selection import TimeSeriesSplit, KFold\nfrom sklearn.metrics import mean_squared_error\nfrom scipy.stats import pearsonr\nfrom scipy.stats.mstats import winsorize","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-05-24T13:34:28.004348Z","iopub.execute_input":"2025-05-24T13:34:28.005172Z","iopub.status.idle":"2025-05-24T13:34:36.927134Z","shell.execute_reply.started":"2025-05-24T13:34:28.005133Z","shell.execute_reply":"2025-05-24T13:34:36.926303Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Load datasets","metadata":{}},{"cell_type":"code","source":"train_df = pd.read_parquet(\"/kaggle/input/drw-crypto-market-prediction/train.parquet\")\ntest_df = pd.read_parquet(\"/kaggle/input/drw-crypto-market-prediction/test.parquet\")\nsample_submission = pd.read_csv(\"/kaggle/input/drw-crypto-market-prediction/sample_submission.csv\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-24T13:34:36.928891Z","iopub.execute_input":"2025-05-24T13:34:36.929542Z","iopub.status.idle":"2025-05-24T13:35:44.387165Z","shell.execute_reply.started":"2025-05-24T13:34:36.929516Z","shell.execute_reply":"2025-05-24T13:35:44.385963Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"SEED = 42\nN_SPLITS = 5\nTEST_SIZE = 20000\nDEBUG = False","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-24T13:35:44.388672Z","iopub.execute_input":"2025-05-24T13:35:44.389044Z","iopub.status.idle":"2025-05-24T13:35:44.395135Z","shell.execute_reply.started":"2025-05-24T13:35:44.389010Z","shell.execute_reply":"2025-05-24T13:35:44.394066Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Feature engineering","metadata":{}},{"cell_type":"code","source":"# x = test_df.index\n# y = test_df['volume']\n\n# plt.plot(x, y)\n# plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-24T13:35:44.397340Z","iopub.execute_input":"2025-05-24T13:35:44.397813Z","iopub.status.idle":"2025-05-24T13:35:44.417683Z","shell.execute_reply.started":"2025-05-24T13:35:44.397791Z","shell.execute_reply":"2025-05-24T13:35:44.416713Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# x_cols = [col for col in train_df.columns if col.startswith(\"X\")]\n# corrs = train_df[x_cols + ['label']].corr()['label'].sort_values(ascending=False)\n# print(corrs.head(20))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-24T13:35:44.418768Z","iopub.execute_input":"2025-05-24T13:35:44.419032Z","iopub.status.idle":"2025-05-24T13:35:44.440419Z","shell.execute_reply.started":"2025-05-24T13:35:44.419012Z","shell.execute_reply":"2025-05-24T13:35:44.439270Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"x_cols = [col for col in train_df.columns if col.startswith(\"X\")]\nX_raw = train_df[x_cols].replace([np.inf, -np.inf], np.nan).fillna(0)\nx_var_series = X_raw.var().sort_values(ascending=False)\ntop_x_cols = x_var_series[x_var_series > 0].head(50).index.tolist()\n\ndel X_raw\ngc.collect()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-24T13:35:44.441316Z","iopub.execute_input":"2025-05-24T13:35:44.441600Z","iopub.status.idle":"2025-05-24T13:36:07.744850Z","shell.execute_reply.started":"2025-05-24T13:35:44.441579Z","shell.execute_reply":"2025-05-24T13:36:07.744081Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def add_new_features(df):\n    df = df.copy()\n    \n    df[\"bid_ask_diff\"] = df[\"bid_qty\"] - df[\"ask_qty\"]\n    df[\"buy_sell_ratio\"] = df[\"buy_qty\"] / (df[\"sell_qty\"] + 1e-6)\n    df[\"bid_ask_ratio\"] = df[\"bid_qty\"] / (df[\"ask_qty\"] + 1e-6)\n    df[\"buy_volume_ratio\"] = df[\"buy_qty\"] / (df[\"volume\"] + 1e-6)\n    df[\"sell_volume_ratio\"] = df[\"sell_qty\"] / (df[\"volume\"] + 1e-6)\n\n    df[\"sell_buy_ratio\"] = df[\"sell_qty\"] / (df[\"buy_qty\"] + df[\"sell_qty\"] + 1e-9)\n    df[\"buy_sell_diff\"] = df[\"buy_qty\"] - df[\"sell_qty\"]\n    df[\"buy_sell_sum\"] = df[\"buy_qty\"] + df[\"sell_qty\"]\n    df[\"ask_bid_ratio\"] = df[\"ask_qty\"] / (df[\"bid_qty\"] + df[\"ask_qty\"] + 1e-9)\n    df[\"bid_ask_sum\"] = df[\"bid_qty\"] + df[\"ask_qty\"]\n\n    df[\"order_pressure\"] = (df[\"buy_qty\"] - df[\"sell_qty\"]) / (df[\"buy_qty\"] + df[\"sell_qty\"] + 1e-6)\n    df[\"quoted_pressure\"] = (df[\"bid_qty\"] - df[\"ask_qty\"]) / (df[\"bid_qty\"] + df[\"ask_qty\"] + 1e-6)\n    df[\"execution_ratio\"] = (df[\"buy_qty\"] + df[\"sell_qty\"]) / (df[\"bid_qty\"] + df[\"ask_qty\"] + 1e-6)\n    df[\"volume_imbalance\"] = (df[\"buy_qty\"] - df[\"sell_qty\"]) / (df[\"volume\"] + 1e-6)\n    df[\"order_book_total\"] = df[\"bid_qty\"] + df[\"ask_qty\"]\n    df[\"execution_total\"] = df[\"buy_qty\"] + df[\"sell_qty\"]\n    df[\"execution_share\"] = df[\"execution_total\"] / (df[\"order_book_total\"] + 1e-6)\n    \n    # X_raw = df[top_x_cols]\n    # df[\"X_mean\"] = X_raw.mean(axis=1)\n    # df[\"X_std\"] = X_raw.std(axis=1)\n    # df[\"X_min\"] = X_raw.min(axis=1)\n    # df[\"X_max\"] = X_raw.max(axis=1)\n    # df[\"X_q25\"] = X_raw.quantile(0.25, axis=1)\n    # df[\"X_q75\"] = X_raw.quantile(0.75, axis=1)\n    # df[\"X_skew\"] = X_raw.skew(axis=1)\n    # df[\"X_kurt\"] = X_raw.kurtosis(axis=1)\n    \n    return df\n\ntrain_df = add_new_features(train_df)\ntest_df = add_new_features(test_df)\n\n# train_df = add_new_features(train_df, top_x_cols)\n# test_df = add_new_features(test_df, top_x_cols)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-24T13:36:07.745584Z","iopub.execute_input":"2025-05-24T13:36:07.745802Z","iopub.status.idle":"2025-05-24T13:36:10.757830Z","shell.execute_reply.started":"2025-05-24T13:36:07.745784Z","shell.execute_reply":"2025-05-24T13:36:10.757070Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# start_date = '2024-01-01'\n# end_date = '2024-01-02'\n# period_df = train_df.loc[start_date:end_date, ['label', 'bid_qty', 'ask_qty', 'buy_qty', 'sell_qty', 'volume']]\n\n# fig, ax1 = plt.subplots(figsize=(15, 7))\n\n# for col in ['bid_qty', 'ask_qty', 'buy_qty', 'sell_qty', 'volume']:\n#     ax1.plot(period_df.index, period_df[col], label=col)\n# ax1.set_ylabel('Quantity / Volume')\n# ax1.grid(True)\n\n# ax2 = ax1.twinx()\n# ax2.plot(period_df.index, period_df['label'], color='black', linestyle='--', label='label')\n# ax2.set_ylabel('Label')\n\n# lines_1, labels_1 = ax1.get_legend_handles_labels()\n# lines_2, labels_2 = ax2.get_legend_handles_labels()\n# ax1.legend(lines_1 + lines_2, labels_1 + labels_2, loc='upper left')\n\n# plt.title(f'Time Series Plot from {start_date} to {end_date}')\n# plt.tight_layout()\n# plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-24T13:36:10.758725Z","iopub.execute_input":"2025-05-24T13:36:10.758996Z","iopub.status.idle":"2025-05-24T13:36:10.763380Z","shell.execute_reply.started":"2025-05-24T13:36:10.758974Z","shell.execute_reply":"2025-05-24T13:36:10.762487Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"features = [col for col in train_df.drop(columns=[\"label\"]).columns if 'X' not in col]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-24T13:36:10.764377Z","iopub.execute_input":"2025-05-24T13:36:10.764617Z","iopub.status.idle":"2025-05-24T13:36:11.836459Z","shell.execute_reply.started":"2025-05-24T13:36:10.764591Z","shell.execute_reply":"2025-05-24T13:36:11.835484Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Clustering\nfrom sklearn.cluster import KMeans\nfrom sklearn.preprocessing import StandardScaler\n\nkm_features = features\n\nscaler = StandardScaler()\nX_train_scaled = scaler.fit_transform(train_df[km_features])\n\nkmeans = KMeans(n_clusters=5, random_state=SEED)\ntrain_df['cluster'] = kmeans.fit_predict(X_train_scaled)\n\nX_test_scaled = scaler.transform(test_df[km_features])\ntest_df['cluster'] = kmeans.predict(X_test_scaled)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-24T13:36:11.839846Z","iopub.execute_input":"2025-05-24T13:36:11.840107Z","iopub.status.idle":"2025-05-24T13:36:20.236761Z","shell.execute_reply.started":"2025-05-24T13:36:11.840088Z","shell.execute_reply":"2025-05-24T13:36:20.236071Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"hhmmm...","metadata":{}},{"cell_type":"markdown","source":"# Modeling","metadata":{}},{"cell_type":"code","source":"# Select features\nfeatures = [\n    \"bid_qty\", \"ask_qty\", \"buy_qty\", \"sell_qty\", \"volume\",\n    \"bid_ask_diff\", \"buy_sell_ratio\", \"bid_ask_ratio\",\n    \"buy_volume_ratio\", \"sell_volume_ratio\",\n    \"sell_buy_ratio\", \"buy_sell_diff\", \"buy_sell_sum\",\n    \"ask_bid_ratio\", \"bid_ask_sum\", \"order_pressure\",\n    \"quoted_pressure\", \"execution_ratio\", \"volume_imbalance\",\n    \"order_book_total\", \"execution_total\", \"execution_share\",\n    \"cluster\"\n] + top_x_cols\n# features = [col for col in train_df.drop(columns=[\"label\"]).columns if 'X' not in col]\n# features = train_df.drop(columns=[\"label\"]).columns + top_x_cols\ntarget = 'label'","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-24T13:36:20.237387Z","iopub.execute_input":"2025-05-24T13:36:20.237629Z","iopub.status.idle":"2025-05-24T13:36:20.244099Z","shell.execute_reply.started":"2025-05-24T13:36:20.237609Z","shell.execute_reply":"2025-05-24T13:36:20.243131Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"X = train_df[features]\n\n# clipping\ny = train_df[target].clip(-3, 3)\n\n# using winsorize\n# lower_limit = 0.01\n# upper_limit = 0.01\n# y_raw = train_df[target].values\n# y_winsor = winsorize(y_raw, limits=(lower_limit, upper_limit))\n# y = pd.Series(y_winsor.data, index=train_df.index)\n\n# transform the target variable using log1p \n# y_raw = train_df[target].values\n# shift = abs(np.min(y_raw)) + 1e-3\n# y_shifted = y_raw + shift\n\n# y_log = np.log1p(y_shifted)\n# y = pd.Series(y_log, index=train_df.index)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-24T13:36:20.244952Z","iopub.execute_input":"2025-05-24T13:36:20.245201Z","iopub.status.idle":"2025-05-24T13:36:20.388682Z","shell.execute_reply.started":"2025-05-24T13:36:20.245181Z","shell.execute_reply":"2025-05-24T13:36:20.387737Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"params = {\n    \"objective\": \"huber\", \n    \"metric\": \"rmse\",\n    \"learning_rate\": 0.01,\n    \"n_estimators\": 3000,\n    \"num_leaves\": 64, \n    \"max_depth\": -1, \n    \"min_child_samples\": 100,\n    \"min_split_gain\": 0.02,\n    \"subsample\": 0.8,        \n    \"colsample_bytree\": 0.8,\n    \"reg_alpha\": 3.0,        \n    \"reg_lambda\": 3.0,       \n    \"feature_fraction\": 0.8,\n    \"random_state\": SEED,\n    \"verbosity\": -1,\n    \"force_col_wise\": True\n}","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-24T13:36:20.390063Z","iopub.execute_input":"2025-05-24T13:36:20.390367Z","iopub.status.idle":"2025-05-24T13:36:20.395573Z","shell.execute_reply.started":"2025-05-24T13:36:20.390345Z","shell.execute_reply":"2025-05-24T13:36:20.394590Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# # TimeSeriesSplit\n# tscv = TimeSeriesSplit(n_splits=N_SPLITS, test_size=TEST_SIZE)\n\nkf = KFold(n_splits=N_SPLITS, shuffle=False)\n\nmodels = []\nval_scores = []\n\nfor fold, (train_idx, val_idx) in enumerate(kf.split(X)):\n    print(f\"\\n========= Fold {fold + 1} =========\")\n    X_train, X_val = X.iloc[train_idx], X.iloc[val_idx]\n    y_train, y_val = y.iloc[train_idx], y.iloc[val_idx]\n\n    model = LGBMRegressor(**params)\n\n    model.fit(\n        X_train,\n        y_train,\n        eval_set=[(X_val, y_val)],\n        eval_metric=\"rmse\",\n        callbacks=[\n            early_stopping(stopping_rounds=100),\n            log_evaluation(period=0)\n        ]\n    )\n\n    y_val_pred = model.predict(X_val)\n\n    # Z-score normalization for Pearson correlation\n    if y_val_pred.std() > 0:\n        y_val_pred = (y_val_pred - y_val_pred.mean()) / y_val_pred.std()\n        \n    val_pearson = pearsonr(y_val, y_val_pred)[0]\n    val_rmse = mean_squared_error(y_val, y_val_pred, squared=False)\n    print(f\"✅ Fold {fold+1} Pearson: {val_pearson:.4f} / RMSE: {val_rmse:.4f}\")\n\n    val_scores.append(val_pearson)\n    models.append(model)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-24T13:36:20.396527Z","iopub.execute_input":"2025-05-24T13:36:20.396809Z","iopub.status.idle":"2025-05-24T13:37:50.350144Z","shell.execute_reply.started":"2025-05-24T13:36:20.396789Z","shell.execute_reply":"2025-05-24T13:37:50.349088Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(f\"\\n✅ Average CV Pearson: {np.mean(val_scores):.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-24T13:37:50.351290Z","iopub.execute_input":"2025-05-24T13:37:50.352165Z","iopub.status.idle":"2025-05-24T13:37:50.356989Z","shell.execute_reply.started":"2025-05-24T13:37:50.352137Z","shell.execute_reply":"2025-05-24T13:37:50.356110Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Prediction & Submit","metadata":{}},{"cell_type":"code","source":"submit_score = []\n\nfor fold_, model in enumerate(models):\n    pred_ = model.predict(test_df[features])\n    submit_score.append(pred_)\n    # pred_log = model.predict(test_df[features])\n    # pred = np.expm1(pred_log) - shift\n    # submit_score.append(pred)\n\n# predict test data\npred = np.mean(submit_score, axis=0)\n\n# Z-score normalization for Pearson correlation\nif pred.std() > 0:\n    pred = (pred - pred.mean()) / pred.std()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-24T13:37:50.357980Z","iopub.execute_input":"2025-05-24T13:37:50.358300Z","iopub.status.idle":"2025-05-24T13:38:00.304290Z","shell.execute_reply.started":"2025-05-24T13:37:50.358279Z","shell.execute_reply":"2025-05-24T13:38:00.303346Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"submission = pd.DataFrame({\n    'ID': sample_submission.ID,\n    'prediction': pred\n})\n\n# Save\nsubmission.to_csv('submission.csv', index=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-24T13:38:00.305288Z","iopub.execute_input":"2025-05-24T13:38:00.305570Z","iopub.status.idle":"2025-05-24T13:38:01.510472Z","shell.execute_reply.started":"2025-05-24T13:38:00.305548Z","shell.execute_reply":"2025-05-24T13:38:01.509555Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"submission","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-24T13:38:01.511345Z","iopub.execute_input":"2025-05-24T13:38:01.511604Z","iopub.status.idle":"2025-05-24T13:38:01.534808Z","shell.execute_reply.started":"2025-05-24T13:38:01.511585Z","shell.execute_reply":"2025-05-24T13:38:01.533763Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}