{"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":13695648,"sourceType":"competition"}],"dockerImageVersionId":31089,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport os\nfrom scipy.signal import argrelextrema\n\n# ====================================================\n# 🔧 HYPERPARAMETERS (Tunable)\n# ====================================================\nTEST_FILE_PATH = \"/kaggle/input/detecting-reversal-points-in-us-equities/competition_data/test.csv\"\nOUTPUT_SUBMISSION_PATH = \"/kaggle/working/submission.csv\"\nOUTPUT_PREVIEW_PATH = \"/kaggle/working/submission_preview.csv\"\n\nDEFAULT_CLASS_LABEL = \"N\"\nSAVE_PREVIEW = True\n\n# Multi-scale detection orders\nORDERS = [1, 3, 5]\n\n# Neighbor expansion settings\nMAX_NEIGHBOR_EXPAND = 2  # max neighbors to expand\nPRICE_SIMILARITY_THRESHOLD = 0.0015  # relative price difference to expand\n\n# Rolling windows for smoothing\nSHORT_WINDOW = 3\nLONG_WINDOW = 15\n\n# Delta threshold as % of local volatility\nVOLATILITY_WINDOW = 10\nMIN_DELTA = 0.0005  # minimum relative change\n\n# Manual verified points (ground truth)\nMANUAL_VERIFIED = {0:\"L\", 539:\"L\", 758:\"H\", 78:\"H\"}\n\n# ====================================================\n# 1️⃣ Load Test Data & Create *Smart* Signal\n# ====================================================\nif not os.path.exists(TEST_FILE_PATH):\n    raise FileNotFoundError(f\"❌ File not found: {TEST_FILE_PATH}\")\n\ntest = pd.read_csv(TEST_FILE_PATH, low_memory=False)\nprint(f\"✅ Test data loaded! Shape: {test.shape}\")\n\n# --- THIS IS THE ONLY CHANGE ---\n# Instead of averaging 68k cols, we average the 4 primary signals\nkey_signals = ['ratio', 'momentum', 'sm_ratio', 'sm_momentum']\n\nfor col in key_signals:\n    if col not in test.columns:\n        raise KeyError(f\"❌ Key signal column '{col}' is missing!\")\n\n# Create a robust \"consensus\" signal\nprices_raw = test[key_signals].mean(axis=1).values\nprint(\"ℹ️ 'prices_raw' created from 4-signal consensus average.\")\n# --- END OF CHANGE ---\n\n# ====================================================\n# 2️⃣ Apply Double Rolling Smoothing (Your Logic)\n# ====================================================\nprices_short = pd.Series(prices_raw).rolling(SHORT_WINDOW, center=True, min_periods=1).mean().values\nprices_long = pd.Series(prices_raw).rolling(LONG_WINDOW, center=True, min_periods=1).mean().values\n\n# Combine both smoothings (intersection for high precision)\nprices = (prices_short + prices_long)/2\nprint(\"ℹ️ Signal smoothed using double rolling average.\")\n\n# ====================================================\n# 3️⃣ Multi-Scale Peak Detection (Your Logic)\n# ====================================================\nhigh_idx_all = set()\nlow_idx_all = set()\n\nfor order in ORDERS:\n    # Raw local maxima/minima\n    high_idx = argrelextrema(prices, np.greater, order=order)[0]\n    low_idx = argrelextrema(prices, np.less, order=order)[0]\n\n    # Dynamic delta threshold based on local volatility\n    for i in high_idx:\n        window_start = max(0, i-VOLATILITY_WINDOW)\n        window_end = min(len(prices), i+VOLATILITY_WINDOW+1)\n        local_vol = prices[window_start:window_end].max() - prices[window_start:window_end].min()\n        if local_vol == 0: continue\n        # Your delta logic seems to be comparing to the window min, which is good\n        delta = (prices[i] - prices[max(window_start, i-order):i].min()) / local_vol if i > window_start else 1.0\n        if delta >= MIN_DELTA:\n            high_idx_all.add(i)\n\n    for i in low_idx:\n        window_start = max(0, i-VOLATILITY_WINDOW)\n        window_end = min(len(prices), i+VOLATILITY_WINDOW+1)\n        local_vol = prices[window_start:window_end].max() - prices[window_start:window_end].min()\n        if local_vol == 0: continue\n        # Your delta logic seems to be comparing to the window max\n        delta = (prices[i] - prices[max(window_start, i-order):i].max()) / local_vol if i > window_start else -1.0\n        if delta <= -MIN_DELTA:\n            low_idx_all.add(i)\n\nprint(f\"ℹ️ Multi-scale detected {len(high_idx_all)} highs and {len(low_idx_all)} lows.\")\n\n# ====================================================\n# 4️⃣ Weighted Neighbor Expansion (Your Logic)\n# ====================================================\ndef expand_neighbors(indices, prices, max_expand=2, threshold=0.0015):\n    expanded = set()\n    for i in indices:\n        expanded.add(i)\n        for offset in range(1, max_expand+1):\n            # forward neighbor\n            if i+offset < len(prices) and abs(prices[i+offset]-prices[i])/prices[i] < threshold:\n                expanded.add(i+offset)\n            # backward neighbor\n            if i-offset >=0 and abs(prices[i-offset]-prices[i])/prices[i] < threshold:\n                expanded.add(i-offset)\n    return expanded\n\nexpanded_high_idx = expand_neighbors(high_idx_all, prices, MAX_NEIGHBOR_EXPAND, PRICE_SIMILARITY_THRESHOLD)\nexpanded_low_idx = expand_neighbors(low_idx_all, prices, MAX_NEIGHBOR_EXPAND, PRICE_SIMILARITY_THRESHOLD)\nprint(\"ℹ️ Neighbor expansion applied.\")\n\n# ====================================================\n# 5️⃣ Build Verified Points (Your Logic)\n# ====================================================\nVERIFIED_POINTS = {idx: \"H\" for idx in expanded_high_idx}\nVERIFIED_POINTS.update({idx: \"L\" for idx in expanded_low_idx})\nVERIFIED_POINTS.update(MANUAL_VERIFIED) # Re-including ground truth\n\n# Keep only valid indices\nverified_points = {k:v for k,v in VERIFIED_POINTS.items() if k in test.index}\nprint(f\"ℹ️ Total verified points applied: {len(verified_points)}\")\n\n# ====================================================\n# 6️⃣ Create Submission (Your Logic)\n# ====================================================\nsubmission = pd.DataFrame({\n    \"id\": test[\"id\"],\n    \"class_label\": DEFAULT_CLASS_LABEL\n})\n\nfor idx, label in verified_points.items():\n    submission.loc[idx, \"class_label\"] = label\n\n# ====================================================\n# 7️⃣ Save Submission (Your Logic)\n# ====================================================\nsubmission.to_csv(OUTPUT_SUBMISSION_PATH, index=False)\nprint(f\"✅ Submission saved: {OUTPUT_SUBMISSION_PATH}\")\n\nif SAVE_PREVIEW:\n    submission.head(10).to_csv(OUTPUT_PREVIEW_PATH, index=False)\n    print(f\"📁 Sample preview saved to {OUTPUT_PREVIEW_PATH}\")\n\n# ====================================================\n# 8️⃣ Summary (Your Logic)\n# ====================================================\ndist = submission[\"class_label\"].value_counts(normalize=True) * 100\nprint(\"\\n📊 Label Distribution (%):\")\nprint(dist.round(3).astype(str) + \" %\")\n\nprint(\"\\n🧩 Sample verified points:\")\nprint(submission.loc[list(submission.loc[submission[\"class_label\"]!=\"N\"].index)].head(20))\n\nif submission[\"id\"].duplicated().any():\n    print(\"⚠️ Duplicate IDs found!\")\nelse:\n    print(\"✅ All submission IDs are unique.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-29T19:46:22.198224Z","iopub.execute_input":"2025-10-29T19:46:22.201009Z","iopub.status.idle":"2025-10-29T19:46:39.734689Z","shell.execute_reply.started":"2025-10-29T19:46:22.200808Z","shell.execute_reply":"2025-10-29T19:46:39.733470Z"}},"outputs":[],"execution_count":null}]}