{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.11","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaL4","dataSources":[{"sourceId":91496,"databundleVersionId":11802066,"sourceType":"competition"},{"sourceId":96164,"databundleVersionId":11418275,"sourceType":"competition"}],"dockerImageVersionId":31041,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nfrom scipy.signal import savgol_filter\n\n# === Step 1.1: Load & Optimize Types ===\ndf = pd.read_parquet(\"/kaggle/input/drw-crypto-market-prediction/train.parquet\")\n\n# Ensure timestamp is a column\nif df.index.name == 'timestamp':\n    df = df.reset_index()\ndf['timestamp'] = pd.to_datetime(df['timestamp'])\n\n# Optional: reduce memory for dev\ndf = df[df['timestamp'].dt.dayofyear <= 10]\n\n# === Step 1.2: Select only important columns ===\nimportant_features = [\n    'volume', 'buy_qty', 'sell_qty', 'ask_qty', 'bid_qty', 'X853', 'X876', 'X875', 'X379', 'X367',\n    'X361', 'X373', 'X385', 'X355', 'X752', 'X537', 'X882', 'X586', 'X610', 'X883', 'X748', 'X611',\n    'X169', 'X175', 'X533', 'X540', 'X687', 'X751', 'X526', 'X271', 'X890', 'X852', 'X531', 'X756',\n    'X320', 'X693', 'X426', 'X211', 'X344', 'X415', 'X204', 'X24', 'X225', 'X880', 'X25', 'X270',\n    'X308', 'X815', 'X224', 'X272', 'X757', 'X205', 'X753', 'X9', 'X197', 'X881', 'X538', 'X422',\n    'X409', 'X414', 'X408', 'X600', 'X198', 'X420', 'X755', 'X338', 'X90', 'X302', 'X226', 'X231',\n    'X839', 'X326', 'X421', 'X332', 'X580', 'X196', 'X862', 'X203', 'X31', 'X416', 'X539', 'X516',\n    'X835', 'X160', 'X488', 'X181', 'X314', 'X482', 'X854', 'X371', 'X232', 'X23', 'X301', 'X94',\n    'X26', 'X303', 'X860', 'X612', 'X93', 'X300', 'X377', 'X133', 'X212', 'X88', 'X879', 'X298',\n    'X730', 'X218', 'X428', 'X187', 'X604', 'X427', 'X219', 'X299', 'X32', 'X887', 'X139', 'X136',\n    'X217', 'X530', 'X803', 'X166', 'X696', 'X162', 'X340', 'X489', 'X96', 'X337', 'X728', 'X210',\n    'X386', 'X886', 'X398', 'X436', 'X19', 'X269', 'X349', 'X233', 'X866', 'X811', 'X95', 'X392',\n    'X184', 'X795', 'X87', 'X805', 'X16', 'X754', 'X383', 'X17', 'X297', 'X189', 'X525', 'X509',\n    'X345', 'X744', 'X404', 'X350', 'X808', 'X419', 'X750', 'X605', 'X827', 'X27', 'X28', 'X490',\n    'X483', 'X596', 'X40', 'X608', 'X820', 'X485', 'X296', 'X888', 'X22', 'X681', 'X295', 'X486',\n    'X125', 'X606', 'X397', 'X191', 'X749', 'X855', 'X690', 'X481', 'X266', 'X185', 'X491', 'X799',\n    'X403', 'X512'\n]\ndf = df[['timestamp'] + important_features]\n\n# === Step 1.3: Optimize numeric types ===\ndef optimize_dataframe(df):\n    for col in df.columns:\n        if pd.api.types.is_numeric_dtype(df[col]):\n            c_min = df[col].min()\n            c_max = df[col].max()\n            if pd.api.types.is_integer_dtype(df[col]):\n                if c_min >= np.iinfo(np.int8).min and c_max <= np.iinfo(np.int8).max:\n                    df[col] = df[col].astype(np.int8)\n                elif c_min >= np.iinfo(np.int16).min and c_max <= np.iinfo(np.int16).max:\n                    df[col] = df[col].astype(np.int16)\n                elif c_min >= np.iinfo(np.int32).min and c_max <= np.iinfo(np.int32).max:\n                    df[col] = df[col].astype(np.int32)\n            else:\n                if c_min >= np.finfo(np.float16).min and c_max <= np.finfo(np.float16).max:\n                    df[col] = df[col].astype(np.float16)\n                elif c_min >= np.finfo(np.float32).min and c_max <= np.finfo(np.float32).max:\n                    df[col] = df[col].astype(np.float32)\n    return df\n\ndf = optimize_dataframe(df)\n\n# === Step 1.4: Add time-based slots ===\ndf['minute'] = df['timestamp'].dt.hour * 60 + df['timestamp'].dt.minute\ndf['dayofyear'] = df['timestamp'].dt.dayofyear - 1\ndf['minute_of_year'] = df['dayofyear'] * 1440 + df['minute']\n\n# === Step 1.5: Fill missing minutes per day ===\nfull_minutes = pd.DataFrame({'minute': np.arange(1440)})\nfeature_cols = important_features\nglobal_mean = df[feature_cols].astype(np.float64).mean()\n\ndaily_sequences = []\n\nfor date, group in df.groupby(df['timestamp'].dt.date):\n    day_index = group['timestamp'].dt.dayofyear.iloc[0] - 1\n    grp = group.groupby('minute')[feature_cols].mean().reset_index()\n    merged = full_minutes.merge(grp, on='minute', how='left')\n    filled = merged[feature_cols].fillna(global_mean)\n    merged = pd.concat([merged[['minute']], filled], axis=1)\n    merged['dayofyear'] = day_index\n    merged['minute_of_year'] = day_index * 1440 + merged['minute']\n    daily_sequences.append(merged)\n\nfilled_df = pd.concat(daily_sequences, ignore_index=True).copy()\n\n# === Step 1.6: Compute and Smooth Anchor Matrix ===\nanchors = np.zeros((365 * 1440, len(feature_cols)), dtype=np.float32)\ncounts = np.zeros((365 * 1440,), dtype=np.int32)\n\nfor _, row in filled_df.iterrows():\n    idx = int(row['minute_of_year'])\n    anchors[idx] += row[feature_cols].values\n    counts[idx] += 1\n\nmask = counts > 0\nanchors[mask] /= counts[mask][:, None]\n\n# === Apply Savitzky-Golay smoothing (window=9, polyorder=2) ===\nfor i in range(anchors.shape[1]):\n    anchors[:, i] = savgol_filter(anchors[:, i], window_length=9, polyorder=2, mode='interp')\n\nanchors_df = pd.DataFrame(anchors, columns=feature_cols)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-30T05:56:51.613664Z","iopub.execute_input":"2025-05-30T05:56:51.614147Z","iopub.status.idle":"2025-05-30T05:57:06.560472Z","shell.execute_reply.started":"2025-05-30T05:56:51.614119Z","shell.execute_reply":"2025-05-30T05:57:06.559889Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport torch.nn.functional as F\n\nclass RecyclingTemporalScoringModel(nn.Module):\n    def __init__(self, feature_dim, hidden_dim=256, cnn_channels=64):\n        super().__init__()\n\n        self.cnn = nn.Sequential(\n            nn.Conv1d(1, cnn_channels, kernel_size=3, padding=1),\n            nn.ReLU(),\n            nn.BatchNorm1d(cnn_channels),\n            nn.Conv1d(cnn_channels, cnn_channels, kernel_size=3, padding=1),\n            nn.ReLU(),\n            nn.AdaptiveAvgPool1d(1),\n        )\n\n        self.attn = nn.MultiheadAttention(embed_dim=cnn_channels, num_heads=4, batch_first=True)\n\n        self.seq_encoder = nn.Sequential(\n            nn.Linear(cnn_channels, hidden_dim),\n            nn.ReLU(),\n            nn.BatchNorm1d(hidden_dim),\n            nn.Linear(hidden_dim, hidden_dim)\n        )\n\n        self.recycle_encoder = nn.Sequential(\n            nn.Linear(feature_dim, hidden_dim),\n            nn.ReLU(),\n            nn.BatchNorm1d(hidden_dim),\n            nn.Linear(hidden_dim, hidden_dim)\n        )\n\n        self.decoder = nn.Sequential(\n            nn.Linear(hidden_dim, hidden_dim),\n            nn.ReLU(),\n            nn.Linear(hidden_dim, feature_dim)\n        )\n\n        self.scorer = nn.CosineSimilarity(dim=1)\n\n    def forward(self, x_feat, prev_anchor_feat):\n        # CNN + Attention\n        x_cnn = x_feat.unsqueeze(1)\n        cnn_out = self.cnn(x_cnn).squeeze(2)\n        attn_out, _ = self.attn(cnn_out.unsqueeze(1), cnn_out.unsqueeze(1), cnn_out.unsqueeze(1))\n        x_embed = self.seq_encoder(attn_out.squeeze(1))\n\n        anchor_embed = self.recycle_encoder(prev_anchor_feat)\n        reconstructed_x = self.decoder(anchor_embed)\n\n        similarity = self.scorer(x_embed, anchor_embed)\n        return similarity, reconstructed_x\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-30T05:57:31.754869Z","iopub.execute_input":"2025-05-30T05:57:31.755666Z","iopub.status.idle":"2025-05-30T05:57:31.762458Z","shell.execute_reply.started":"2025-05-30T05:57:31.75564Z","shell.execute_reply":"2025-05-30T05:57:31.761937Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nfrom torch.utils.data import Dataset, DataLoader\nimport numpy as np\n\n# === Anchor Matrix ===\nanchors = np.zeros((365 * 1440, len(feature_cols)), dtype=np.float32)\ncounts = np.zeros((365 * 1440,), dtype=np.int32)\n\nfor _, row in filled_df.iterrows():\n    idx = int(row['minute_of_year'])\n    anchors[idx] += row[feature_cols].values\n    counts[idx] += 1\n\nmask = counts > 0\nanchors[mask] /= counts[mask][:, None]\nanchors_tensor = torch.tensor(anchors, dtype=torch.float32).cuda()\n\n# === Dataset ===\nclass AnchorPairDataset(Dataset):\n    def __init__(self, df, feature_cols, anchors):\n        self.X = torch.tensor(df[feature_cols].values, dtype=torch.float32)\n        self.idx = torch.tensor(df['minute_of_year'].values, dtype=torch.long)\n        self.anchors = anchors\n\n    def __len__(self):\n        return len(self.X)\n\n    def __getitem__(self, i):\n        return self.X[i], self.idx[i]\n\ndataset = AnchorPairDataset(filled_df, feature_cols, anchors_tensor)\nloader = DataLoader(dataset, batch_size=1024, shuffle=True, num_workers=4, pin_memory=True)\n\n# === Model + Optimizer ===\nbase_model = RecyclingTemporalScoringModel(feature_dim=len(feature_cols))\nmodel = torch.nn.DataParallel(base_model).cuda()\n\noptimizer = torch.optim.AdamW(model.parameters(), lr=1e-4, weight_decay=1e-4)\nloss_fn = torch.nn.MSELoss()\nscaler = torch.cuda.amp.GradScaler()\n\n# === Training Loop with Inline Chunked Recycling + Reconstruction Loss ===\nEPOCHS = 100\nrecycles = 1\nchunk_size = 500  # Memory-safe chunk\n\nfor epoch in range(1, EPOCHS + 1):\n    model.train()\n    total_loss = 0\n    for x, idx in loader:\n        x = x.cuda(non_blocking=True)\n        idx = idx.cuda(non_blocking=True)\n        target_anchor = anchors_tensor[idx]\n\n        optimizer.zero_grad()\n        loss = 0.0\n        prev_anchor = torch.zeros_like(target_anchor)\n\n        for _ in range(recycles):\n            with torch.amp.autocast(\"cuda\"):\n                pred_score, x_recon = model(x, prev_anchor)\n                loss_sim = loss_fn(pred_score, torch.ones_like(pred_score))\n                loss_recon = loss_fn(x_recon, x)\n                loss_step = loss_sim + loss_recon\n            loss += loss_step / recycles\n\n            # === Inline Chunked Anchor Scoring ===\n            scores = []\n            for i in range(0, anchors_tensor.shape[0], chunk_size):\n                chunk = anchors_tensor[i:i + chunk_size]\n                x_repeat = x.unsqueeze(1).expand(-1, chunk.size(0), -1).reshape(-1, x.shape[1])\n                chunk_repeat = chunk.unsqueeze(0).expand(x.size(0), -1, -1).reshape(-1, x.shape[1])\n                with torch.no_grad():\n                    sim, _ = model(x_repeat, chunk_repeat)\n                    sim = sim.view(x.size(0), -1)\n                scores.append(sim.detach().cpu())\n\n            scores = torch.cat(scores, dim=1)  # (B, N)\n            best_idx = scores.argmax(dim=1).to(x.device)\n            prev_anchor = anchors_tensor[best_idx]\n\n        scaler.scale(loss).backward()\n        scaler.step(optimizer)\n        scaler.update()\n        total_loss += loss.item() * x.size(0)\n\n    print(f\"[Epoch {epoch}] Loss: {total_loss / len(loader.dataset):.6f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-30T05:57:36.029589Z","iopub.execute_input":"2025-05-30T05:57:36.030227Z","execution_failed":"2025-05-30T06:14:04.94Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport pandas as pd\nimport numpy as np\n\ndef slot_to_day_minute(slot):\n    day = slot // 1440\n    minute = slot % 1440\n    return day, minute\n\n# === Inference with Recycling Loop (CPU) ===\nmodel.cpu()  # ensure model is on CPU\n\nsample_df = filled_df.sample(n=5, random_state=42).reset_index(drop=True)\nsample_x = torch.tensor(sample_df[feature_cols].values, dtype=torch.float32)\ntrue_slots = sample_df['minute_of_year'].values\n\nmodel = model.module\nmodel.cpu()\nmodel.eval()\nrecycles = 1\nfinal_preds = []\nreconstruction_errors = []\n\nwith torch.no_grad():\n    for x in sample_x:\n        prev_anchor = None\n        for _ in range(recycles):\n            x_batch = x.unsqueeze(0).repeat(anchors_tensor.shape[0], 1)\n            scores, _ = model(x_batch, anchors_tensor)\n            best_idx = scores.argmax().item()\n            prev_anchor = anchors_tensor[best_idx]\n        final_preds.append(best_idx)\n\n# === Print results ===\nfor i in range(len(sample_x)):\n    true_day, true_min = slot_to_day_minute(true_slots[i])\n    pred_day, pred_min = slot_to_day_minute(final_preds[i])\n    x_true = sample_x[i].numpy()\n    x_pred = anchors_tensor[final_preds[i]].numpy()\n\n    abs_diff = np.abs(x_true - x_pred)\n    mean_abs_error = abs_diff.mean()\n\n    print(f\"[Sample {i}]\")\n    print(f\"  True Slot:      Day {true_day}, Minute {true_min}  (slot {true_slots[i]})\")\n    print(f\"  Predicted Slot: Day {pred_day}, Minute {pred_min}  (slot {final_preds[i]})\")\n    print(f\"  Mean Absolute Feature Diff: {mean_abs_error:.6f}\")\n    print(\"  Top 5 Feature Differences:\")\n    top5_idx = abs_diff.argsort()[-5:][::-1]\n    for j in top5_idx:\n        fname = feature_cols[j]\n        print(f\"    {fname:<15}  |  True: {x_true[j]:>8.4f}  |  Pred: {x_pred[j]:>8.4f}  |  Δ: {abs_diff[j]:.4f}\")\n    print(\"  ---\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-29T15:13:12.77676Z","iopub.execute_input":"2025-05-29T15:13:12.777404Z","iopub.status.idle":"2025-05-29T15:19:34.730015Z","shell.execute_reply.started":"2025-05-29T15:13:12.777373Z","shell.execute_reply":"2025-05-29T15:19:34.729482Z"}},"outputs":[],"execution_count":null}]}