File size: 8,435 Bytes
818e006
 
 
 
 
fc24755
 
818e006
 
34614be
818e006
34614be
 
fc24755
34614be
 
818e006
34614be
 
 
 
 
 
 
818e006
60163b7
818e006
34614be
818e006
34614be
818e006
 
4ca194f
 
 
 
 
 
818e006
 
4ca194f
 
818e006
 
4ca194f
818e006
 
 
60163b7
34614be
e6dcbc7
34614be
e6dcbc7
34614be
 
 
 
818e006
 
34614be
818e006
 
 
 
 
34614be
2b559f3
34614be
818e006
 
 
 
 
 
34614be
 
 
818e006
60163b7
34614be
65cc758
 
34614be
 
818e006
fc24755
34614be
 
 
60163b7
34614be
 
 
fc24755
818e006
 
34614be
 
 
 
 
 
 
4ca194f
 
 
 
 
 
 
34614be
e6dcbc7
4ca194f
34614be
818e006
34614be
 
 
 
 
 
 
 
 
 
 
 
e6dcbc7
 
34614be
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e6dcbc7
34614be
 
 
 
 
 
e6dcbc7
34614be
 
e6dcbc7
34614be
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4ca194f
 
 
 
 
 
 
 
818e006
 
34614be
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
import gradio as gr
import torch
import numpy as np
import pandas as pd
import plotly.graph_objects as go
import os
import sys
from datasets import load_dataset
from src.models import get_model
from src.engine import quantize_model

# --- CONFIGURATION ---
DATASET_NAME = "aayushkrm/wunder-fund-hft-data" 

# --- LOAD DATASET ---
print("Initializing App...")
try:
    print("Loading FULL dataset from Hugging Face...")
    dataset = load_dataset(DATASET_NAME, split="train")
    df = dataset.to_pandas()
    df['seq_ix'] = df['seq_ix'].astype(int)
    unique_ids_np = df['seq_ix'].unique()
    SEQ_IDS = sorted([int(x) for x in unique_ids_np.tolist()])
    print(f"βœ… Loaded {len(df)} rows.")
except Exception as e:
    print(f"⚠️ Could not load HF dataset: {e}")
    df = None
    SEQ_IDS = [0, 42, 100, 500]

# --- CACHED MODEL LOADER ---
def load_cached_model():
    model = get_model("winner", input_size=32, hidden_size=256, layers=6)
    # Check current directory first, then artifacts
    if os.path.exists("best_model.pt"):
        model_path = "best_model.pt"
    else:
        model_path = "artifacts/best_model.pt"

    if os.path.exists(model_path):
        try:
            # FIX 1: Added weights_only=False to allow loading quantized/scripted models
            state = torch.load(model_path, map_location='cpu', weights_only=False)
            state = {k: v.float() for k, v in state.items()}
            model.load_state_dict(state)
            print(f"βœ… Loaded {model_path}")
        except Exception as e:
            print(f"⚠️ Error loading model: {e}")
    else:
        print("⚠️ Model file not found, using random weights.")
    return quantize_model(model)

MODEL = load_cached_model()

def inference(seq_id_input, steps_input):
    seq_id = int(seq_id_input)
    steps_to_plot = int(steps_input)
    
    if df is not None:
        seq_data = df[df['seq_ix'] == seq_id].sort_values('step_in_seq')
        raw_values = seq_data[[str(i) for i in range(32)]].values.astype(np.float32) if len(seq_data) > 0 else np.random.randn(1000, 32).astype(np.float32)
        mean = raw_values.mean(axis=0)
        std = raw_values.std(axis=0) + 1e-6
        norm_values = (raw_values - mean) / std
    else:
        norm_values = np.random.randn(1000, 32).astype(np.float32)

    x = torch.tensor(norm_values).unsqueeze(0)
    
    with torch.no_grad():
        preds = []
        h = None
        for t in range(min(len(x[0]), steps_to_plot)):
            xt = x[:, t:t+1, :]
            o, h = MODEL(xt, h)
            preds.append(float(o.numpy()[0,0,0]))
            
    # Plotly Chart
    fig = go.Figure()
    y_actual = [float(v) for v in norm_values[:steps_to_plot, 0].flatten()]
    y_pred = preds
    x_axis = list(range(len(y_actual)))
    
    fig.add_trace(go.Scatter(x=x_axis, y=y_actual, mode='lines', name='Actual Market Feature', line=dict(color='gray', width=1, dash='dot')))
    fig.add_trace(go.Scatter(x=x_axis, y=y_pred, mode='lines', name='Model Prediction (SE-Mish-GRU)', line=dict(color='#00ff00', width=2)))
    
    fig.update_layout(
        title=f"Sequence {seq_id} | Lookahead: 1 Step", 
        xaxis_title="Time Step (t)", 
        yaxis_title="Normalized Price/Volume Feature",
        template="plotly_dark",
        height=450,
        hovermode="x unified",
        margin=dict(l=20, r=20, t=50, b=20)
    )
    return fig

# ==========================================
# PROFESSIONAL UI LAYOUT
# ==========================================
custom_css = """
footer {visibility: hidden;}
.gradio-container {max-width: 900px !important;}
h1 {text-align: center;}
.metric-card {
    background-color: #222; 
    padding: 15px; 
    border-radius: 10px; 
    text-align: center; 
    border: 1px solid #444;
}
"""

# FIX 2: Moved theme/css to launch() logic or kept here but cleaned up deprecated syntax below
with gr.Blocks(theme=gr.themes.Monochrome(), css=custom_css) as demo:
    
    # HEADER
    gr.Markdown("# ⚑ Quant-Lab: Efficient High-Frequency Trading")
    gr.Markdown("### **Rank 28 (Top 1%)** in the Wunder Fund RNN Challenge")
    gr.Markdown(
        "A resource-constrained Deep Learning pipeline optimized for sub-millisecond CPU inference and strict 20MB memory footprints. "
        "Developed by **Aayush Kumar**."
    )

    with gr.Tabs():
        # --- TAB 1: INTERACTIVE DEMO ---
        with gr.Tab("πŸ“ˆ Interactive Forecast"):
            gr.Markdown("Select a market sequence to see the quantized **SE-Mish-GRU** perform real-time prediction.")
            
            with gr.Row():
                seq_selector = gr.Dropdown(choices=SEQ_IDS, label="Market Sequence ID", value=SEQ_IDS[0], scale=2)
                step_slider = gr.Slider(minimum=50, maximum=1000, value=200, label="Steps to Predict", scale=2)
                btn = gr.Button("⚑ Run Inference", variant="primary", scale=1)
            
            plot = gr.Plot(label="Forecast Visualization")
            
            # Preset Examples
            gr.Examples(
                examples=[
                    [int(SEQ_IDS[2]), 200],
                    [int(SEQ_IDS[10]), 500],
                    [int(SEQ_IDS[42]), 1000],
                ],
                inputs=[seq_selector, step_slider],
                label="Interesting Market Regimes"
            )
            
            btn.click(inference, inputs=[seq_selector, step_slider], outputs=plot)

        # --- TAB 2: TECHNICAL REPORT ---
        with gr.Tab("πŸ”¬ Technical Report & Architecture"):
            gr.Markdown("""
            ### The Challenge
            Predict high-frequency financial time-series $X_{t+1}$ given history $X_{0...t}$. 
            **Constraints:** Maximum solution size 20 MB, inference on 1 CPU core < 60 mins.

            ### The Winning Solution: SE-Mish-Swarm
            After testing 42 architectures, the best solution was a **10-Model Quantized Ensemble** of Deep Residual GRUs with custom gating.
            
            1. **Mish Activation:** Replaced standard `ReLU/Tanh` with `Mish`. This preserved gradient flow across 6-layer deep RNNs, which is critical for highly noisy financial data.
            2. **Squeeze-and-Excitation (SE-Block):** Integrated 1D channel attention *before* the RNN loop. This allows the model to dynamically suppress "noisy" features based on the current market state.
            3. **INT8 Dynamic Quantization:** Implemented Post-Training Quantization (PTQ) to reduce model sizes by **73%** (5MB $\\rightarrow$ 1.3MB) with 0.0% accuracy degradation.
            4. **Variance Reduction:** Fitting 10 models inside the 20MB limit allowed for extreme ensemble robustness, surviving the Private Leaderboard shakeup.
            """)

        # --- TAB 3: ABLATION STUDY ---
        with gr.Tab("πŸ“Š Ablation Study (Failures)"):
            gr.Markdown("""
            ### What Failed and Why?
            Treating this challenge as a research residency, I benchmarked modern SOTA architectures against traditional approaches.

            | Architecture / Strategy | Outcome | Reason for Failure |
            | :--- | :--- | :--- |
            | **Transformers (XL, Performer)** | ❌ Failed | Context fragmentation and severe overfitting to noise. |
            | **Mamba-2 (SSM)** | ❌ Failed | High training instability (NaNs) in floating-point financial data. |
            | **WaveNet (Causal CNN)** | ❌ Failed | Inference timeout. Recomputing large receptive fields on CPU is $O(N)$ per step. |
            | **Feature Diffing ($X_t - X_{t-1}$)** | ❌ Failed | Models lost spatial anchor points, failing to reconstruct absolute price levels. |
            | **SE-Mish-GRU (Selected)** | βœ… **Winner** | Maximum inductive bias for time-series combined with scale compression. |
            """)

        # --- TAB 4: METRICS ---
        with gr.Tab("⏱️ Performance Metrics"):
            # FIX 3: Replaced deprecated 'gr.columns' and '.metric()' with 'gr.Row' and Markdown
            with gr.Row():
                with gr.Column():
                    gr.Markdown("### RΒ² Score (Private LB)\n# 0.3873\n**+14.5% vs Baseline**")
                with gr.Column():
                    gr.Markdown("### Model Size (Quantized)\n# 1.8 MB\n**-3.2 MB (FP32)**")
                with gr.Column():
                    gr.Markdown("### Inference Latency\n# < 1.0 ms\n**Per step, CPU**")

if __name__ == "__main__":
    demo.launch()