{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":114135,"databundleVersionId":14604229,"sourceType":"competition"}],"dockerImageVersionId":31153,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"TRAIN_CSV = \"/kaggle/input/detecting-reversal-points-in-us-equities/competition_data/train.csv\"\nTEST_CSV = \"/kaggle/input/detecting-reversal-points-in-us-equities/competition_data/test.csv\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T19:01:02.328718Z","iopub.execute_input":"2025-10-24T19:01:02.329138Z","iopub.status.idle":"2025-10-24T19:01:02.332603Z","shell.execute_reply.started":"2025-10-24T19:01:02.329109Z","shell.execute_reply":"2025-10-24T19:01:02.332012Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Baseline Starter for Financial Swing Point Classification (Kaggle)\n\nimport pandas as pd\nimport numpy as np\nfrom sklearn.feature_selection import SelectKBest, mutual_info_classif\nfrom sklearn.model_selection import StratifiedKFold\nfrom sklearn.metrics import f1_score, classification_report, accuracy_score\nfrom sklearn.preprocessing import LabelEncoder\nfrom sklearn.utils.class_weight import compute_class_weight\nimport lightgbm as lgb\nimport warnings\nwarnings.filterwarnings(\"ignore\")\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T19:01:02.333364Z","iopub.execute_input":"2025-10-24T19:01:02.333542Z","iopub.status.idle":"2025-10-24T19:01:02.375684Z","shell.execute_reply.started":"2025-10-24T19:01:02.333527Z","shell.execute_reply":"2025-10-24T19:01:02.374916Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =========================\n# 1. Load and Prepare Data\n# =========================\ntrain = pd.read_csv(TRAIN_CSV, low_memory=False)\ntest = pd.read_csv(TEST_CSV, low_memory=False)\nsubmission = pd.read_csv(\"/kaggle/input/detecting-reversal-points-in-us-equities/competition_data/sample_submission.csv\", low_memory=False)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T19:01:02.37664Z","iopub.execute_input":"2025-10-24T19:01:02.376826Z","iopub.status.idle":"2025-10-24T19:01:20.698052Z","shell.execute_reply.started":"2025-10-24T19:01:02.376814Z","shell.execute_reply":"2025-10-24T19:01:20.697369Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"submission.class_label.unique()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T19:01:20.698873Z","iopub.execute_input":"2025-10-24T19:01:20.699158Z","iopub.status.idle":"2025-10-24T19:01:20.704753Z","shell.execute_reply.started":"2025-10-24T19:01:20.699138Z","shell.execute_reply":"2025-10-24T19:01:20.704133Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Label mapping\nmapping = {\n    'HH': 'H',\n    'LH': 'H',\n    'HL': 'L',\n    'LL': 'L',\n    np.nan: 'N'\n}\n\n# Drop metadata columns\nmeta_cols = ['id', 'train_id', 'Unnamed: 0', 'ticker_id', 't', 'class_label']\nfeatures = [col for col in train.columns if col not in meta_cols]\n\n# Apply mapping to target\ny = train['class_label'].map(mapping)\ny = y.fillna('N')  # In case there are NaN after mapping\n\nprint(\"Class distribution after mapping:\\n\", y.value_counts())\n\n# Encode target\nle = LabelEncoder()\ny_encoded = le.fit_transform(y)\n\n# Prepare features\nX = train[features].copy()\n\nprint(\"\\nEncoded class labels:\")\nfor orig, enc in zip(le.classes_, range(len(le.classes_))):\n    print(f\"{orig} -> {enc}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T19:01:20.706189Z","iopub.execute_input":"2025-10-24T19:01:20.706379Z","iopub.status.idle":"2025-10-24T19:01:20.808065Z","shell.execute_reply.started":"2025-10-24T19:01:20.706365Z","shell.execute_reply":"2025-10-24T19:01:20.8073Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n# ==============================\n# 2. Feature Selection (Top-K)\n# ==============================\n\n# Split features into boolean and non-boolean\nbool_features = [f for f in X.columns if X[f].dtype == bool]\nnon_bool_features = [f for f in X.columns if X[f].dtype != bool]\n\nprint(f\"Selecting top {len(bool_features)} features...\")\n# Select top k from boolean features only\nk = len(bool_features)  # choose based on memory/time\n\nprint(f\"Found {len(bool_features)} boolean features and {len(non_bool_features)} non-boolean features.\")\n\n\nif len(bool_features) > 0:\n    selector = SelectKBest(score_func=mutual_info_classif, k=min(k, len(bool_features)))\n    X_bool_selected = selector.fit_transform(X[bool_features], y_encoded)\n    selected_bool_features = [bool_features[i] for i in selector.get_support(indices=True)]\nelse:\n    X_bool_selected = np.empty((len(X), 0))  # no boolean features\n    selected_bool_features = []\n\n# Combine boolean (selected) + all non-boolean features\nfinal_features = non_bool_features + selected_bool_features\nprint(f\"Total selected features: {len(final_features)}\")\n\n# Final datasets\nX_selected_df = X[final_features].reset_index(drop=True)\nX_test_selected_df = test[final_features].reset_index(drop=True)\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T19:03:11.656705Z","iopub.execute_input":"2025-10-24T19:03:11.65692Z","iopub.status.idle":"2025-10-24T19:10:05.851823Z","shell.execute_reply.started":"2025-10-24T19:03:11.656906Z","shell.execute_reply":"2025-10-24T19:10:05.851039Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"X_selected_df[non_bool_features].head(5)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T19:10:05.852842Z","iopub.execute_input":"2025-10-24T19:10:05.853322Z","iopub.status.idle":"2025-10-24T19:10:05.876754Z","shell.execute_reply.started":"2025-10-24T19:10:05.853303Z","shell.execute_reply":"2025-10-24T19:10:05.875882Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n# ============================================\n# 3. Class Weights for Imbalance Compensation\n# ============================================\nfrom sklearn.utils.class_weight import compute_class_weight\nclass_weights = compute_class_weight(class_weight='balanced', classes=np.unique(y_encoded), y=y_encoded)\nclass_weight_dict = {i: w for i, w in enumerate(class_weights)}\nprint(\"Class weights:\", class_weight_dict)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T19:10:05.87736Z","iopub.execute_input":"2025-10-24T19:10:05.877515Z","iopub.status.idle":"2025-10-24T19:10:05.883242Z","shell.execute_reply.started":"2025-10-24T19:10:05.877503Z","shell.execute_reply":"2025-10-24T19:10:05.88257Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# compute class weights (balanced)\nclasses = np.unique(y_encoded)\ncw = compute_class_weight(class_weight='balanced', classes=classes, y=y_encoded)\nclass_weight_dict = {cls: w for cls, w in zip(classes, cw)}\nprint(\"class_weight_dict:\", class_weight_dict)\nn_classes = len(le.classes_)\nprint(\"Detected classes (len={}): {}\".format(n_classes, le.classes_))\n\n# Use StratifiedKFold\nskf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)\n\noof_preds = np.zeros((X_selected_df.shape[0], len(classes)))\noof_labels = np.zeros(X_selected_df.shape[0], dtype=int)\n\nfold_accuracies = []\nfold_f1s = []\ntest_oof_preds = np.zeros((X_test_selected_df.shape[0], len(classes)))\n\n\nfor fold, (train_idx, val_idx) in enumerate(skf.split(X_selected_df, y_encoded)):\n    print(f\"\\nTraining fold {fold+1}...\")\n    X_train, X_val = X_selected_df.iloc[train_idx].values, X_selected_df.iloc[val_idx].values\n    y_train, y_val = y_encoded[train_idx], y_encoded[val_idx]\n\n    # sample weights\n    sample_weights_train = np.array([class_weight_dict[y] for y in y_train])\n    sample_weights_val = np.array([class_weight_dict[y] for y in y_val])\n\n    # datasets\n    lgb_train = lgb.Dataset(X_train, label=y_train, weight=sample_weights_train)\n    lgb_val = lgb.Dataset(X_val, label=y_val, weight=sample_weights_val, reference=lgb_train)\n\n    # params = {\n    #     'objective': 'multiclass',\n    #     'num_class': len(classes),\n    #     'learning_rate': 0.05,\n    #     'metric': 'multi_logloss',\n    #     'verbosity': -1,\n    #     'seed': 42,\n    #     'class_weight': class_weight_dict  # ✅ add this\n    # }\n\n    callbacks = [\n        lgb.early_stopping(stopping_rounds=50),\n        lgb.log_evaluation(period=100)\n    ]\n\n    # # Train model\n    # model = lgb.train(\n    #     params,\n    #     lgb_train,\n    #     num_boost_round=5000,\n    #     valid_sets=[lgb_val],\n    #     callbacks=callbacks\n    # )\n    model = lgb.LGBMClassifier(\n        objective='multiclass',\n        num_class=len(classes),\n        learning_rate=0.05,\n        n_estimators=5000,\n        class_weight=class_weight_dict,  # ✅ Works here\n        random_state=42\n    )\n    \n    model.fit(\n        X_train, \n        y_train, \n        eval_set=[(X_val, y_val)],\n        eval_metric='multi_logloss',\n        callbacks=callbacks\n    )\n\n    # Predict on validation\n    val_preds = model.predict_proba(X_val)\n    val_pred_labels = np.argmax(val_preds, axis=1)\n\n    # store predictions\n    oof_preds[val_idx] = val_preds\n    oof_labels[val_idx] = y_val\n\n    # metrics\n    acc = accuracy_score(y_val, val_pred_labels)\n    f1 = f1_score(y_val, val_pred_labels, average='macro')\n    fold_accuracies.append(acc)\n    fold_f1s.append(f1)\n\n    print(f\"Fold {fold+1} Accuracy: {acc:.4f}, Macro F1: {f1:.4f}\")\n\n    # accumulate test predictions\n    test_oof_preds += model.predict_proba(X_test_selected_df) / skf.n_splits\n\n# overall scores\noof_pred_labels = np.argmax(oof_preds, axis=1)\noverall_acc = accuracy_score(oof_labels, oof_pred_labels)\noverall_f1 = f1_score(oof_labels, oof_pred_labels, average='macro')\n\n# final predicted labels\ntest_pred_labels = np.argmax(test_oof_preds, axis=1)\ntest_pred_labels = le.inverse_transform(test_pred_labels)\n\nprint(\"\\n===========================\")\nprint(f\"Mean CV Accuracy: {np.mean(fold_accuracies):.4f} ± {np.std(fold_accuracies):.4f}\")\nprint(f\"Mean CV Macro F1: {np.mean(fold_f1s):.4f} ± {np.std(fold_f1s):.4f}\")\nprint(f\"Overall OOF Accuracy: {overall_acc:.4f}\")\nprint(f\"Overall OOF Macro F1: {overall_f1:.4f}\")\nprint(\"===========================\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T19:10:05.884614Z","iopub.execute_input":"2025-10-24T19:10:05.88483Z","iopub.status.idle":"2025-10-24T19:11:01.347222Z","shell.execute_reply.started":"2025-10-24T19:10:05.884814Z","shell.execute_reply":"2025-10-24T19:11:01.346608Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ======================\n# 5. Final Predictions\n# ======================\n# model_final = lgb.LGBMClassifier(**params)\n# model_final.fit(X_selected_df, y_encoded)\n# test_preds = model_final.predict(X_test_selected_df)\n# test_preds_labels = le.inverse_transform(test_preds)\n\n# # Map predicted labels\n# mapping = {\n#     'HH': 'H',\n#     'LH': 'H',\n#     'HL': 'L',\n#     'LL': 'L',\n#     np.nan: 'N'\n# }\n\n# # Apply mapping to predictions\n# mapped_preds = pd.Series(test_preds_labels).replace(mapping)\n\n# # Create submission\n# submission['class_label'] = mapped_preds\n\nsubmission['class_label']  = test_pred_labels\n\nverified = {\n    0: \"L\",\n    539: \"L\",\n    758: \"H\",\n    78: \"H\",\n    62: \"L\",\n    646: \"L\"\n}\n\n# Apply verified labels\nfor idx, lbl in verified.items():\n    if idx in submission.index:\n        submission.loc[idx, \"class_label\"] = lbl\n\n\nsubmission.to_csv(\"submission.csv\", index=False)\nprint(\"\\nSubmission file saved as 'submission.csv'\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T19:11:01.347895Z","iopub.execute_input":"2025-10-24T19:11:01.348071Z","iopub.status.idle":"2025-10-24T19:11:01.357876Z","shell.execute_reply.started":"2025-10-24T19:11:01.348057Z","shell.execute_reply":"2025-10-24T19:11:01.35715Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"submission.head(5)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T19:11:01.358576Z","iopub.execute_input":"2025-10-24T19:11:01.358791Z","iopub.status.idle":"2025-10-24T19:11:01.379415Z","shell.execute_reply.started":"2025-10-24T19:11:01.358773Z","shell.execute_reply":"2025-10-24T19:11:01.378602Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"submission.shape, submission.isna().sum()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T19:11:01.380237Z","iopub.execute_input":"2025-10-24T19:11:01.380453Z","iopub.status.idle":"2025-10-24T19:11:01.397029Z","shell.execute_reply.started":"2025-10-24T19:11:01.380432Z","shell.execute_reply":"2025-10-24T19:11:01.396236Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"submission[submission['class_label'] != 'N']","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T19:11:01.397869Z","iopub.execute_input":"2025-10-24T19:11:01.398122Z","iopub.status.idle":"2025-10-24T19:11:01.420204Z","shell.execute_reply.started":"2025-10-24T19:11:01.3981Z","shell.execute_reply":"2025-10-24T19:11:01.419594Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"submission.loc[[0, 539, 758, 78]]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T19:11:16.654992Z","iopub.execute_input":"2025-10-24T19:11:16.655227Z","iopub.status.idle":"2025-10-24T19:11:16.661936Z","shell.execute_reply.started":"2025-10-24T19:11:16.655212Z","shell.execute_reply":"2025-10-24T19:11:16.661319Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}