{"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":13600155,"sourceType":"datasetVersion","datasetId":8642072}],"dockerImageVersionId":31153,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os, gc, random, re, sys, subprocess\nfrom pathlib import Path\nimport numpy as np\nimport pandas as pd\nimport glob\nfrom tqdm.notebook import tqdm\nimport matplotlib.pyplot as plt\nimport seaborn as sns\n# from sklearn.linear_model import LinearRegression # <-- No longer needed\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.\")\n\n# --- Setup Paths and IDs ---\nSEED = 42\nrandom.seed(SEED); np.random.seed(SEED)\nUSE_GPU_WISH = True\nCHUNKSIZE = 250_000\n\nDATA_PATH = '/kaggle/input/alpha-radar-solana-sprint/'\nWORK_DIR  = Path(\"/kaggle/working\"); WORK_DIR.mkdir(parents=True, exist_ok=True)\nKEY_COL  = \"mint_token_id\"\nTIME_COL = \"timestamp\"\n\nTARGET_TOKENS_GDRIVE_ID = \"1EsqpZXPBU-6m0djDmccCrtUX07jV2fHA\"\nTARGET_TOKEN_PATH = WORK_DIR / \"target_tokens.csv\" # <-- Use our GDrive path\n\n# --- CatBoost/gdown Installation ---\ntry:\n    from catboost import CatBoostClassifier, Pool\n    try: from catboost.utils import get_gpu_device_count\n    except Exception: get_gpu_device_count = None\nexcept Exception:\n    subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"-q\", \"catboost\"])\n    from catboost import CatBoostClassifier, Pool\n    try: from catboost.utils import get_gpu_device_count\n    except Exception: get_gpu_device_count = None\n\ntry:\n    import gdown; HAVE_GDOWN = True\nexcept Exception:\n    try:\n        subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"-q\", \"gdown\"])\n        import gdown; HAVE_GDOWN = True\n    except Exception:\n        HAVE_GDOWN = False\nprint(f\"CatBoost OK | gdown: {HAVE_GDOWN}\")\n\n# --- GPU Detection ---\ndef detect_gpu_for_catboost() -> bool:\n    has_dev = any(Path(f\"/dev/nvidia{i}\").exists() for i in range(4))\n    cnt = 0\n    if get_gpu_device_count is not None:\n        try: cnt = int(get_gpu_device_count())\n        except Exception: cnt = 0\n    cuda_visible = os.environ.get(\"CUDA_VISIBLE_DEVICES\", \"\")\n    blocked = (cuda_visible.strip() == \"-1\")\n    return (has_dev or cnt > 0) and not blocked\nUSE_GPU = bool(USE_GPU_WISH and detect_gpu_for_catboost())\nprint(\"Will try GPU:\", USE_GPU)\n\n# === 2. Load Data & Create Labels ===\n\n# --- Helper Functions for Data Loading ---\ndef download_target_tokens_csv(dst_path: Path):\n    if dst_path.exists(): return True\n    if not HAVE_GDOWN:\n        print(\"gdown not available — upload target_tokens.csv to /kaggle/working/\")\n        return False\n    url = f\"https://drive.google.com/uc?id={TARGET_TOKENS_GDRIVE_ID}\"\n    gdown.download(url, str(dst_path), quiet=False)\n    return dst_path.exists() and dst_path.stat().st_size > 0\n\ndef load_target_list(path: Path):\n    df = pd.read_csv(path)\n    # --- MODIFIED: Use the correct column name from baseline ---\n    cands = [c for c in df.columns if \"Target Token Addresses\" in c] \n    if not cands: # Fallback to our logic\n         cands = [c for c in df.columns if \"mint\" in c.lower() or \"token\" in c.lower()]\n    # --- End Modification ---\n    assert cands, \"Cannot find token id column in target csv\"\n    col = cands[0]\n    t = df[[col]].dropna().drop_duplicates().rename(columns={col: KEY_COL})\n    t[KEY_COL] = t[KEY_COL].astype(str)\n    print(f\"Loaded {len(t)} target (positive) tokens successfully.\")\n    return t\n\n# --- 2. Load Target Tokens (Using GDrive) ---\nprint(f\"Attempting to download target tokens to: {TARGET_TOKEN_PATH}\")\nok = download_target_tokens_csv(TARGET_TOKEN_PATH)\nif not ok: raise SystemExit(\"Missing target_tokens.csv\")\n\ntarget_list_df = load_target_list(TARGET_TOKEN_PATH)\ntarget_token_set = set(target_list_df['mint_token_id'])\n# --- End ---\n\n# --- 3. Load Training Transactions ---\nprint(\"Loading training transaction data (Sample_Dataset.csv)...\")\ntry:\n    train_tx_df = pd.read_csv(os.path.join(DATA_PATH, 'Sample_Dataset.csv'))\n    print(f\"Loaded {len(train_tx_df)} training transactions.\")\nexcept FileNotFoundError:\n    print(\"FATAL: Sample_Dataset.csv not found. This strategy relies on it.\")\n    # Add fallback just in case\n    FULL_DIR  = Path(\"/kaggle/input/pumpfun-30s-september-2025\")\n    full_paths = sorted(FULL_DIR.glob(\"september_2025_first30s_chunk_*.csv\"))\n    train_dfs = [pd.read_csv(p) for p in full_paths]\n    train_tx_df = pd.concat(train_dfs, ignore_index=True)\n    print(f\"Loaded {len(train_tx_df)} training transactions from full dataset (FALLBACK).\")\n\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({\n    'mint_token_id': unique_train_tokens,\n    'is_target': y_train_list\n})\n# --- MODIFIED: Create the Series y_train as in the baseline ---\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# --- End Modification ---\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}\")\n\n\n# === 3. Feature Engineering (Baseline V1) ===\n\n# --- Define the Feature Engineering Function (V1 from baseline) ---\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        # --- ADDED: Fallback sort by row order ---\n        df['_row_order'] = df.groupby('mint_token_id').cumcount()\n        df_sorted = df.sort_values(by=['mint_token_id', '_row_order'])\n        # --- End Add ---\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\n\n\n# === 4. Run Feature Engineering (V1) ===\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))\n\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']\n\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. NO Creator DNA in this version ---\n\n# --- 5. Align Training Labels (y_train) ---\nprint(\"\\nAligning data...\")\nX_test = X_test.reindex(columns=X_train.columns, fill_value=0)\nX_train = X_train.reindex(columns=X_test.columns, fill_value=0) # Align both ways\n\ny_train_aligned = y_train.reindex(X_train.index).fillna(0).astype(int) # Get the aligned series\n\n# --- 6. Define Categorical Features for CatBoost ---\ncat_features = [col for col in ['creator'] if col in X_train.columns]\n\n# --- 7. 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.\")\n    # Add reindexing logic from baseline\n    correct_token_order = pd.Series(eval_tx_df['mint_token_id'].unique())\n    X_test = X_test.reindex(correct_token_order, fill_value=0)\n    print(f\"Reindexed X_test shape: {X_test.shape}\")\n    if X_test.shape[0] == required_rows:\n         print(\"SUCCESS after reindexing.\")\n    else:\n         print(\"FATAL: Reindexing failed to fix row count.\")\n\n# Clean up\ndel train_tx_df, eval_dfs, target_list_df; gc.collect()\n\n\n# === 5. Model Training (V1 - Tuned) ===\n# --- 1. Define Model Parameters ---\nN_SPLITS = 5\nRANDOM_STATE = 42\nSCALE_POS_WEIGHT = imbalance_ratio # From Cell 2\n\n# --- MODIFIED: Tuned Hyperparameters ---\ncb_params = {\n    'iterations': 4000,             # More iterations\n    'learning_rate': 0.02,          # Slower learning rate\n    'depth': 7,                     # Deeper trees\n    'l2_leaf_reg': 10.0,            # Added regularization\n    'loss_function': 'Logloss',\n    'eval_metric': 'F1',\n    'scale_pos_weight': SCALE_POS_WEIGHT,\n    'random_seed': RANDOM_STATE,\n    'early_stopping_rounds': 200,     # More patience\n    'verbose': 250,\n    'cat_features': cat_features\n}\n# --- End Modification ---\n\nif USE_GPU:\n    cb_params['task_type'] = 'GPU'\n    cb_params['devices'] = '0'\nelse:\n    cb_params.pop('task_type', None)\n    cb_params.pop('devices', None)\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 (V15 - Tuned Baseline) ---\")\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    \n    try:\n        model.fit(train_pool, eval_set=val_pool)\n    except Exception as e:\n        print(f\"Error during training (fold {fold+1}): {e}\")\n        if \"GPU\" in str(e) or \"Cuda\" in str(e) or \"driver\" in str(e):\n            print(\"GPU error, falling back to CPU...\")\n            cb_params_cpu = cb_params.copy()\n            cb_params_cpu.pop('task_type', None)\n            cb_params_cpu.pop('devices', None)\n            model = CatBoostClassifier(**cb_params_cpu)\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    del X_train_fold, X_val_fold, y_train_fold, y_val_fold, train_pool, val_pool; gc.collect()\n\nprint(\"\\n--- Cross-Validation Complete ---\")\nprint(\"OOF predictions generated.\")\nprint(\"Test predictions generated.\")\n\n# === 6. Find Optimal Threshold (Fine Search) ===\n# --- 1. Find Best Threshold (Fine Search) ---\nthresholds = np.linspace(0.01, 0.99, 500)\nbest_jaccard = -1\nbest_recall = -1\nbest_threshold = 0\nRECALL_TARGET = 0.75 # <-- Using the 0.163 baseline's 75% target\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 >= RECALL_TARGET: # 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 (V15) ---\")\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    print(\"Fallback: Finding best Jaccard score regardless of recall...\")\n    for threshold in thresholds:\n        oof_binary_preds = (oof_preds > threshold).astype(int)\n        jaccard = jaccard_score(y_train_aligned, oof_binary_preds, zero_division=0)\n        if jaccard > best_jaccard:\n            best_jaccard = jaccard\n            best_recall = recall_score(y_train_aligned, oof_binary_preds, zero_division=0)\n            best_threshold = threshold\n    print(f\"Fallback Optimal Threshold: {best_threshold:.4f}\")\n    print(f\"   Best Jaccard (fallback): {best_jaccard:.4f}\")\n    print(f\"       OOF Recall (fallback): {best_recall:.4f}\")\n\n\n# --- 2. Show Confusion Matrix ---\nprint(\"\\n--- OOF Confusion Matrix (V15) ---\")\nfinal_oof_preds = (oof_preds > best_threshold).astype(int)\ncm = confusion_matrix(y_train_aligned, final_oof_preds)\n\ntry:\n    plt.figure(figsize=(8, 6))\n    sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',\n                xticklabels=['Predicted 0', 'Predicted 1'],\n                yticklabels=['Actual 0', 'Actual 1'])\n    plt.title(f\"OOF Confusion Matrix (Threshold = {best_threshold:.4f})\")\n    plt.ylabel('Actual Label')\n    plt.xlabel('Predicted Label')\n    plt.savefig(\"oof_confusion_matrix.png\") # Save the plot\n    print(\"Saved OOF confusion matrix plot to oof_confusion_matrix.png\")\nexcept Exception as e:\n    print(f\"Could not generate plot: {e}\")\n\n\n# === 7. Create Submission Files ===\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) ---\n# --- MODIFIED: Use X_test.index for correct order ---\nsubmission_df_ordered = pd.DataFrame({'mint_token_id': X_test.index})\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# --- End Modification ---\n\nsubmission_df_ordered['is_target'] = submission_df_ordered['is_target'].fillna(0).astype(int)\nsubmission_df_ordered['prediction_value'] = submission_df_ordered['prediction_value'].fillna(0)\n\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_df.csv shape: {deliverable_df.shape}\")\nprint(f\"Final positive prediction rate: {final_submission_csv['is_target'].mean():.2%}\")\n\nprint(\"\\nAll done!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-08T04:19:52.743299Z","iopub.execute_input":"2025-11-08T04:19:52.744007Z","execution_failed":"2025-11-08T04:21:54.591Z"}},"outputs":[],"execution_count":null}]}