Trading-Benchmark-Model
Collection
Models for portfolio construction
•
8 items
•
Updated
class CausalTimeConv2d(nn.Conv2d):
"""
Input: [B, C=in_ch, H=stocks, W=time]
kernel_size=(ksz,1), dilation=(dil,1), padding=(0,0) # important!
"""
def __init__(
self,
in_channel: int,
out_channel: int,
kernel_size: int = 4,
dilation: int = 1,
bias: bool = False,
) -> None:
super().__init__(
in_channel,
out_channel,
kernel_size=(1, kernel_size),
stride=(1, 1),
padding=(0, 0),
dilation=(1, dilation),
bias=bias,
)
self.pad_w = (kernel_size - 1) * dilation
def forward(self, input: torch.Tensor) -> torch.Tensor:
if self.pad_w > 0:
input = F.pad(input, (self.pad_w, 0, 0, 0))
return super().forward(input)
class ParallelTCNBlock(nn.Module):
def __init__(
self,
in_channel: int,
out_channel: int,
kernel_size: int = 4,
dilation: int = 1,
dropout: float = 0.0,
) -> None:
super().__init__()
self.conv1 = CausalTimeConv2d(
in_channel, out_channel, kernel_size, dilation, bias=False
)
self.relu1 = nn.ReLU(inplace=True)
self.conv2 = CausalTimeConv2d(
out_channel, out_channel, kernel_size, dilation, bias=False
)
self.relu2 = nn.ReLU(inplace=True)
self.drop = nn.Dropout(dropout) if dropout > 0 else nn.Identity()
self.down = (
nn.Conv2d(in_channel, out_channel, kernel_size=1, bias=False)
if in_channel != out_channel
else nn.Identity()
)
def forward(self, x): # x: [B, C, S, T]
y = self.relu1(self.conv1(x)) # width T preserved
y = self.relu2(self.conv2(y)) # width T preserved
y = self.drop(y)
# residual width must match; no extra padding here
res = self.down(x)
# Optional assert to catch shape drift during dev:
# assert y.shape == res.shape, f"{y.shape} vs {res.shape}"
return torch.relu_(y + res)
class TCN(nn.Module):
"""
Parallel TCN over [B, T, S, F]:
- Converts to [B, F, S, T]
- Applies dilated causal Conv2d with kernel (k,1) so each stock is independent but parallel
- Takes the last time step (T) and projects to c_out
"""
def __init__(
self,
enc_in: int,
c_out: int,
d_model: int,
d_ff: int,
e_layers: int,
kernel_size: int = 4,
dropout: float = 0.0,
) -> None:
super().__init__()
blocks = []
for i in range(e_layers):
in_ch = enc_in if i == 0 else d_model
dil = 2**i
blocks.append(
ParallelTCNBlock(
in_ch, d_model, kernel_size=kernel_size, dilation=dil, dropout=dropout
)
)
self.tcn = nn.Sequential(*blocks)
self.proj = nn.Sequential(
nn.Linear(d_model, d_ff, bias=True),
nn.GELU(),
nn.Linear(d_ff, c_out, bias=True),
)
def forward(self, x: torch.Tensor) -> torch.Tensor:
B, T, S, F = x.shape
x = x.permute(0, 3, 2, 1).contiguous() # [b, f, s, t]
y = self.tcn(x) # [B, d_model, S, T]
last = y[:, :, :, -1] # take last time step -> [B, d_model, S]
out = self.proj(last.transpose(1, 2)) # [B, S, c_out]
return out.squeeze(-1) # [B, S] if c_out=1
enc_in: 8
c_out: 1
d_model: 64
d_ff: 64
e_layers: 2
kernel_size: 4
dropout: 0.0