{"metadata":{"kernelspec":{"display_name":"Python 3","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"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":96164,"databundleVersionId":11418275,"sourceType":"competition"}],"dockerImageVersionId":31041,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true},"papermill":{"default_parameters":{},"duration":55.407478,"end_time":"2025-06-24T00:12:07.467568","environment_variables":{},"exception":null,"input_path":"__notebook__.ipynb","output_path":"__notebook__.ipynb","parameters":{},"start_time":"2025-06-24T00:11:12.060090","version":"2.6.0"}},"nbformat_minor":4,"nbformat":4,"cells":[{"id":"b53f837b","cell_type":"markdown","source":"<a id='Imports'></a>\n# Imports","metadata":{"papermill":{"duration":0.002252,"end_time":"2025-06-24T00:11:16.112116","exception":false,"start_time":"2025-06-24T00:11:16.109864","status":"completed"},"tags":[]}},{"id":"a1c498ad","cell_type":"code","source":"import sys\nimport pandas as pd\nimport numpy as np\nfrom sklearn.model_selection import KFold\nfrom xgboost import XGBRegressor\nfrom lightgbm import LGBMRegressor\nfrom scipy.stats import pearsonr\nimport shap","metadata":{"execution":{"iopub.status.busy":"2025-06-27T17:23:08.523328Z","iopub.execute_input":"2025-06-27T17:23:08.524283Z","iopub.status.idle":"2025-06-27T17:23:08.528572Z","shell.execute_reply.started":"2025-06-27T17:23:08.524252Z","shell.execute_reply":"2025-06-27T17:23:08.527840Z"},"papermill":{"duration":6.955002,"end_time":"2025-06-24T00:11:23.069583","exception":false,"start_time":"2025-06-24T00:11:16.114581","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"ea40a3b0","cell_type":"markdown","source":"<a id='feature'></a>\n# Feature Engineering","metadata":{"papermill":{"duration":0.002445,"end_time":"2025-06-24T00:11:23.074825","exception":false,"start_time":"2025-06-24T00:11:23.072380","status":"completed"},"tags":[]}},{"id":"fcc6b247","cell_type":"code","source":"def feature_engineering(df):\n\n    df['volume_weighted_sell'] = df['sell_qty'] * df['volume']\n    df['buy_sell_ratio'] = df['buy_qty'] / (df['sell_qty'] + 1e-8)\n    df['selling_pressure'] = df['sell_qty'] / (df['volume'] + 1e-8)\n    df['effective_spread_proxy'] = np.abs(df['buy_qty'] - df['sell_qty']) / (df['volume'] + 1e-8)\n\n\n    df = df.replace([np.inf, -np.inf], np.nan)\n    df = df.fillna(0)\n    return df ","metadata":{"execution":{"iopub.status.busy":"2025-06-27T17:23:08.529675Z","iopub.execute_input":"2025-06-27T17:23:08.530087Z","iopub.status.idle":"2025-06-27T17:23:08.542452Z","shell.execute_reply.started":"2025-06-27T17:23:08.530071Z","shell.execute_reply":"2025-06-27T17:23:08.541827Z"},"papermill":{"duration":0.008475,"end_time":"2025-06-24T00:11:23.085787","exception":false,"start_time":"2025-06-24T00:11:23.077312","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"7882aa2f","cell_type":"markdown","source":"<a id='Config'></a>\n# Configuration","metadata":{"papermill":{"duration":0.002299,"end_time":"2025-06-24T00:11:23.090462","exception":false,"start_time":"2025-06-24T00:11:23.088163","status":"completed"},"tags":[]}},{"id":"4083ebce","cell_type":"code","source":"class Config:\n    TRAIN_PATH = \"/kaggle/input/drw-crypto-market-prediction/train.parquet\"\n    TEST_PATH = \"/kaggle/input/drw-crypto-market-prediction/test.parquet\"\n    SUBMISSION_PATH = \"/kaggle/input/drw-crypto-market-prediction/sample_submission.csv\"\n\n    FEATURES = [\n        \"X863\", \"X856\", \"X598\", \"X862\", \"X385\", \"X852\", \"X603\", \"X860\", \"X674\",\n        \"X415\", \"X345\", \"X855\", \"X174\", \"X302\", \"X178\", \"X168\", \"X612\",\n        \"buy_qty\", \"sell_qty\", \"volume\", \"X888\", \"X421\", \"X333\",\"X292\",\n    ]\n\n    LABEL_COLUMN = \"label\"\n    N_FOLDS = 3\n    RANDOM_STATE = 42\n\n\nXGB_PARAMS ={'tree_method': 'hist', 'device': 'gpu','n_jobs': -1,\n            'random_state': Config.RANDOM_STATE,\n             'colsample_bytree': 0.4111224922845363, 'colsample_bynode': 0.28869302181383194,\n             'gamma': 1.4665430311056709, 'learning_rate': 0.014053505540364681, \n             'max_depth': 7, 'max_leaves': 40, 'n_estimators': 500,\n             'reg_alpha': 27.791606770656145, 'reg_lambda': 84.90603428439086,\n             'subsample': 0.06567}\n\n\nLEARNERS = [\n    {\"name\": \"xgb\", \"Estimator\": XGBRegressor, \"params\": XGB_PARAMS},\n]","metadata":{"execution":{"iopub.status.busy":"2025-06-27T17:23:08.543797Z","iopub.execute_input":"2025-06-27T17:23:08.543995Z","iopub.status.idle":"2025-06-27T17:23:08.555214Z","shell.execute_reply.started":"2025-06-27T17:23:08.543980Z","shell.execute_reply":"2025-06-27T17:23:08.554539Z"},"papermill":{"duration":0.00968,"end_time":"2025-06-24T00:11:23.102581","exception":false,"start_time":"2025-06-24T00:11:23.092901","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"4a4e29f2","cell_type":"markdown","source":"<a id='load'></a>\n# Loading Data","metadata":{"papermill":{"duration":0.002178,"end_time":"2025-06-24T00:11:23.107141","exception":false,"start_time":"2025-06-24T00:11:23.104963","status":"completed"},"tags":[]}},{"id":"7cd7a65e","cell_type":"code","source":"def create_time_decay_weights(n: int, decay: float = 0.9) -> np.ndarray:\n    positions = np.arange(n)\n    normalized = positions / (n - 1)\n    weights = decay ** (1.0 - normalized)\n    return weights * n / weights.sum()\n\ndef load_data():\n    train_df = pd.read_parquet(Config.TRAIN_PATH, columns=Config.FEATURES + [Config.LABEL_COLUMN])\n    test_df = pd.read_parquet(Config.TEST_PATH, columns=Config.FEATURES)\n    submission_df = pd.read_csv(Config.SUBMISSION_PATH)\n\n    train_df = feature_engineering(train_df)\n    test_df = feature_engineering(test_df)\n    print(f\"Loaded data - Train: {train_df.shape}, Test: {test_df.shape}, Submission: {submission_df.shape}\")\n    return train_df.reset_index(drop=True), test_df.reset_index(drop=True), submission_df","metadata":{"execution":{"iopub.status.busy":"2025-06-27T17:23:08.555868Z","iopub.execute_input":"2025-06-27T17:23:08.556055Z","iopub.status.idle":"2025-06-27T17:23:08.572633Z","shell.execute_reply.started":"2025-06-27T17:23:08.556042Z","shell.execute_reply":"2025-06-27T17:23:08.571772Z"},"papermill":{"duration":0.008213,"end_time":"2025-06-24T00:11:23.117703","exception":false,"start_time":"2025-06-24T00:11:23.109490","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"edd5d9df","cell_type":"code","source":"Config.FEATURES += [\"bid_qty\", \"ask_qty\", \"buy_qty\", \"sell_qty\", \"volume\"]\nConfig.FEATURES = list(set(Config.FEATURES))  # remove duplicates","metadata":{"execution":{"iopub.status.busy":"2025-06-27T17:23:08.573974Z","iopub.execute_input":"2025-06-27T17:23:08.574478Z","iopub.status.idle":"2025-06-27T17:23:08.583952Z","shell.execute_reply.started":"2025-06-27T17:23:08.574461Z","shell.execute_reply":"2025-06-27T17:23:08.583230Z"},"papermill":{"duration":0.007114,"end_time":"2025-06-24T00:11:23.127160","exception":false,"start_time":"2025-06-24T00:11:23.120046","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"425d1405","cell_type":"markdown","source":"<a id='Train'></a>\n# Training and Evaluation","metadata":{"papermill":{"duration":0.002193,"end_time":"2025-06-24T00:11:23.131716","exception":false,"start_time":"2025-06-24T00:11:23.129523","status":"completed"},"tags":[]}},{"id":"7bdd4008","cell_type":"code","source":"def get_model_slices(n_samples: int):\n    return [\n        {\"name\": \"full_data\", \"cutoff\": 0},\n        {\"name\": \"last_75pct\", \"cutoff\": int(0.25 * n_samples)},\n        {\"name\": \"last_50pct\", \"cutoff\": int(0.50 * n_samples)},\n    ]\n\ndef train_and_evaluate(train_df, test_df):\n    n_samples = len(train_df)\n    model_slices = get_model_slices(n_samples)\n    models=[]\n    model_names=[]\n    oof_preds = {\n        learner[\"name\"]: {s[\"name\"]: np.zeros(n_samples) for s in model_slices}\n        for learner in LEARNERS\n    }\n    test_preds = {\n        learner[\"name\"]: {s[\"name\"]: np.zeros(len(test_df)) for s in model_slices}\n        for learner in LEARNERS\n    }\n\n    full_weights = create_time_decay_weights(n_samples)\n    kf = KFold(n_splits=Config.N_FOLDS, shuffle=False)\n\n    for fold, (train_idx, valid_idx) in enumerate(kf.split(train_df), start=1):\n        print(f\"\\n--- Fold {fold}/{Config.N_FOLDS} ---\")\n        X_valid = train_df.iloc[valid_idx][Config.FEATURES]\n        y_valid = train_df.iloc[valid_idx][Config.LABEL_COLUMN]\n\n        for s in model_slices:\n            cutoff = s[\"cutoff\"]\n            slice_name = s[\"name\"]\n            subset = train_df.iloc[cutoff:].reset_index(drop=True)\n            rel_idx = train_idx[train_idx >= cutoff] - cutoff\n\n            X_train = subset.iloc[rel_idx][Config.FEATURES]\n            y_train = subset.iloc[rel_idx][Config.LABEL_COLUMN]\n            sw = create_time_decay_weights(len(subset))[rel_idx] if cutoff > 0 else full_weights[train_idx]\n            X_train_np = X_train.values\n            y_train_np = y_train.values\n            X_valid_np = X_valid.values\n            y_valid_np = y_valid.values\n            print(f\"  Training slice: {slice_name}, samples: {len(X_train)}\")\n\n            for learner in LEARNERS:\n                model = learner[\"Estimator\"](**learner[\"params\"])\n                model.fit(X_train_np, y_train_np, sample_weight=sw, \n                          eval_set=[(X_valid_np, y_valid_np)], verbose=False)\n                models.append(model)\n                model_name = f\"fold: {fold} slice:{slice_name}\"  # Use f-string formatting\n                model_names.append(model_name)\n                mask = valid_idx >= cutoff\n                if mask.any():\n                    idxs = valid_idx[mask]\n                    oof_preds[learner[\"name\"]][slice_name][idxs] = model.predict(train_df.iloc[idxs][Config.FEATURES].values)\n                if cutoff > 0 and (~mask).any():\n                    oof_preds[learner[\"name\"]][slice_name][valid_idx[~mask]] = oof_preds[learner[\"name\"]][\"full_data\"][valid_idx[~mask]]\n\n                test_preds[learner[\"name\"]][slice_name] += model.predict(test_df[Config.FEATURES])\n\n    # Normalize test predictions\n    for learner_name in test_preds:\n        for slice_name in test_preds[learner_name]:\n            test_preds[learner_name][slice_name] /= Config.N_FOLDS\n\n    return oof_preds, test_preds, model_slices, models, model_names","metadata":{"execution":{"iopub.status.busy":"2025-06-27T17:23:08.778738Z","iopub.execute_input":"2025-06-27T17:23:08.778930Z","iopub.status.idle":"2025-06-27T17:23:08.789647Z","shell.execute_reply.started":"2025-06-27T17:23:08.778916Z","shell.execute_reply":"2025-06-27T17:23:08.788874Z"},"papermill":{"duration":0.012782,"end_time":"2025-06-24T00:11:23.146837","exception":false,"start_time":"2025-06-24T00:11:23.134055","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"86c5cfe6","cell_type":"markdown","source":"<a id='Subm'></a>\n# Submission","metadata":{"papermill":{"duration":0.002222,"end_time":"2025-06-24T00:11:23.151439","exception":false,"start_time":"2025-06-24T00:11:23.149217","status":"completed"},"tags":[]}},{"id":"5699f1ea","cell_type":"code","source":"def ensemble_and_submit(train_df, oof_preds, test_preds, submission_df):\n    learner_ensembles = {}\n    for learner_name in oof_preds:\n        scores = {s: pearsonr(train_df[Config.LABEL_COLUMN], oof_preds[learner_name][s])[0]\n                  for s in oof_preds[learner_name]}\n        total_score = sum(scores.values())\n\n        oof_simple = np.mean(list(oof_preds[learner_name].values()), axis=0)\n        test_simple = np.mean(list(test_preds[learner_name].values()), axis=0)\n        score_simple = pearsonr(train_df[Config.LABEL_COLUMN], oof_simple)[0]\n\n        oof_weighted = sum(scores[s] / total_score * oof_preds[learner_name][s] for s in scores)\n        test_weighted = sum(scores[s] / total_score * test_preds[learner_name][s] for s in scores)\n        score_weighted = pearsonr(train_df[Config.LABEL_COLUMN], oof_weighted)[0]\n\n        print(f\"\\n{learner_name.upper()} Simple Ensemble Pearson:   {score_simple:.4f}\")\n        print(f\"{learner_name.upper()} Weighted Ensemble Pearson: {score_weighted:.4f}\")\n\n        learner_ensembles[learner_name] = {\n            \"oof_simple\": oof_simple,\n            \"test_simple\": test_simple\n        }\n\n    final_oof = np.mean([le[\"oof_simple\"] for le in learner_ensembles.values()], axis=0)\n    final_test = np.mean([le[\"test_simple\"] for le in learner_ensembles.values()], axis=0)\n    final_score = pearsonr(train_df[Config.LABEL_COLUMN], final_oof)[0]\n\n    print(f\"\\nFINAL ensemble across learners Pearson: {final_score:.4f}\")\n\n    submission_df[\"prediction\"] = final_test\n    submission_df.to_csv(\"submission.csv\", index=False)\n    print(\"Saved: submission.csv\")","metadata":{"execution":{"iopub.status.busy":"2025-06-27T17:23:08.790737Z","iopub.execute_input":"2025-06-27T17:23:08.790947Z","iopub.status.idle":"2025-06-27T17:23:08.806965Z","shell.execute_reply.started":"2025-06-27T17:23:08.790933Z","shell.execute_reply":"2025-06-27T17:23:08.806275Z"},"papermill":{"duration":0.009874,"end_time":"2025-06-24T00:11:23.163694","exception":false,"start_time":"2025-06-24T00:11:23.153820","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"97808378","cell_type":"markdown","source":"<a id='Main'></a>\n# Main ","metadata":{"papermill":{"duration":0.002308,"end_time":"2025-06-24T00:11:23.168367","exception":false,"start_time":"2025-06-24T00:11:23.166059","status":"completed"},"tags":[]}},{"id":"792e0d2b","cell_type":"code","source":"if __name__ == \"__main__\":\n    train_df, test_df, submission_df = load_data()\n    oof_preds, test_preds, model_slices, models, model_names = train_and_evaluate(train_df, test_df)\n    ensemble_and_submit(train_df, oof_preds, test_preds, submission_df)\n","metadata":{"execution":{"iopub.status.busy":"2025-06-27T17:23:08.807690Z","iopub.execute_input":"2025-06-27T17:23:08.807924Z","iopub.status.idle":"2025-06-27T17:23:48.577558Z","shell.execute_reply.started":"2025-06-27T17:23:08.807905Z","shell.execute_reply":"2025-06-27T17:23:48.576668Z"},"papermill":{"duration":43.469897,"end_time":"2025-06-24T00:12:06.640627","exception":false,"start_time":"2025-06-24T00:11:23.170730","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"f98e5078-916c-4cb3-860a-f3684d074cd9","cell_type":"code","source":"print_shap=True ##set for False to avoid SHAP analysis\n\n## only for 2 first models\nif print_shap:\n    for md_ix in [0,1]:\n        \n        print(f\"\\nGenerating SHAP analysis for the model {model_names[md_ix]}...\")\n        explainer = shap.TreeExplainer(models[md_ix], feature_perturbation=\"tree_path_dependent\", model_output=\"raw\")\n        shap_values = explainer.shap_values(test_df[Config.FEATURES])\n        shap.summary_plot(shap_values, test_df[Config.FEATURES], max_display=35)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-27T17:23:48.578972Z","iopub.execute_input":"2025-06-27T17:23:48.579201Z","iopub.status.idle":"2025-06-27T17:26:42.020459Z","shell.execute_reply.started":"2025-06-27T17:23:48.579183Z","shell.execute_reply":"2025-06-27T17:26:42.019629Z"}},"outputs":[],"execution_count":null}]}