{"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":88.072356,"end_time":"2025-06-14T11:26:22.99696","environment_variables":{},"exception":null,"input_path":"__notebook__.ipynb","output_path":"__notebook__.ipynb","parameters":{},"start_time":"2025-06-14T11:24:54.924604","version":"2.6.0"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# 📈 DRW Crypto Market Prediction | Time Series + Ensemble\n\nWelcome to my solution for the **DRW Crypto Market Prediction** Kaggle competition!\n\n---\n\n> _If this helps you, consider giving it an upvote ❤️_\n","metadata":{}},{"cell_type":"markdown","source":"<a id='Imports'></a>\n# Imports","metadata":{}},{"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","metadata":{"execution":{"iopub.status.busy":"2025-06-17T09:16:57.858115Z","iopub.execute_input":"2025-06-17T09:16:57.858812Z","iopub.status.idle":"2025-06-17T09:16:57.863603Z","shell.execute_reply.started":"2025-06-17T09:16:57.858788Z","shell.execute_reply":"2025-06-17T09:16:57.862929Z"},"papermill":{"duration":7.230706,"end_time":"2025-06-14T11:25:06.375184","exception":false,"start_time":"2025-06-14T11:24:59.144478","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"<a id='feature'></a>\n# Feature Engineering","metadata":{}},{"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":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-17T09:16:57.864655Z","iopub.execute_input":"2025-06-17T09:16:57.864846Z","iopub.status.idle":"2025-06-17T09:16:57.869082Z","shell.execute_reply.started":"2025-06-17T09:16:57.864831Z","shell.execute_reply":"2025-06-17T09:16:57.868453Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"<a id='Config'></a>\n# Configuration","metadata":{}},{"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\"\n    ]\n\n    LABEL_COLUMN = \"label\"\n    N_FOLDS = 3\n    RANDOM_STATE = 42\n\nXGB_PARAMS = {\n    \"tree_method\": \"hist\",\n    \"device\": \"gpu\",\n    \"colsample_bylevel\": 0.4778,\n    \"colsample_bynode\": 0.3628,\n    \"colsample_bytree\": 0.7107,\n    \"gamma\": 1.7095,\n    \"learning_rate\": 0.02213,\n    \"max_depth\": 20,\n    \"max_leaves\": 12,\n    \"min_child_weight\": 16,\n    \"n_estimators\": 1667,\n    \"subsample\": 0.06567,\n    \"reg_alpha\": 39.3524,\n    \"reg_lambda\": 75.4484,\n    \"verbosity\": 0,\n    \"random_state\": Config.RANDOM_STATE,\n    \"n_jobs\": -1\n}\n\nLEARNERS = [\n    {\"name\": \"xgb\", \"Estimator\": XGBRegressor, \"params\": XGB_PARAMS},\n]","metadata":{"execution":{"iopub.status.busy":"2025-06-17T09:16:57.872794Z","iopub.execute_input":"2025-06-17T09:16:57.872971Z","iopub.status.idle":"2025-06-17T09:16:57.878216Z","shell.execute_reply.started":"2025-06-17T09:16:57.872958Z","shell.execute_reply":"2025-06-17T09:16:57.877624Z"},"papermill":{"duration":0.009344,"end_time":"2025-06-14T11:25:06.386541","exception":false,"start_time":"2025-06-14T11:25:06.377197","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"<a id='load'></a>\n# Loading Data","metadata":{}},{"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":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-17T09:16:57.879125Z","iopub.execute_input":"2025-06-17T09:16:57.879346Z","iopub.status.idle":"2025-06-17T09:16:57.884215Z","shell.execute_reply.started":"2025-06-17T09:16:57.879331Z","shell.execute_reply":"2025-06-17T09:16:57.883598Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"Config.FEATURES += [\"bid_qty\", \"ask_qty\", \"buy_qty\", \"sell_qty\", \"volume\"]\nConfig.FEATURES = list(set(Config.FEATURES))  # remove duplicates","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-17T09:16:57.885240Z","iopub.execute_input":"2025-06-17T09:16:57.885512Z","iopub.status.idle":"2025-06-17T09:16:57.888946Z","shell.execute_reply.started":"2025-06-17T09:16:57.885491Z","shell.execute_reply":"2025-06-17T09:16:57.888287Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"<a id='Train'></a>\n# Training and Evaluation","metadata":{}},{"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\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\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, y_train, sample_weight=sw, eval_set=[(X_valid, y_valid)], verbose=False)\n\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])\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","metadata":{"execution":{"iopub.status.busy":"2025-06-17T09:16:57.977489Z","iopub.execute_input":"2025-06-17T09:16:57.977751Z","iopub.status.idle":"2025-06-17T09:16:57.988091Z","shell.execute_reply.started":"2025-06-17T09:16:57.977733Z","shell.execute_reply":"2025-06-17T09:16:57.987550Z"},"papermill":{"duration":0.013922,"end_time":"2025-06-14T11:25:06.402128","exception":false,"start_time":"2025-06-14T11:25:06.388206","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"<a id='Subm'></a>\n# Submission","metadata":{}},{"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-17T09:16:57.989236Z","iopub.execute_input":"2025-06-17T09:16:57.989455Z","iopub.status.idle":"2025-06-17T09:16:57.995915Z","shell.execute_reply.started":"2025-06-17T09:16:57.989439Z","shell.execute_reply":"2025-06-17T09:16:57.995193Z"},"papermill":{"duration":0.010179,"end_time":"2025-06-14T11:25:06.413978","exception":false,"start_time":"2025-06-14T11:25:06.403799","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"<a id='Main'></a>\n# Main ","metadata":{}},{"cell_type":"code","source":"if __name__ == \"__main__\":\n    train_df, test_df, submission_df = load_data()\n    oof_preds, test_preds, model_slices = 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-17T09:16:57.996667Z","iopub.execute_input":"2025-06-17T09:16:57.996901Z","iopub.status.idle":"2025-06-17T09:18:10.373470Z","shell.execute_reply.started":"2025-06-17T09:16:57.996881Z","shell.execute_reply":"2025-06-17T09:18:10.372570Z"},"papermill":{"duration":75.760202,"end_time":"2025-06-14T11:26:22.175852","exception":false,"start_time":"2025-06-14T11:25:06.41565","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"![Upvote](https://media.giphy.com/media/xT5LMHxhOfscxPfIfm/giphy.gif)\n\n","metadata":{}}]}