Spaces:
Running
Running
Sync from GitHub (tests passed)
Browse files
deep_learning/calibration/conformal.py
CHANGED
|
@@ -33,6 +33,15 @@ def compute_nonconformity(actual: np.ndarray, lower: np.ndarray, upper: np.ndarr
|
|
| 33 |
return np.maximum(lower - actual, actual - upper)
|
| 34 |
|
| 35 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
def rolling_conformal_adjustment(
|
| 37 |
actual: np.ndarray,
|
| 38 |
lower: np.ndarray,
|
|
|
|
| 33 |
return np.maximum(lower - actual, actual - upper)
|
| 34 |
|
| 35 |
|
| 36 |
+
def interval_coverage(actual: np.ndarray, lower: np.ndarray, upper: np.ndarray) -> float:
|
| 37 |
+
actual_np = np.asarray(actual, dtype=np.float64)
|
| 38 |
+
lower_np = np.asarray(lower, dtype=np.float64)
|
| 39 |
+
upper_np = np.asarray(upper, dtype=np.float64)
|
| 40 |
+
if actual_np.size == 0:
|
| 41 |
+
return 0.0
|
| 42 |
+
return float(np.mean((actual_np >= lower_np) & (actual_np <= upper_np)))
|
| 43 |
+
|
| 44 |
+
|
| 45 |
def rolling_conformal_adjustment(
|
| 46 |
actual: np.ndarray,
|
| 47 |
lower: np.ndarray,
|
deep_learning/config.py
CHANGED
|
@@ -141,9 +141,11 @@ class WeeklyLossConfig:
|
|
| 141 |
lambda_dispersion: float = 0.35
|
| 142 |
lambda_magnitude: float = 0.55
|
| 143 |
lambda_naive: float = 0.40
|
| 144 |
-
lambda_bias: float = 0.
|
| 145 |
lambda_directional: float = 0.06
|
| 146 |
lambda_saturation: float = 0.25
|
|
|
|
|
|
|
| 147 |
weekly_median_cap_abs_median_multiple: float = 2.0
|
| 148 |
weekly_median_cap_mean_abs_multiple: float = 1.6
|
| 149 |
weekly_median_cap_std_multiple: float = 1.2
|
|
|
|
| 141 |
lambda_dispersion: float = 0.35
|
| 142 |
lambda_magnitude: float = 0.55
|
| 143 |
lambda_naive: float = 0.40
|
| 144 |
+
lambda_bias: float = 0.19
|
| 145 |
lambda_directional: float = 0.06
|
| 146 |
lambda_saturation: float = 0.25
|
| 147 |
+
lambda_positive_rate: float = 0.20
|
| 148 |
+
lambda_interval: float = 0.15
|
| 149 |
weekly_median_cap_abs_median_multiple: float = 2.0
|
| 150 |
weekly_median_cap_mean_abs_multiple: float = 1.6
|
| 151 |
weekly_median_cap_std_multiple: float = 1.2
|
deep_learning/models/tft_copper.py
CHANGED
|
@@ -135,7 +135,17 @@ def _weekly_scale_losses(
|
|
| 135 |
model_mae = torch.mean(torch.abs(pred_weekly_median - actual_weekly))
|
| 136 |
zero_mae = actual_abs_mean
|
| 137 |
naive_relative_loss = torch.relu((model_mae / zero_mae) - 1.0)
|
| 138 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
|
| 140 |
return {
|
| 141 |
"dispersion_loss": dispersion_loss,
|
|
@@ -147,6 +157,54 @@ def _weekly_scale_losses(
|
|
| 147 |
}
|
| 148 |
|
| 149 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 150 |
# ---------------------------------------------------------------------------
|
| 151 |
# Module-level ASRO loss class (must be at module level for pickle / checkpoint)
|
| 152 |
# ---------------------------------------------------------------------------
|
|
@@ -259,6 +317,8 @@ try:
|
|
| 259 |
lambda_naive: float = 0.40,
|
| 260 |
lambda_bias: float = 0.25,
|
| 261 |
lambda_saturation: float = 0.25,
|
|
|
|
|
|
|
| 262 |
weekly_median_cap: Optional[float] = None,
|
| 263 |
sharpe_eps: float = 1e-8,
|
| 264 |
debug_mode: bool = False,
|
|
@@ -272,6 +332,8 @@ try:
|
|
| 272 |
self.lambda_naive = lambda_naive
|
| 273 |
self.lambda_bias = lambda_bias
|
| 274 |
self.lambda_saturation = lambda_saturation
|
|
|
|
|
|
|
| 275 |
self.weekly_median_cap = weekly_median_cap
|
| 276 |
self.sharpe_eps = sharpe_eps
|
| 277 |
self.debug_mode = debug_mode
|
|
@@ -287,6 +349,8 @@ try:
|
|
| 287 |
"naive": 0.0,
|
| 288 |
"bias": 0.0,
|
| 289 |
"saturation": 0.0,
|
|
|
|
|
|
|
| 290 |
"directional": 0.0,
|
| 291 |
"total": 0.0,
|
| 292 |
}
|
|
@@ -301,6 +365,8 @@ try:
|
|
| 301 |
naive_relative_loss: torch.Tensor,
|
| 302 |
bias_loss: torch.Tensor,
|
| 303 |
saturation_loss: torch.Tensor,
|
|
|
|
|
|
|
| 304 |
directional_loss: torch.Tensor,
|
| 305 |
total_loss: torch.Tensor,
|
| 306 |
) -> None:
|
|
@@ -311,6 +377,8 @@ try:
|
|
| 311 |
self._component_sums["naive"] += float(naive_relative_loss.detach().mean().cpu())
|
| 312 |
self._component_sums["bias"] += float(bias_loss.detach().mean().cpu())
|
| 313 |
self._component_sums["saturation"] += float(saturation_loss.detach().mean().cpu())
|
|
|
|
|
|
|
| 314 |
self._component_sums["directional"] += float(directional_loss.detach().mean().cpu())
|
| 315 |
self._component_sums["total"] += float(total_loss.detach().mean().cpu())
|
| 316 |
self._component_batches += 1
|
|
@@ -327,6 +395,8 @@ try:
|
|
| 327 |
"naive_loss_mean": 0.0,
|
| 328 |
"bias_loss_mean": 0.0,
|
| 329 |
"saturation_loss_mean": 0.0,
|
|
|
|
|
|
|
| 330 |
"directional_loss_mean": 0.0,
|
| 331 |
"total_loss_mean": 0.0,
|
| 332 |
"dominant_component": None,
|
|
@@ -340,6 +410,8 @@ try:
|
|
| 340 |
"naive": self._component_sums["naive"],
|
| 341 |
"bias": self._component_sums["bias"],
|
| 342 |
"saturation": self._component_sums["saturation"],
|
|
|
|
|
|
|
| 343 |
"directional": self._component_sums["directional"],
|
| 344 |
}
|
| 345 |
return {
|
|
@@ -351,6 +423,8 @@ try:
|
|
| 351 |
"naive_loss_mean": self._component_sums["naive"] / n_batches,
|
| 352 |
"bias_loss_mean": self._component_sums["bias"] / n_batches,
|
| 353 |
"saturation_loss_mean": self._component_sums["saturation"] / n_batches,
|
|
|
|
|
|
|
| 354 |
"directional_loss_mean": self._component_sums["directional"] / n_batches,
|
| 355 |
"total_loss_mean": self._component_sums["total"] / n_batches,
|
| 356 |
"dominant_component": max(components, key=components.get),
|
|
@@ -420,6 +494,17 @@ try:
|
|
| 420 |
magnitude_loss = scale_losses["magnitude_loss"]
|
| 421 |
naive_relative_loss = scale_losses["naive_relative_loss"]
|
| 422 |
bias_loss = scale_losses["bias_loss"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 423 |
|
| 424 |
weekly_pred_direction = torch.tanh(pred_weekly_median * 10.0)
|
| 425 |
weekly_actual_direction = torch.sign(actual_weekly)
|
|
@@ -440,6 +525,8 @@ try:
|
|
| 440 |
naive_relative_loss = _to_scalar(naive_relative_loss)
|
| 441 |
bias_loss = _to_scalar(bias_loss)
|
| 442 |
saturation_loss = _to_scalar(saturation_loss)
|
|
|
|
|
|
|
| 443 |
directional_loss = _to_scalar(directional_loss)
|
| 444 |
|
| 445 |
total_loss = (
|
|
@@ -450,6 +537,8 @@ try:
|
|
| 450 |
+ self.lambda_naive * _to_scalar(naive_relative_loss)
|
| 451 |
+ self.lambda_bias * _to_scalar(bias_loss)
|
| 452 |
+ self.lambda_saturation * _to_scalar(saturation_loss)
|
|
|
|
|
|
|
| 453 |
+ self.lambda_directional * _to_scalar(directional_loss)
|
| 454 |
)
|
| 455 |
|
|
@@ -461,6 +550,8 @@ try:
|
|
| 461 |
naive_relative_loss,
|
| 462 |
bias_loss,
|
| 463 |
saturation_loss,
|
|
|
|
|
|
|
| 464 |
directional_loss,
|
| 465 |
total_loss,
|
| 466 |
)
|
|
@@ -506,11 +597,14 @@ def create_tft_model(
|
|
| 506 |
lambda_bias=cfg.weekly_loss.lambda_bias,
|
| 507 |
lambda_directional=cfg.weekly_loss.lambda_directional,
|
| 508 |
lambda_saturation=cfg.weekly_loss.lambda_saturation,
|
|
|
|
|
|
|
| 509 |
weekly_median_cap=cfg.weekly_loss.weekly_median_cap,
|
| 510 |
)
|
| 511 |
logger.info(
|
| 512 |
"Using weekly ASRO loss | weekly_q=%.2f t1_q=%.2f dispersion=%.2f "
|
| 513 |
"magnitude=%.2f naive=%.2f bias=%.2f dir=%.2f saturation=%.2f "
|
|
|
|
| 514 |
"median_cap=%s "
|
| 515 |
"monotonic_transform=true gap_scale=%.3f",
|
| 516 |
cfg.weekly_loss.lambda_weekly_quantile,
|
|
@@ -521,6 +615,8 @@ def create_tft_model(
|
|
| 521 |
cfg.weekly_loss.lambda_bias,
|
| 522 |
cfg.weekly_loss.lambda_directional,
|
| 523 |
cfg.weekly_loss.lambda_saturation,
|
|
|
|
|
|
|
| 524 |
(
|
| 525 |
f"{cfg.weekly_loss.weekly_median_cap:.6f}"
|
| 526 |
if cfg.weekly_loss.weekly_median_cap is not None
|
|
|
|
| 135 |
model_mae = torch.mean(torch.abs(pred_weekly_median - actual_weekly))
|
| 136 |
zero_mae = actual_abs_mean
|
| 137 |
naive_relative_loss = torch.relu((model_mae / zero_mae) - 1.0)
|
| 138 |
+
mean_gap = (pred_weekly_median.mean() - actual_weekly.mean()) / zero_mae
|
| 139 |
+
median_gap = (
|
| 140 |
+
(pred_weekly_median.median() - actual_weekly.median())
|
| 141 |
+
/ actual_abs_median
|
| 142 |
+
)
|
| 143 |
+
bias_loss = (
|
| 144 |
+
torch.abs(mean_gap)
|
| 145 |
+
+ 0.50 * torch.abs(median_gap)
|
| 146 |
+
+ 0.75 * torch.relu(mean_gap)
|
| 147 |
+
+ 0.50 * torch.relu(median_gap)
|
| 148 |
+
)
|
| 149 |
|
| 150 |
return {
|
| 151 |
"dispersion_loss": dispersion_loss,
|
|
|
|
| 157 |
}
|
| 158 |
|
| 159 |
|
| 160 |
+
def _weekly_positive_rate_loss(
|
| 161 |
+
pred_weekly_median: torch.Tensor,
|
| 162 |
+
actual_weekly: torch.Tensor,
|
| 163 |
+
temperature: float = 0.01,
|
| 164 |
+
eps: float = 1e-8,
|
| 165 |
+
) -> torch.Tensor:
|
| 166 |
+
"""Penalize batch-level weekly sign-balance drift with a smooth sign proxy."""
|
| 167 |
+
temp = max(float(temperature), eps)
|
| 168 |
+
pred_positive_rate = torch.sigmoid(pred_weekly_median / temp).mean()
|
| 169 |
+
actual_positive_rate = (actual_weekly > 0).float().mean()
|
| 170 |
+
return torch.abs(pred_positive_rate - actual_positive_rate)
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
def _weekly_interval_undercoverage_loss(
|
| 174 |
+
pred_weekly_quantiles: torch.Tensor,
|
| 175 |
+
actual_weekly: torch.Tensor,
|
| 176 |
+
quantiles: Sequence[float],
|
| 177 |
+
*,
|
| 178 |
+
target_width_ratio: float = 0.70,
|
| 179 |
+
eps: float = 1e-8,
|
| 180 |
+
) -> torch.Tensor:
|
| 181 |
+
"""Penalize weekly PI80 misses and intervals that are too narrow for actual scale."""
|
| 182 |
+
q = list(quantiles)
|
| 183 |
+
q10_idx = q.index(0.10) if 0.10 in q else 1
|
| 184 |
+
q90_idx = q.index(0.90) if 0.90 in q else len(q) - 2
|
| 185 |
+
lower = pred_weekly_quantiles[:, q10_idx]
|
| 186 |
+
upper = pred_weekly_quantiles[:, q90_idx]
|
| 187 |
+
width = torch.clamp(upper - lower, min=0.0)
|
| 188 |
+
|
| 189 |
+
actual_scale = actual_weekly.abs().mean().clamp_min(eps)
|
| 190 |
+
miss_loss = (
|
| 191 |
+
torch.relu(lower - actual_weekly)
|
| 192 |
+
+ torch.relu(actual_weekly - upper)
|
| 193 |
+
).mean() / actual_scale
|
| 194 |
+
|
| 195 |
+
actual_std = actual_weekly.std(unbiased=False).clamp_min(eps)
|
| 196 |
+
width_ratio = width.mean() / (2.56 * actual_std + eps)
|
| 197 |
+
under_width_loss = torch.relu(
|
| 198 |
+
torch.as_tensor(
|
| 199 |
+
float(target_width_ratio),
|
| 200 |
+
device=pred_weekly_quantiles.device,
|
| 201 |
+
dtype=pred_weekly_quantiles.dtype,
|
| 202 |
+
)
|
| 203 |
+
- width_ratio
|
| 204 |
+
).pow(2)
|
| 205 |
+
return miss_loss + under_width_loss
|
| 206 |
+
|
| 207 |
+
|
| 208 |
# ---------------------------------------------------------------------------
|
| 209 |
# Module-level ASRO loss class (must be at module level for pickle / checkpoint)
|
| 210 |
# ---------------------------------------------------------------------------
|
|
|
|
| 317 |
lambda_naive: float = 0.40,
|
| 318 |
lambda_bias: float = 0.25,
|
| 319 |
lambda_saturation: float = 0.25,
|
| 320 |
+
lambda_positive_rate: float = 0.20,
|
| 321 |
+
lambda_interval: float = 0.15,
|
| 322 |
weekly_median_cap: Optional[float] = None,
|
| 323 |
sharpe_eps: float = 1e-8,
|
| 324 |
debug_mode: bool = False,
|
|
|
|
| 332 |
self.lambda_naive = lambda_naive
|
| 333 |
self.lambda_bias = lambda_bias
|
| 334 |
self.lambda_saturation = lambda_saturation
|
| 335 |
+
self.lambda_positive_rate = lambda_positive_rate
|
| 336 |
+
self.lambda_interval = lambda_interval
|
| 337 |
self.weekly_median_cap = weekly_median_cap
|
| 338 |
self.sharpe_eps = sharpe_eps
|
| 339 |
self.debug_mode = debug_mode
|
|
|
|
| 349 |
"naive": 0.0,
|
| 350 |
"bias": 0.0,
|
| 351 |
"saturation": 0.0,
|
| 352 |
+
"positive_rate": 0.0,
|
| 353 |
+
"interval": 0.0,
|
| 354 |
"directional": 0.0,
|
| 355 |
"total": 0.0,
|
| 356 |
}
|
|
|
|
| 365 |
naive_relative_loss: torch.Tensor,
|
| 366 |
bias_loss: torch.Tensor,
|
| 367 |
saturation_loss: torch.Tensor,
|
| 368 |
+
positive_rate_loss: torch.Tensor,
|
| 369 |
+
interval_loss: torch.Tensor,
|
| 370 |
directional_loss: torch.Tensor,
|
| 371 |
total_loss: torch.Tensor,
|
| 372 |
) -> None:
|
|
|
|
| 377 |
self._component_sums["naive"] += float(naive_relative_loss.detach().mean().cpu())
|
| 378 |
self._component_sums["bias"] += float(bias_loss.detach().mean().cpu())
|
| 379 |
self._component_sums["saturation"] += float(saturation_loss.detach().mean().cpu())
|
| 380 |
+
self._component_sums["positive_rate"] += float(positive_rate_loss.detach().mean().cpu())
|
| 381 |
+
self._component_sums["interval"] += float(interval_loss.detach().mean().cpu())
|
| 382 |
self._component_sums["directional"] += float(directional_loss.detach().mean().cpu())
|
| 383 |
self._component_sums["total"] += float(total_loss.detach().mean().cpu())
|
| 384 |
self._component_batches += 1
|
|
|
|
| 395 |
"naive_loss_mean": 0.0,
|
| 396 |
"bias_loss_mean": 0.0,
|
| 397 |
"saturation_loss_mean": 0.0,
|
| 398 |
+
"positive_rate_loss_mean": 0.0,
|
| 399 |
+
"interval_loss_mean": 0.0,
|
| 400 |
"directional_loss_mean": 0.0,
|
| 401 |
"total_loss_mean": 0.0,
|
| 402 |
"dominant_component": None,
|
|
|
|
| 410 |
"naive": self._component_sums["naive"],
|
| 411 |
"bias": self._component_sums["bias"],
|
| 412 |
"saturation": self._component_sums["saturation"],
|
| 413 |
+
"positive_rate": self._component_sums["positive_rate"],
|
| 414 |
+
"interval": self._component_sums["interval"],
|
| 415 |
"directional": self._component_sums["directional"],
|
| 416 |
}
|
| 417 |
return {
|
|
|
|
| 423 |
"naive_loss_mean": self._component_sums["naive"] / n_batches,
|
| 424 |
"bias_loss_mean": self._component_sums["bias"] / n_batches,
|
| 425 |
"saturation_loss_mean": self._component_sums["saturation"] / n_batches,
|
| 426 |
+
"positive_rate_loss_mean": self._component_sums["positive_rate"] / n_batches,
|
| 427 |
+
"interval_loss_mean": self._component_sums["interval"] / n_batches,
|
| 428 |
"directional_loss_mean": self._component_sums["directional"] / n_batches,
|
| 429 |
"total_loss_mean": self._component_sums["total"] / n_batches,
|
| 430 |
"dominant_component": max(components, key=components.get),
|
|
|
|
| 494 |
magnitude_loss = scale_losses["magnitude_loss"]
|
| 495 |
naive_relative_loss = scale_losses["naive_relative_loss"]
|
| 496 |
bias_loss = scale_losses["bias_loss"]
|
| 497 |
+
positive_rate_loss = _weekly_positive_rate_loss(
|
| 498 |
+
pred_weekly_median,
|
| 499 |
+
actual_weekly,
|
| 500 |
+
eps=eps,
|
| 501 |
+
)
|
| 502 |
+
interval_loss = _weekly_interval_undercoverage_loss(
|
| 503 |
+
pred_weekly_quantiles,
|
| 504 |
+
actual_weekly,
|
| 505 |
+
self.quantiles,
|
| 506 |
+
eps=eps,
|
| 507 |
+
)
|
| 508 |
|
| 509 |
weekly_pred_direction = torch.tanh(pred_weekly_median * 10.0)
|
| 510 |
weekly_actual_direction = torch.sign(actual_weekly)
|
|
|
|
| 525 |
naive_relative_loss = _to_scalar(naive_relative_loss)
|
| 526 |
bias_loss = _to_scalar(bias_loss)
|
| 527 |
saturation_loss = _to_scalar(saturation_loss)
|
| 528 |
+
positive_rate_loss = _to_scalar(positive_rate_loss)
|
| 529 |
+
interval_loss = _to_scalar(interval_loss)
|
| 530 |
directional_loss = _to_scalar(directional_loss)
|
| 531 |
|
| 532 |
total_loss = (
|
|
|
|
| 537 |
+ self.lambda_naive * _to_scalar(naive_relative_loss)
|
| 538 |
+ self.lambda_bias * _to_scalar(bias_loss)
|
| 539 |
+ self.lambda_saturation * _to_scalar(saturation_loss)
|
| 540 |
+
+ self.lambda_positive_rate * _to_scalar(positive_rate_loss)
|
| 541 |
+
+ self.lambda_interval * _to_scalar(interval_loss)
|
| 542 |
+ self.lambda_directional * _to_scalar(directional_loss)
|
| 543 |
)
|
| 544 |
|
|
|
|
| 550 |
naive_relative_loss,
|
| 551 |
bias_loss,
|
| 552 |
saturation_loss,
|
| 553 |
+
positive_rate_loss,
|
| 554 |
+
interval_loss,
|
| 555 |
directional_loss,
|
| 556 |
total_loss,
|
| 557 |
)
|
|
|
|
| 597 |
lambda_bias=cfg.weekly_loss.lambda_bias,
|
| 598 |
lambda_directional=cfg.weekly_loss.lambda_directional,
|
| 599 |
lambda_saturation=cfg.weekly_loss.lambda_saturation,
|
| 600 |
+
lambda_positive_rate=cfg.weekly_loss.lambda_positive_rate,
|
| 601 |
+
lambda_interval=cfg.weekly_loss.lambda_interval,
|
| 602 |
weekly_median_cap=cfg.weekly_loss.weekly_median_cap,
|
| 603 |
)
|
| 604 |
logger.info(
|
| 605 |
"Using weekly ASRO loss | weekly_q=%.2f t1_q=%.2f dispersion=%.2f "
|
| 606 |
"magnitude=%.2f naive=%.2f bias=%.2f dir=%.2f saturation=%.2f "
|
| 607 |
+
"positive_rate=%.2f interval=%.2f "
|
| 608 |
"median_cap=%s "
|
| 609 |
"monotonic_transform=true gap_scale=%.3f",
|
| 610 |
cfg.weekly_loss.lambda_weekly_quantile,
|
|
|
|
| 615 |
cfg.weekly_loss.lambda_bias,
|
| 616 |
cfg.weekly_loss.lambda_directional,
|
| 617 |
cfg.weekly_loss.lambda_saturation,
|
| 618 |
+
cfg.weekly_loss.lambda_positive_rate,
|
| 619 |
+
cfg.weekly_loss.lambda_interval,
|
| 620 |
(
|
| 621 |
f"{cfg.weekly_loss.weekly_median_cap:.6f}"
|
| 622 |
if cfg.weekly_loss.weekly_median_cap is not None
|
deep_learning/training/hyperopt.py
CHANGED
|
@@ -71,8 +71,10 @@ KNOWN_GOOD_TRIAL_PARAMS = {
|
|
| 71 |
"lambda_dispersion": 0.35,
|
| 72 |
"lambda_magnitude": 0.55,
|
| 73 |
"lambda_naive": 0.40,
|
| 74 |
-
"lambda_bias": 0.
|
| 75 |
"lambda_directional": 0.06,
|
|
|
|
|
|
|
| 76 |
"batch_size": 32,
|
| 77 |
}
|
| 78 |
|
|
@@ -419,6 +421,8 @@ def create_trial_config(trial, base_cfg: TFTASROConfig) -> TFTASROConfig:
|
|
| 419 |
"lambda_directional",
|
| 420 |
[0.05, 0.06, 0.07],
|
| 421 |
),
|
|
|
|
|
|
|
| 422 |
)
|
| 423 |
|
| 424 |
training_cfg = TrainingConfig(
|
|
|
|
| 71 |
"lambda_dispersion": 0.35,
|
| 72 |
"lambda_magnitude": 0.55,
|
| 73 |
"lambda_naive": 0.40,
|
| 74 |
+
"lambda_bias": 0.19,
|
| 75 |
"lambda_directional": 0.06,
|
| 76 |
+
"lambda_positive_rate": 0.20,
|
| 77 |
+
"lambda_interval": 0.15,
|
| 78 |
"batch_size": 32,
|
| 79 |
}
|
| 80 |
|
|
|
|
| 421 |
"lambda_directional",
|
| 422 |
[0.05, 0.06, 0.07],
|
| 423 |
),
|
| 424 |
+
lambda_positive_rate=0.20,
|
| 425 |
+
lambda_interval=0.15,
|
| 426 |
)
|
| 427 |
|
| 428 |
training_cfg = TrainingConfig(
|
deep_learning/training/trainer.py
CHANGED
|
@@ -62,9 +62,11 @@ KNOWN_GOOD_CONFIG = {
|
|
| 62 |
"lambda_dispersion": 0.35,
|
| 63 |
"lambda_magnitude": 0.55,
|
| 64 |
"lambda_naive": 0.40,
|
| 65 |
-
"lambda_bias": 0.
|
| 66 |
"lambda_directional": 0.06,
|
| 67 |
"lambda_saturation": 0.25,
|
|
|
|
|
|
|
| 68 |
"batch_size": 32,
|
| 69 |
}
|
| 70 |
|
|
@@ -349,7 +351,8 @@ def train_tft_model(
|
|
| 349 |
)
|
| 350 |
logger.info(
|
| 351 |
"Weekly loss | weekly_q=%.2f t1_q=%.2f dispersion=%.2f "
|
| 352 |
-
"magnitude=%.2f naive=%.2f directional=%.2f saturation=%.2f "
|
|
|
|
| 353 |
"median_cap=%.6f "
|
| 354 |
"monotonic_transform=true",
|
| 355 |
cfg.weekly_loss.lambda_weekly_quantile,
|
|
@@ -357,8 +360,11 @@ def train_tft_model(
|
|
| 357 |
cfg.weekly_loss.lambda_dispersion,
|
| 358 |
cfg.weekly_loss.lambda_magnitude,
|
| 359 |
cfg.weekly_loss.lambda_naive,
|
|
|
|
| 360 |
cfg.weekly_loss.lambda_directional,
|
| 361 |
cfg.weekly_loss.lambda_saturation,
|
|
|
|
|
|
|
| 362 |
cfg.weekly_loss.weekly_median_cap or 0.0,
|
| 363 |
)
|
| 364 |
else:
|
|
@@ -523,6 +529,8 @@ def train_tft_model(
|
|
| 523 |
"lambda_bias": cfg.weekly_loss.lambda_bias,
|
| 524 |
"lambda_directional": cfg.weekly_loss.lambda_directional,
|
| 525 |
"lambda_saturation": cfg.weekly_loss.lambda_saturation,
|
|
|
|
|
|
|
| 526 |
"weekly_median_cap_abs_median_multiple": (
|
| 527 |
cfg.weekly_loss.weekly_median_cap_abs_median_multiple
|
| 528 |
),
|
|
@@ -614,7 +622,11 @@ def _write_conformal_calibration_artifact(
|
|
| 614 |
try:
|
| 615 |
import torch
|
| 616 |
|
| 617 |
-
from deep_learning.calibration.conformal import
|
|
|
|
|
|
|
|
|
|
|
|
|
| 618 |
from deep_learning.training.metrics import (
|
| 619 |
apply_weekly_median_cap_np,
|
| 620 |
cumulative_horizon,
|
|
@@ -655,26 +667,58 @@ def _write_conformal_calibration_artifact(
|
|
| 655 |
q90_idx = q.index(0.90)
|
| 656 |
raw_lower = weekly_quantiles[:, q10_idx]
|
| 657 |
raw_upper = weekly_quantiles[:, q90_idx]
|
| 658 |
-
validation_pi80_coverage =
|
| 659 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 660 |
)
|
| 661 |
|
| 662 |
calibration_status = "fit"
|
| 663 |
if validation_pi80_coverage >= 0.90:
|
| 664 |
-
|
| 665 |
calibration_status = "skipped_interval_already_overcovered"
|
| 666 |
else:
|
| 667 |
-
|
| 668 |
weekly_actual,
|
| 669 |
raw_lower,
|
| 670 |
raw_upper,
|
| 671 |
alpha=0.20,
|
| 672 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 673 |
artifact = {
|
| 674 |
"generated_at": datetime.now(timezone.utc).isoformat(),
|
| 675 |
"target": "weekly_5d_log_return",
|
| 676 |
"alpha": 0.20,
|
| 677 |
"global_adjustment": float(global_adj),
|
|
|
|
|
|
|
| 678 |
"bucket_adjustments": {
|
| 679 |
"neutral": float(global_adj),
|
| 680 |
"risk_on": float(global_adj),
|
|
@@ -687,6 +731,12 @@ def _write_conformal_calibration_artifact(
|
|
| 687 |
"fit_split": "validation",
|
| 688 |
"test_split_used_for_fit": False,
|
| 689 |
"validation_pi80_coverage": validation_pi80_coverage,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 690 |
"calibration_status": calibration_status,
|
| 691 |
}
|
| 692 |
|
|
@@ -819,7 +869,8 @@ def _overlay_training_config(cfg: TFTASROConfig, params: dict) -> TFTASROConfig:
|
|
| 819 |
k: params[k] for k in (
|
| 820 |
"lambda_weekly_quantile", "lambda_t1_quantile", "lambda_directional",
|
| 821 |
"lambda_dispersion", "lambda_magnitude", "lambda_naive", "lambda_bias",
|
| 822 |
-
"lambda_saturation", "
|
|
|
|
| 823 |
"weekly_median_cap_mean_abs_multiple", "weekly_median_cap_std_multiple",
|
| 824 |
) if k in params
|
| 825 |
}
|
|
|
|
| 62 |
"lambda_dispersion": 0.35,
|
| 63 |
"lambda_magnitude": 0.55,
|
| 64 |
"lambda_naive": 0.40,
|
| 65 |
+
"lambda_bias": 0.19,
|
| 66 |
"lambda_directional": 0.06,
|
| 67 |
"lambda_saturation": 0.25,
|
| 68 |
+
"lambda_positive_rate": 0.20,
|
| 69 |
+
"lambda_interval": 0.15,
|
| 70 |
"batch_size": 32,
|
| 71 |
}
|
| 72 |
|
|
|
|
| 351 |
)
|
| 352 |
logger.info(
|
| 353 |
"Weekly loss | weekly_q=%.2f t1_q=%.2f dispersion=%.2f "
|
| 354 |
+
"magnitude=%.2f naive=%.2f bias=%.2f directional=%.2f saturation=%.2f "
|
| 355 |
+
"positive_rate=%.2f interval=%.2f "
|
| 356 |
"median_cap=%.6f "
|
| 357 |
"monotonic_transform=true",
|
| 358 |
cfg.weekly_loss.lambda_weekly_quantile,
|
|
|
|
| 360 |
cfg.weekly_loss.lambda_dispersion,
|
| 361 |
cfg.weekly_loss.lambda_magnitude,
|
| 362 |
cfg.weekly_loss.lambda_naive,
|
| 363 |
+
cfg.weekly_loss.lambda_bias,
|
| 364 |
cfg.weekly_loss.lambda_directional,
|
| 365 |
cfg.weekly_loss.lambda_saturation,
|
| 366 |
+
cfg.weekly_loss.lambda_positive_rate,
|
| 367 |
+
cfg.weekly_loss.lambda_interval,
|
| 368 |
cfg.weekly_loss.weekly_median_cap or 0.0,
|
| 369 |
)
|
| 370 |
else:
|
|
|
|
| 529 |
"lambda_bias": cfg.weekly_loss.lambda_bias,
|
| 530 |
"lambda_directional": cfg.weekly_loss.lambda_directional,
|
| 531 |
"lambda_saturation": cfg.weekly_loss.lambda_saturation,
|
| 532 |
+
"lambda_positive_rate": cfg.weekly_loss.lambda_positive_rate,
|
| 533 |
+
"lambda_interval": cfg.weekly_loss.lambda_interval,
|
| 534 |
"weekly_median_cap_abs_median_multiple": (
|
| 535 |
cfg.weekly_loss.weekly_median_cap_abs_median_multiple
|
| 536 |
),
|
|
|
|
| 622 |
try:
|
| 623 |
import torch
|
| 624 |
|
| 625 |
+
from deep_learning.calibration.conformal import (
|
| 626 |
+
apply_conformal_interval,
|
| 627 |
+
interval_coverage,
|
| 628 |
+
rolling_conformal_adjustment,
|
| 629 |
+
)
|
| 630 |
from deep_learning.training.metrics import (
|
| 631 |
apply_weekly_median_cap_np,
|
| 632 |
cumulative_horizon,
|
|
|
|
| 667 |
q90_idx = q.index(0.90)
|
| 668 |
raw_lower = weekly_quantiles[:, q10_idx]
|
| 669 |
raw_upper = weekly_quantiles[:, q90_idx]
|
| 670 |
+
validation_pi80_coverage = interval_coverage(weekly_actual, raw_lower, raw_upper)
|
| 671 |
+
validation_pi80_width = float(np.mean(raw_upper - raw_lower))
|
| 672 |
+
validation_weekly_std = float(np.std(weekly_actual))
|
| 673 |
+
target_min_pi80_width_ratio = 0.70
|
| 674 |
+
validation_pi80_width_ratio = (
|
| 675 |
+
validation_pi80_width / (2.56 * validation_weekly_std + 1e-8)
|
| 676 |
+
if validation_weekly_std > 1e-12
|
| 677 |
+
else 0.0
|
| 678 |
+
)
|
| 679 |
+
target_min_pi80_width = target_min_pi80_width_ratio * 2.56 * validation_weekly_std
|
| 680 |
+
width_floor_adjustment = max(
|
| 681 |
+
0.0,
|
| 682 |
+
(target_min_pi80_width - validation_pi80_width) / 2.0,
|
| 683 |
)
|
| 684 |
|
| 685 |
calibration_status = "fit"
|
| 686 |
if validation_pi80_coverage >= 0.90:
|
| 687 |
+
conformal_adj = 0.0
|
| 688 |
calibration_status = "skipped_interval_already_overcovered"
|
| 689 |
else:
|
| 690 |
+
conformal_adj = rolling_conformal_adjustment(
|
| 691 |
weekly_actual,
|
| 692 |
raw_lower,
|
| 693 |
raw_upper,
|
| 694 |
alpha=0.20,
|
| 695 |
)
|
| 696 |
+
global_adj = max(float(conformal_adj), float(width_floor_adjustment))
|
| 697 |
+
if width_floor_adjustment > conformal_adj and width_floor_adjustment > 0.0:
|
| 698 |
+
calibration_status = "fit_width_floor"
|
| 699 |
+
calibrated_lower, calibrated_upper = apply_conformal_interval(
|
| 700 |
+
raw_lower,
|
| 701 |
+
raw_upper,
|
| 702 |
+
global_adj,
|
| 703 |
+
)
|
| 704 |
+
calibrated_validation_pi80_coverage = interval_coverage(
|
| 705 |
+
weekly_actual,
|
| 706 |
+
calibrated_lower,
|
| 707 |
+
calibrated_upper,
|
| 708 |
+
)
|
| 709 |
+
calibrated_validation_pi80_width = float(np.mean(calibrated_upper - calibrated_lower))
|
| 710 |
+
calibrated_validation_pi80_width_ratio = (
|
| 711 |
+
calibrated_validation_pi80_width / (2.56 * validation_weekly_std + 1e-8)
|
| 712 |
+
if validation_weekly_std > 1e-12
|
| 713 |
+
else 0.0
|
| 714 |
+
)
|
| 715 |
artifact = {
|
| 716 |
"generated_at": datetime.now(timezone.utc).isoformat(),
|
| 717 |
"target": "weekly_5d_log_return",
|
| 718 |
"alpha": 0.20,
|
| 719 |
"global_adjustment": float(global_adj),
|
| 720 |
+
"base_conformal_adjustment": float(conformal_adj),
|
| 721 |
+
"width_floor_adjustment": float(width_floor_adjustment),
|
| 722 |
"bucket_adjustments": {
|
| 723 |
"neutral": float(global_adj),
|
| 724 |
"risk_on": float(global_adj),
|
|
|
|
| 731 |
"fit_split": "validation",
|
| 732 |
"test_split_used_for_fit": False,
|
| 733 |
"validation_pi80_coverage": validation_pi80_coverage,
|
| 734 |
+
"calibrated_validation_pi80_coverage": calibrated_validation_pi80_coverage,
|
| 735 |
+
"validation_pi80_width": validation_pi80_width,
|
| 736 |
+
"calibrated_validation_pi80_width": calibrated_validation_pi80_width,
|
| 737 |
+
"validation_pi80_width_ratio": validation_pi80_width_ratio,
|
| 738 |
+
"calibrated_validation_pi80_width_ratio": calibrated_validation_pi80_width_ratio,
|
| 739 |
+
"target_min_pi80_width_ratio": target_min_pi80_width_ratio,
|
| 740 |
"calibration_status": calibration_status,
|
| 741 |
}
|
| 742 |
|
|
|
|
| 869 |
k: params[k] for k in (
|
| 870 |
"lambda_weekly_quantile", "lambda_t1_quantile", "lambda_directional",
|
| 871 |
"lambda_dispersion", "lambda_magnitude", "lambda_naive", "lambda_bias",
|
| 872 |
+
"lambda_saturation", "lambda_positive_rate", "lambda_interval",
|
| 873 |
+
"weekly_median_cap_abs_median_multiple",
|
| 874 |
"weekly_median_cap_mean_abs_multiple", "weekly_median_cap_std_multiple",
|
| 875 |
) if k in params
|
| 876 |
}
|