Skip to content

Commit 67a1afb

Browse files
committed
fix(monitors): prevent overconfident prediction bounds
1 parent 5118316 commit 67a1afb

1 file changed

Lines changed: 16 additions & 1 deletion

File tree

testgen/common/time_series_service.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from datetime import datetime
33

44
import holidays
5+
import numpy as np
56
import pandas as pd
67
from statsmodels.tsa.statespace.sarimax import SARIMAX
78

@@ -94,7 +95,21 @@ def get_exog_flags(index: pd.DatetimeIndex) -> pd.DataFrame:
9495

9596
results = pd.DataFrame(index=forecast_index)
9697
results["mean"] = forecast.predicted_mean
97-
results["se"] = forecast.var_pred_mean ** 0.5
98+
99+
# SE estimation: take the max of three sources to prevent overconfident bounds.
100+
# 1. Model SE (var_pred_mean): can be artificially small when AR/MA nearly cancel
101+
# 2. Residual SE: the model's actual 1-step prediction errors (after Kalman burn-in)
102+
# 3. Raw diff SE: std of first-differences of the original data — captures inherent
103+
# point-to-point variability that the model may underestimate
104+
model_se = forecast.var_pred_mean ** 0.5
105+
order_sum = model.k_ar + model.k_diff + model.k_ma
106+
burn_in = max(order_sum, 3)
107+
usable_residuals = fitted_model.resid.iloc[burn_in:]
108+
resid_se = usable_residuals.std() if len(usable_residuals) >= 5 else 0.0
109+
raw_diffs = np.diff(history.iloc[:, 0].values)
110+
raw_diff_se = np.std(raw_diffs, ddof=1) if len(raw_diffs) > 1 else 0.0
111+
results["se"] = np.maximum(model_se, max(resid_se, raw_diff_se))
112+
98113
return results
99114

100115

0 commit comments

Comments
 (0)