{"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":117097,"databundleVersionId":14080503,"sourceType":"competition"},{"sourceId":13280781,"sourceType":"datasetVersion","datasetId":8416714},{"sourceId":13490214,"sourceType":"datasetVersion","datasetId":8565137}],"dockerImageVersionId":31153,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Alpha Radar: A Complete Baseline (0.16+)\n\nThis notebook provides a complete, end-to-end baseline for the Alpha Radar: Solana Sprint competition. It uses our V1 feature set, which is simple, fast, and scores over 0.16 on the leaderboard.\n\n**Our strategy is:**\n1.  **Feature Engineering:** Aggregate all 30s of transaction data into a single row per token (using `last`, `sum`, `mean`, etc.).\n2.  **Imbalance Handling:** Use CatBoost's `scale_pos_weight` to handle the highly imbalanced data (only 2.5% of tokens are targets).\n3.  **Modeling:** Train a 5-fold CatBoost model.\n4.  **Threshold Tuning:** Find the optimal probability threshold that gives us >= 75% recall while maximizing the Jaccard score.\n","metadata":{}},{"cell_type":"code","source":"# === 1. Imports ===\nimport pandas as pd\nimport numpy as np\nimport os\nimport glob\nfrom tqdm.notebook import tqdm\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nfrom catboost import CatBoostClassifier, Pool\nfrom sklearn.model_selection import StratifiedKFold\nfrom sklearn.metrics import jaccard_score, recall_score, f1_score, confusion_matrix, precision_recall_curve\nimport warnings\n\n# Suppress warnings\nwarnings.filterwarnings('ignore')\nprint(\"Libraries imported.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-01T16:07:05.838526Z","iopub.execute_input":"2025-11-01T16:07:05.838783Z","iopub.status.idle":"2025-11-01T16:07:07.730902Z","shell.execute_reply.started":"2025-11-01T16:07:05.838763Z","shell.execute_reply":"2025-11-01T16:07:07.730195Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 2. Load Data & Create Labels\nFirst, we load all the training and evaluation transaction files. We also load the target token list and create our `y_train` DataFrame, which holds the \"ground truth\" labels (0 or 1) for each token in the training set.","metadata":{}},{"cell_type":"code","source":"# === 2. Load Data & Create Labels ===\n\n# --- 1. Define File Paths ---\nDATA_PATH = '../input/alpha-radar-solana-sprint/'\nTARGET_TOKEN_PATH = '/kaggle/input/target-tokens/Alpha Radar Target Tokens.csv'\n\n# --- 2. Load Target Tokens ---\nprint(f\"Attempting to load target tokens from: {TARGET_TOKEN_PATH}\")\ntarget_tokens_df = pd.read_csv(TARGET_TOKEN_PATH)\ntarget_token_set = set(target_tokens_df['Target Token Addresses'])\nprint(f\"Loaded {len(target_token_set)} target (positive) tokens successfully.\")\n\n# --- 3. Load Training Transactions ---\nprint(\"Loading training transaction data (Sample_Dataset.csv)...\")\ntrain_tx_df = pd.read_csv(os.path.join(DATA_PATH, 'Sample_Dataset.csv'))\nprint(f\"Loaded {len(train_tx_df)} training transactions.\")\n\n# --- 4. Load & Concatenate Evaluation Transactions ---\nprint(\"Loading evaluation transaction data (chunks)...\")\neval_files = sorted(glob.glob(os.path.join(DATA_PATH, 'evaluation_set_30s_chunk_*.csv')))\neval_dfs = []\nfor f in tqdm(eval_files, desc=\"Loading eval chunks\"):\n    eval_dfs.append(pd.read_csv(f))\neval_tx_df = pd.concat(eval_dfs, ignore_index=True)\nprint(f\"Loaded {len(eval_tx_df)} evaluation transactions from {len(eval_files)} files.\")\n\n# --- 5. Get all unique tokens from the training transaction data ---\nunique_train_tokens = train_tx_df['mint_token_id'].unique()\nprint(f\"Found {len(unique_train_tokens)} unique tokens in the training transaction set.\")\n\n# --- 6. Create the y_train (ground truth) DataFrame ---\ny_train_list = [1 if token in target_token_set else 0 for token in unique_train_tokens]\ny_train_df = pd.DataFrame({ # Save as DataFrame for merging\n    'mint_token_id': unique_train_tokens,\n    'is_target': y_train_list\n})\ny_train = y_train_df.set_index('mint_token_id')['is_target'] # For model training\nprint(f\"Created y_train with {len(y_train)} entries.\")\n\n# --- 7. Check Class Imbalance & Get Ratio ---\nprint(\"\\n--- Training Set Class Imbalance ---\")\nprint(y_train.value_counts(normalize=True))\nimbalance_ratio = (y_train == 0).sum() / (y_train == 1).sum()\nprint(f\"\\nCalculated imbalance ratio (scale_pos_weight): {imbalance_ratio:.2f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-01T16:08:03.796286Z","iopub.execute_input":"2025-11-01T16:08:03.796714Z","iopub.status.idle":"2025-11-01T16:08:21.636818Z","shell.execute_reply.started":"2025-11-01T16:08:03.796693Z","shell.execute_reply":"2025-11-01T16:08:21.635691Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 3. Feature Engineering\nThis is our V1 feature function. It's a simple, robust function that creates our base features. It converts all transaction logs for a single token into a single row of features.","metadata":{}},{"cell_type":"code","source":"# --- Define the Feature Engineering Function (V1) ---\ndef create_features(df, numeric_cols, categorical_cols):\n    \"\"\"\n    V1: Our simple, robust baseline feature set.\n    \"\"\"\n    \n    print(\"Starting V1 feature aggregation...\")\n    \n    # --- Force all numeric columns to be numeric *before* aggregation ---\n    for col in numeric_cols:\n        if col in df.columns:\n            df[col] = pd.to_numeric(df[col], errors='coerce').fillna(0)\n    \n    aggregations = {}\n    def add_agg(col, agg_func):\n        if col not in aggregations: aggregations[col] = []\n        if agg_func not in aggregations[col]: aggregations[col].append(agg_func)\n\n    last_state_cols = ['market_cap_usd', 'buy_count', 'sell_count', 'total_count', 'total_holders', 'current_holders', 'top10_percent_total', 'creator_balance', 'creator_sold', 'buy_sell_ratio', 'relative_strength_index', 'bollinger_relative_position', 'money_flow_index']\n    for col in last_state_cols:\n        if col in numeric_cols: add_agg(col, 'last')\n    sum_cols = ['sol_volume', 'token_volume', 'consumed_gas', 'fee', 'sol_delta']\n    for col in sum_cols:\n         if col in numeric_cols: add_agg(col, 'sum')\n    mean_cols = ['volume_oscillator', 'rate_of_change', 'liquidity_ratio']\n    for col in mean_cols:\n         if col in numeric_cols: add_agg(col, 'mean')\n    if 'holder' in categorical_cols: add_agg('holder', 'nunique')\n    if 'creator' in categorical_cols: add_agg('creator', 'first')\n    if 'creator_fee' in numeric_cols: add_agg('creator_fee', 'first')\n    if 'market_cap_usd' in numeric_cols:\n        add_agg('market_cap_usd', 'std')\n        add_agg('market_cap_usd', lambda x: x.iloc[-1] - x.iloc[0] if len(x) > 0 else 0)\n\n    final_aggregations = {col: funcs for col, funcs in aggregations.items()}\n            \n    try:\n        df_sorted = df.sort_values(by=['mint_token_id', 'timestamp'])\n    except KeyError:\n        print(\"Warning: 'timestamp' column not found for sorting. Proceeding without sorting.\")\n        df_sorted = df\n        \n    feature_df = df_sorted.groupby('mint_token_id').agg(final_aggregations)\n    \n    # Flatten MultiIndex Columns\n    new_cols = []\n    for col in feature_df.columns:\n        if isinstance(col, tuple):\n            if '<lambda>' in str(col[1]): new_cols.append(f\"{col[0]}_change\")\n            else: new_cols.append(f\"{col[0]}_{col[1]}\")\n        else: new_cols.append(col)\n    feature_df.columns = new_cols\n    \n    if 'holder_nunique' in feature_df.columns: feature_df = feature_df.rename(columns={'holder_nunique': 'unique_holders'})\n    if 'creator_first' in feature_df.columns: feature_df = feature_df.rename(columns={'creator_first': 'creator'})\n\n    # --- V1 Filling NaNs ---\n    if 'market_cap_usd_std' in feature_df.columns: feature_df['market_cap_usd_std'] = feature_df['market_cap_usd_std'].fillna(0)\n    if 'buy_sell_ratio_last' in feature_df.columns: \n        feature_df['buy_sell_ratio_last'] = feature_df['buy_sell_ratio_last'].fillna(1000).replace(np.inf, 1000)\n\n    feature_df = feature_df.fillna(0)\n    \n    print(\"V1 Feature aggregation complete.\")\n    return feature_df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-01T16:09:10.050888Z","iopub.execute_input":"2025-11-01T16:09:10.051154Z","iopub.status.idle":"2025-11-01T16:09:10.061145Z","shell.execute_reply.started":"2025-11-01T16:09:10.051135Z","shell.execute_reply":"2025-11-01T16:09:10.060308Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# === 4. Run Feature Engineering (V1) ===\n\n# --- 1. Identify Shared Columns ---\ntrain_cols = set(train_tx_df.columns)\neval_cols = set(eval_tx_df.columns)\nshared_cols = list(train_cols.intersection(eval_tx_df.columns))\nprint(f\"Column(s) missing from evaluation set: {train_cols - eval_cols}\")\n\nall_numeric_cols = ['token_quantity', 'creator_fee', 'creator_fee_pump', 'market_cap_usd', 'token_delta', 'sol_delta', 'buy_count', 'sell_count', 'total_count', 'token_volume', 'sol_volume', 'liquidity_ratio', 'virtual_sol_reserves', 'virtual_token_reserves', 'consumed_gas', 'fee', 'relative_strength_index', 'bollinger_relative_position', 'volume_oscillator', 'rate_of_change', 'money_flow_index', 'total_holders', 'current_holders', 'top10_percent_total', 'creator_balance', 'creator_sold', 'holder_ratio', 'buy_sell_ratio']\nall_categorical_cols = ['holder', 'trade_mode', 'creator']\nnumeric_cols = [col for col in all_numeric_cols if col in shared_cols]\ncategorical_cols = [col for col in all_categorical_cols if col in shared_cols]\n\n# --- 2. Create Training Features (X_train) ---\nprint(\"Processing Training Data (V1)...\")\nX_train = create_features(train_tx_df,\n                          numeric_cols=numeric_cols,\n                          categorical_cols=categorical_cols)\n\n# --- 3. Create Test Features (X_test) ---\nprint(\"\\nProcessing Evaluation Data (V1)...\")\nX_test = create_features(eval_tx_df,\n                         numeric_cols=numeric_cols,\n                         categorical_cols=categorical_cols)\n\n# --- 4. Align Training Labels (y_train) ---\nprint(\"\\nAligning data...\")\nX_test = X_test.reindex(columns=X_train.columns, fill_value=0)\ny_train_aligned = y_train.loc[X_train.index] # Get the aligned series\n\n# --- 5. Define Categorical Features for CatBoost ---\ncat_features = [col for col in ['creator'] if col in X_train.columns]\n\n# --- 6. Final Verification ---\nprint(\"\\n--- Final Shapes ---\")\nprint(f\"X_train shape:      {X_train.shape}\")\nprint(f\"y_train_aligned shape: {y_train_aligned.shape}\")\nprint(f\"X_test shape:       {X_test.shape}\")\nprint(f\"Categorical features: {cat_features}\")\n\nrequired_rows = 64208\nif X_test.shape[0] == required_rows:\n    print(f\"\\nSUCCESS: X_test has exactly {required_rows} rows.\")\nelse:\n    print(f\"\\nWARNING: X_test has {X_test.shape[0]} rows.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-01T16:09:29.682985Z","iopub.execute_input":"2025-11-01T16:09:29.683518Z","iopub.status.idle":"2025-11-01T16:09:35.046998Z","shell.execute_reply.started":"2025-11-01T16:09:29.683499Z","shell.execute_reply":"2025-11-01T16:09:35.046172Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 4. Model Training\nWe train a CatBoost model using 5-Fold Stratified Cross-Validation. This helps prevent overfitting and gives us reliable out-of-fold (OOF) predictions for threshold tuning.\n\nWe use `scale_pos_weight` to tell CatBoost how much more to \"punish\" mistakes on the rare positive class, which is essential for meeting the 75% recall target.","metadata":{}},{"cell_type":"code","source":"# === 5. Model Training (V1) ===\n\n# --- 1. Define Model Parameters ---\nN_SPLITS = 5\nRANDOM_STATE = 42\nSCALE_POS_WEIGHT = imbalance_ratio # From Cell 2\n\ncb_params = {\n    'iterations': 2000,\n    'learning_rate': 0.03,\n    'loss_function': 'Logloss',\n    'eval_metric': 'F1',\n    'scale_pos_weight': SCALE_POS_WEIGHT,\n    'random_seed': RANDOM_STATE,\n    'early_stopping_rounds': 100,\n    'verbose': 250,\n    'cat_features': cat_features\n}\n\n# --- 2. Set up Cross-Validation ---\nskf = StratifiedKFold(n_splits=N_SPLITS, shuffle=True, random_state=RANDOM_STATE)\n\n# --- 3. Prepare Arrays to Store Predictions ---\noof_preds = np.zeros(len(X_train))\ntest_preds = np.zeros(len(X_test))\nmodels = []\n\nprint(\"--- Starting CatBoost Cross-Validation (V1) ---\")\n\nfor fold, (train_index, val_index) in enumerate(skf.split(X_train, y_train_aligned)):\n    print(f\"\\n--- Fold {fold+1}/{N_SPLITS} ---\")\n\n    X_train_fold, X_val_fold = X_train.iloc[train_index], X_train.iloc[val_index]\n    y_train_fold, y_val_fold = y_train_aligned.iloc[train_index], y_train_aligned.iloc[val_index]\n\n    train_pool = Pool(X_train_fold, y_train_fold, cat_features=cat_features)\n    val_pool = Pool(X_val_fold, y_val_fold, cat_features=cat_features)\n\n    model = CatBoostClassifier(**cb_params)\n    model.fit(train_pool, eval_set=val_pool)\n\n    oof_preds[val_index] = model.predict_proba(val_pool)[:, 1]\n    test_preds += model.predict_proba(X_test)[:, 1] / N_SPLITS\n\n    models.append(model)\n    print(f\"Fold {fold+1} finished.\")\n\nprint(\"\\n--- Cross-Validation Complete ---\")\nprint(\"OOF predictions generated.\")\nprint(\"Test predictions generated.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-01T16:10:20.350028Z","iopub.execute_input":"2025-11-01T16:10:20.350272Z","iopub.status.idle":"2025-11-01T16:10:49.881561Z","shell.execute_reply.started":"2025-11-01T16:10:20.350256Z","shell.execute_reply":"2025-11-01T16:10:49.880900Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 5. Find Optimal Threshold\nThe competition requires >= 75% Recall and is scored on the Jaccard Index. We must use our OOF predictions to find a probability threshold that satisfies this. We'll search 500 different thresholds and pick the one that gives the highest Jaccard score *after* meeting the 75% recall requirement.","metadata":{}},{"cell_type":"code","source":"# === 6. Find Optimal Threshold (Fine Search) ===\n\n# --- 1. Find Best Threshold (Fine Search) ---\nthresholds = np.linspace(0.01, 0.99, 500)\nbest_jaccard = -1\nbest_recall = -1\nbest_threshold = 0\n\nprint(\"Starting fine threshold search (500 steps)...\")\nfor threshold in thresholds:\n    oof_binary_preds = (oof_preds > threshold).astype(int)\n    recall = recall_score(y_train_aligned, oof_binary_preds, zero_division=0)\n\n    if recall >= 0.75: # Must have at least 75% Recall\n        jaccard = jaccard_score(y_train_aligned, oof_binary_preds, zero_division=0)\n        if jaccard > best_jaccard: # If it does, find the best Jaccard\n            best_jaccard = jaccard\n            best_recall = recall\n            best_threshold = threshold\n\nprint(\"--- Optimal Threshold Search (V1) ---\")\nif best_threshold > 0:\n    print(f\"Optimal Threshold: {best_threshold:.4f}\")\n    print(f\"   Best Jaccard: {best_jaccard:.4f}\")\n    print(f\"       OOF Recall: {best_recall:.4f}\")\nelse:\n    print(\"\\nWARNING: Could not find a threshold that achieves 75% recall.\")\n\n# --- 2. Show Confusion Matrix ---\nprint(\"\\n--- OOF Confusion Matrix (V1) ---\")\nfinal_oof_preds = (oof_preds > best_threshold).astype(int)\ncm = confusion_matrix(y_train_aligned, final_oof_preds)\n\nplt.figure(figsize=(8, 6))\nsns.heatmap(cm, annot=True, fmt='d', cmap='Blues',\n            xticklabels=['Predicted 0', 'Predicted 1'],\n            yticklabels=['Actual 0', 'Actual 1'])\nplt.title(f\"OOF Confusion Matrix (Threshold = {best_threshold:.4f})\")\nplt.ylabel('Actual Label')\nplt.xlabel('Predicted Label')\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-01T16:11:13.030075Z","iopub.execute_input":"2025-11-01T16:11:13.030418Z","iopub.status.idle":"2025-11-01T16:11:19.642077Z","shell.execute_reply.started":"2025-11-01T16:11:13.030398Z","shell.execute_reply":"2025-11-01T16:11:19.640952Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 6. Create Submission\nFinally, we apply the optimal threshold to our test predictions and save the file in the correct order.","metadata":{}},{"cell_type":"code","source":"# === 7. Create Submission Files ===\n\n# --- 1. Apply the optimal threshold ---\nfinal_binary_preds = (test_preds > best_threshold).astype(int)\n\n# --- 2. Create the main submission.csv file (with correct order) ---\ncorrect_token_order = pd.Series(eval_tx_df['mint_token_id']).unique()\nsubmission_df_ordered = pd.DataFrame({'mint_token_id': correct_token_order})\n\npredictions_map = pd.Series(test_preds, index=X_test.index)\nbinary_predictions_map = pd.Series(final_binary_preds, index=X_test.index)\n\nsubmission_df_ordered['is_target'] = submission_df_ordered['mint_token_id'].map(binary_predictions_map)\nsubmission_df_ordered['prediction_value'] = submission_df_ordered['mint_token_id'].map(predictions_map)\n\n# --- 3. Create the deliverable_details.csv file ---\ndeliverable_df = pd.DataFrame({\n    'token': submission_df_ordered['mint_token_id'],\n    'threshold': best_threshold,\n    'prediction_value': submission_df_ordered['prediction_value'],\n    'isTargetToken': submission_df_ordered['is_target']\n})\n\n# --- 4. Save the files ---\nfinal_submission_csv = submission_df_ordered[['mint_token_id', 'is_target']]\nfinal_submission_csv.to_csv('submission.csv', index=False)\ndeliverable_df.to_csv('deliverable_details.csv', index=False)\n\nprint(\"\\n--- Submission Files Created ---\")\nprint(f\"submission.csv shape: {final_submission_csv.shape}\")\nprint(f\"deliverable_details.csv shape: {deliverable_df.shape}\")\nprint(\"\\nAll done!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-01T16:12:35.079536Z","iopub.execute_input":"2025-11-01T16:12:35.079807Z","iopub.status.idle":"2025-11-01T16:12:35.472810Z","shell.execute_reply.started":"2025-11-01T16:12:35.079790Z","shell.execute_reply":"2025-11-01T16:12:35.472051Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}