Ultron28 commited on
Commit
64d4dfc
·
verified ·
1 Parent(s): f6044b4

⚡ ULTRON: Sync model.py

Browse files
Files changed (1) hide show
  1. model.py +295 -0
model.py ADDED
@@ -0,0 +1,295 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ ⚡ ULTRON Systems // Advanced Convolutional Transformer Architecture for OHLCV
3
+ Implements:
4
+ 1. Reversible Instance Normalization (RevIN)
5
+ 2. Multi-Scale Overlapping 1D Convolutional Tokenizer (kernels 3, 7, 15, 31)
6
+ 3. Rotary Positional Embeddings (RoPE)
7
+ 4. Deep Conformer Blocks (Self-Attention + Depthwise Separable 1D Conv)
8
+ 5. Multi-Horizon Dual Forecasting Head (Return Magnitude + Directional Probability)
9
+ """
10
+ import math
11
+ import torch
12
+ import torch.nn as nn
13
+ import torch.nn.functional as F
14
+ from typing import List, Tuple
15
+
16
+ from config import ModelConfig
17
+
18
+ class RevIN(nn.Module):
19
+ """
20
+ Reversible Instance Normalization to eliminate non-stationary distribution drift.
21
+ Normalizes input sequences per-instance and per-feature.
22
+ """
23
+ def __init__(self, num_features: int, eps: float = 1e-5, affine: bool = True):
24
+ super().__init__()
25
+ self.num_features = num_features
26
+ self.eps = eps
27
+ self.affine = affine
28
+ if self.affine:
29
+ self.affine_weight = nn.Parameter(torch.ones(num_features))
30
+ self.affine_bias = nn.Parameter(torch.zeros(num_features))
31
+
32
+ def forward(self, x: torch.Tensor, mode: str = "norm") -> torch.Tensor:
33
+ # x: [Batch, SeqLen, Features]
34
+ if mode == "norm":
35
+ self.mean = torch.mean(x, dim=1, keepdim=True).detach()
36
+ self.stdev = torch.sqrt(torch.var(x, dim=1, keepdim=True, unbiased=False) + self.eps).detach()
37
+ x_norm = (x - self.mean) / self.stdev
38
+ if self.affine:
39
+ x_norm = x_norm * self.affine_weight + self.affine_bias
40
+ return x_norm
41
+ elif mode == "denorm":
42
+ x_denorm = x
43
+ if self.affine:
44
+ x_denorm = (x_denorm - self.affine_bias) / (self.affine_weight + self.eps)
45
+ return x_denorm * self.stdev + self.mean
46
+ else:
47
+ raise ValueError(f"Unknown RevIN mode: {mode}")
48
+
49
+ class MultiScaleOverlappingConvTokenizer(nn.Module):
50
+ """
51
+ Extracts multi-frequency candlestick representations via overlapping 1D convolutions.
52
+ Operates over overlapping temporal windows with kernel sizes [3, 7, 15, 31].
53
+ Stride S < K ensures heavy overlap between adjacent token receptive fields.
54
+ """
55
+ def __init__(self, in_features: int, d_model: int, kernels: List[int] = [3, 7, 15, 31], stride: int = 4):
56
+ super().__init__()
57
+ self.stride = stride
58
+ branch_dim = d_model // len(kernels)
59
+ self.branches = nn.ModuleList()
60
+
61
+ for k in kernels:
62
+ # Symmetric causal/same padding
63
+ pad = k // 2
64
+ self.branches.append(
65
+ nn.Sequential(
66
+ nn.Conv1d(in_features, branch_dim, kernel_size=k, stride=stride, padding=pad),
67
+ nn.GELU()
68
+ )
69
+ )
70
+
71
+ # Projection layer to fuse multi-scale features into exact d_model
72
+ concat_dim = branch_dim * len(kernels)
73
+ self.proj = nn.Linear(concat_dim, d_model)
74
+ self.norm = nn.LayerNorm(d_model)
75
+
76
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
77
+ # x: [Batch, SeqLen, InFeatures] -> [Batch, InFeatures, SeqLen]
78
+ x_trans = x.transpose(1, 2)
79
+
80
+ branch_outs = []
81
+ min_len = None
82
+ for branch in self.branches:
83
+ out = branch(x_trans)
84
+ if min_len is None or out.shape[2] < min_len:
85
+ min_len = out.shape[2]
86
+ branch_outs.append(out)
87
+
88
+ # Align temporal length across all branches
89
+ aligned = [b[:, :, :min_len] for b in branch_outs]
90
+ fused = torch.cat(aligned, dim=1) # [Batch, concat_dim, Patches]
91
+
92
+ # Transpose back: [Batch, Patches, concat_dim]
93
+ fused = fused.transpose(1, 2)
94
+ return self.norm(self.proj(fused))
95
+
96
+ class RotaryEmbedding(nn.Module):
97
+ """Rotary Position Embeddings (RoPE) for sequence tokens."""
98
+ def __init__(self, dim: int, max_seq_len: int = 2048):
99
+ super().__init__()
100
+ inv_freq = 1.0 / (10000 ** (torch.arange(0, dim, 2).float() / dim))
101
+ self.register_buffer("inv_freq", inv_freq)
102
+ t = torch.arange(max_seq_len, dtype=torch.float32)
103
+ freqs = torch.einsum("i,j->ij", t, self.inv_freq)
104
+ emb = torch.cat((freqs, freqs), dim=-1)
105
+ self.register_buffer("cos_cached", emb.cos(), persistent=False)
106
+ self.register_buffer("sin_cached", emb.sin(), persistent=False)
107
+
108
+ def forward(self, q: torch.Tensor, k: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]:
109
+ seq_len = q.shape[2]
110
+ cos = self.cos_cached[:seq_len, :].unsqueeze(0).unsqueeze(0) # [1, 1, seq_len, dim]
111
+ sin = self.sin_cached[:seq_len, :].unsqueeze(0).unsqueeze(0)
112
+
113
+ def rotate_half(x):
114
+ x1 = x[..., :x.shape[-1] // 2]
115
+ x2 = x[..., x.shape[-1] // 2:]
116
+ return torch.cat((-x2, x1), dim=-1)
117
+
118
+ q_rot = (q * cos) + (rotate_half(q) * sin)
119
+ k_rot = (k * cos) + (rotate_half(k) * sin)
120
+ return q_rot, k_rot
121
+
122
+ class ConformerConvModule(nn.Module):
123
+ """
124
+ Depthwise Separable Convolution block to model fine-grained micro-structure
125
+ and local volatility gradients.
126
+ """
127
+ def __init__(self, d_model: int, kernel_size: int = 7, dropout: float = 0.1):
128
+ super().__init__()
129
+ self.norm = nn.LayerNorm(d_model)
130
+ # Pointwise expansion
131
+ self.pointwise1 = nn.Conv1d(d_model, 2 * d_model, kernel_size=1)
132
+ # Depthwise conv
133
+ self.depthwise = nn.Conv1d(
134
+ d_model, d_model,
135
+ kernel_size=kernel_size,
136
+ stride=1,
137
+ padding=kernel_size // 2,
138
+ groups=d_model
139
+ )
140
+ self.bn = nn.BatchNorm1d(d_model)
141
+ self.act = nn.SiLU()
142
+ # Pointwise projection
143
+ self.pointwise2 = nn.Conv1d(d_model, d_model, kernel_size=1)
144
+ self.dropout = nn.Dropout(dropout)
145
+
146
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
147
+ # x: [Batch, Patches, d_model]
148
+ res = x
149
+ x = self.norm(x).transpose(1, 2)
150
+ # Pointwise expansion with Gated Linear Unit
151
+ x = self.pointwise1(x)
152
+ x = F.glu(x, dim=1) # [Batch, d_model, Patches]
153
+ # Depthwise
154
+ x = self.depthwise(x)
155
+ x = self.bn(x)
156
+ x = self.act(x)
157
+ x = self.pointwise2(x)
158
+ x = self.dropout(x)
159
+ return res + x.transpose(1, 2)
160
+
161
+ class ConformerBlock(nn.Module):
162
+ """
163
+ Hybrid Convolution + FlashAttention Conformer Block.
164
+ """
165
+ def __init__(self, d_model: int, n_heads: int = 4, d_ff: int = 256, conv_kernel: int = 7, dropout: float = 0.1):
166
+ super().__init__()
167
+ self.d_model = d_model
168
+ self.n_heads = n_heads
169
+ self.head_dim = d_model // n_heads
170
+
171
+ # 1. Multi-Head Self-Attention with RoPE
172
+ self.ln_attn = nn.LayerNorm(d_model)
173
+ self.q_proj = nn.Linear(d_model, d_model, bias=False)
174
+ self.k_proj = nn.Linear(d_model, d_model, bias=False)
175
+ self.v_proj = nn.Linear(d_model, d_model, bias=False)
176
+ self.out_proj = nn.Linear(d_model, d_model)
177
+ self.rope = RotaryEmbedding(self.head_dim)
178
+ self.attn_dropout = dropout
179
+
180
+ # 2. Conformer Convolution Module
181
+ self.conv_module = ConformerConvModule(d_model, kernel_size=conv_kernel, dropout=dropout)
182
+
183
+ # 3. Feed-Forward SwiGLU Network
184
+ self.ln_ffn = nn.LayerNorm(d_model)
185
+ self.ffn = nn.Sequential(
186
+ nn.Linear(d_model, d_ff),
187
+ nn.GELU(),
188
+ nn.Dropout(dropout),
189
+ nn.Linear(d_ff, d_model),
190
+ nn.Dropout(dropout)
191
+ )
192
+
193
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
194
+ # Step 1: Pre-LN Self Attention with FlashAttention-2 / scaled_dot_product_attention
195
+ res = x
196
+ x_norm = self.ln_attn(x)
197
+ B, T, C = x_norm.shape
198
+
199
+ q = self.q_proj(x_norm).view(B, T, self.n_heads, self.head_dim).transpose(1, 2)
200
+ k = self.k_proj(x_norm).view(B, T, self.n_heads, self.head_dim).transpose(1, 2)
201
+ v = self.v_proj(x_norm).view(B, T, self.n_heads, self.head_dim).transpose(1, 2)
202
+
203
+ # Apply RoPE
204
+ q, k = self.rope(q, k)
205
+
206
+ # Native PyTorch FlashAttention / SDPA
207
+ attn_out = F.scaled_dot_product_attention(
208
+ q, k, v,
209
+ dropout_p=self.attn_dropout if self.training else 0.0,
210
+ is_causal=True # Causal temporal mask ensures no future leakage
211
+ )
212
+ attn_out = attn_out.transpose(1, 2).contiguous().view(B, T, C)
213
+ x = res + self.out_proj(attn_out)
214
+
215
+ # Step 2: Conformer Depthwise Convolution
216
+ x = self.conv_module(x)
217
+
218
+ # Step 3: Feed-Forward
219
+ x = x + self.ffn(self.ln_ffn(x))
220
+ return x
221
+
222
+ class OHLCVTransformer(nn.Module):
223
+ """
224
+ End-to-End Advanced Convolutional Transformer for Multi-Horizon OHLCV Forecasting.
225
+ """
226
+ def __init__(self, cfg: ModelConfig):
227
+ super().__init__()
228
+ self.cfg = cfg
229
+ self.revin = RevIN(num_features=cfg.in_features) if cfg.use_revin else None
230
+
231
+ # Multi-scale overlapping tokenizer
232
+ self.tokenizer = MultiScaleOverlappingConvTokenizer(
233
+ in_features=cfg.in_features,
234
+ d_model=cfg.d_model,
235
+ kernels=cfg.conv_kernels,
236
+ stride=cfg.patch_stride
237
+ )
238
+
239
+ # Conformer Layers
240
+ self.blocks = nn.ModuleList([
241
+ ConformerBlock(
242
+ d_model=cfg.d_model,
243
+ n_heads=cfg.n_heads,
244
+ d_ff=cfg.d_ff,
245
+ conv_kernel=cfg.conv_depthwise_kernel,
246
+ dropout=cfg.dropout
247
+ ) for _ in range(cfg.n_layers)
248
+ ])
249
+
250
+ self.final_norm = nn.LayerNorm(cfg.d_model)
251
+
252
+ # Multi-Horizon Heads:
253
+ # 1. Return Regression Head: predicts expected log return for [1m, 3m, 5m, 10m, 15m]
254
+ self.reg_head = nn.Sequential(
255
+ nn.Linear(cfg.d_model, cfg.d_ff),
256
+ nn.GELU(),
257
+ nn.Dropout(cfg.dropout),
258
+ nn.Linear(cfg.d_ff, cfg.num_horizons)
259
+ )
260
+
261
+ # 2. Directional Classification Head: predicts binary direction probability
262
+ self.dir_head = nn.Sequential(
263
+ nn.Linear(cfg.d_model, cfg.d_ff),
264
+ nn.GELU(),
265
+ nn.Dropout(cfg.dropout),
266
+ nn.Linear(cfg.d_ff, cfg.num_horizons)
267
+ )
268
+
269
+ def forward(self, x: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]:
270
+ """
271
+ x: [Batch, SeqLen, InFeatures]
272
+ Returns:
273
+ pred_returns: [Batch, NumHorizons] (expected future log returns)
274
+ pred_logits: [Batch, NumHorizons] (logits for P(return > 0))
275
+ """
276
+ # RevIN normalisation
277
+ if self.revin is not None:
278
+ x = self.revin(x, mode="norm")
279
+
280
+ # Overlapping 1D conv tokenization
281
+ tokens = self.tokenizer(x) # [Batch, Patches, d_model]
282
+
283
+ # Transformer processing
284
+ for block in self.blocks:
285
+ tokens = block(tokens)
286
+
287
+ tokens = self.final_norm(tokens)
288
+
289
+ # Global aggregation: use final token (most recent causal context)
290
+ context = tokens[:, -1, :] # [Batch, d_model]
291
+
292
+ pred_returns = self.reg_head(context)
293
+ pred_logits = self.dir_head(context)
294
+
295
+ return pred_returns, pred_logits