6.5 KiB
Theil's U: Theil's U Statistic
The forecast that matters is the one that beats a naive guess.
| Property | Value |
|---|---|
| Category | Error Metric |
| Inputs | Actual vs Predicted (dual input) |
| Parameters | period |
| Outputs | Single series (TheilU) |
| Output range | \geq 0 |
| Warmup | period bars |
| PineScript | theilu.pine |
- Theil's U Statistic measures forecast accuracy relative to a naive no-change forecast.
- Similar: MASE, Rsquared | Trading note: Theil's U statistic; <1 = forecast beats naïve, >1 = worse than naïve random walk.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
Theil's U Statistic measures forecast accuracy relative to a naive no-change forecast. A value below 1 indicates the model outperforms simply predicting that tomorrow equals today; above 1 means you'd be better off not forecasting at all.
Historical Context
Developed by Dutch econometrician Henri Theil in the 1960s, Theil's U was designed to evaluate economic forecasts against the simplest possible benchmark: the assumption of no change. This was revolutionary because many sophisticated models fail to beat this naive approach, especially in financial markets.
Architecture & Physics
Theil's U computes two parallel error metrics: one for the forecast and one for a naive prediction. The ratio reveals whether the forecasting effort adds value. A forecast might have low absolute error but still be worse than doing nothing.
Properties
- Relative benchmark: Compares against naive no-change forecast
- Scale-independent: Ratio is unitless
- Interpretable threshold: U = 1 is the break-even point
- Range: 0 to ∞, with 0 being perfect and > 1 being worse than naive
Mathematical Foundation
1. Forecast Error
Calculate squared errors for the actual forecast:
FPE = \sum_{i=1}^{n} (y_i - \hat{y}_i)^2
Where:
y_i= actual value at time i\hat{y}_i= predicted value at time i
2. Naive Error
Calculate squared errors for naive prediction (previous actual):
NPE = \sum_{i=1}^{n} (y_i - y_{i-1})^2
3. Theil's U Calculation
Take the ratio of forecast to naive:
U = \sqrt{\frac{FPE}{NPE}} = \sqrt{\frac{\sum_{i=1}^{n} (y_i - \hat{y}_i)^2}{\sum_{i=1}^{n} (y_i - y_{i-1})^2}}
4. Running Update (O(1))
QuanTAlib maintains running sums of both squared error terms:
S_{f,new} = S_{f,old} - e_{f,oldest}^2 + e_{f,newest}^2
S_{n,new} = S_{n,old} - e_{n,oldest}^2 + e_{n,newest}^2
U = \sqrt{\frac{S_{f,new}}{S_{n,new}}}
Implementation Details
Usage Patterns
// Streaming mode - update with each new observation
var theilU = new TheilU(period: 20);
var result = theilU.Update(actualValue, predictedValue);
// Batch mode - calculate for entire series
var results = TheilU.Calculate(actualSeries, predictedSeries, period: 20);
// Span mode - zero-allocation for high performance
TheilU.Batch(actualSpan, predictedSpan, outputSpan, period: 20);
Parameters
| Parameter | Type | Description |
|---|---|---|
| period | int | Lookback window for calculation (must be > 0) |
Properties
| Property | Type | Description |
|---|---|---|
| Last | TValue | Most recent Theil's U value |
| IsHot | bool | True when buffer is full |
| Name | string | Indicator name (e.g., "TheilU(20)") |
| WarmupPeriod | int | Number of periods before valid output |
Performance Profile
Operation Count (Streaming Mode)
Theil's U statistic: U = sqrt(MSE_forecast) / sqrt(MSE_naive). Requires two running mean-squared-error accumulators.
| Operation | Count | Cost (cycles) | Subtotal |
|---|---|---|---|
| Forecast MSE update (e^2 + EMA) | 2 | ~5 cy | ~10 cy |
| Naive MSE update (naive_e^2 + EMA) | 2 | ~5 cy | ~10 cy |
| U = sqrt(MSE_f) / sqrt(MSE_n) | 2 | ~15 cy | ~30 cy |
| Total | ~6 | — | ~50 cycles |
O(1) per bar. Two parallel EMA accumulators + ratio with sqrt. ~50 cycles/bar.
Batch Mode (SIMD Analysis)
| Operation | Vectorizable? | Notes |
|---|---|---|
| Squared error accumulation | Yes | Element-wise squares + reduction |
| sqrt ratio | No | Single scalar at end |
Batch MSE accumulation vectorizable; final ratio is scalar. ~8 cy/bar for squared-error accumulation.
| Metric | Score | Notes |
|---|---|---|
| Throughput | ~15 ns/bar | O(1) update complexity |
| Allocations | 0 | Uses pre-allocated ring buffers |
| Complexity | O(1) | Constant time per update |
| Accuracy | 10/10 | Exact calculation |
| Timeliness | 9/10 | No lag beyond the period |
| Interpretability | 10/10 | Clear benchmark comparison |
Interpretation
| Theil's U | Interpretation |
|---|---|
| 0 | Perfect prediction |
| < 0.5 | Excellent (error < 50% of naive) |
| 0.5 - 0.8 | Good forecasting skill |
| 0.8 - 1.0 | Marginal improvement over naive |
| = 1.0 | Equal to naive forecast |
| > 1.0 | Worse than naive (model adds noise) |
Why Use Theil's U?
| Scenario | Low MAE but High U | High MAE but Low U |
|---|---|---|
| Meaning | Series is easy to predict | Model adds value despite errors |
| Example | Stable prices, any model works | Volatile prices, model captures moves |
| Recommendation | Use simpler model | Keep using the model |
Common Use Cases
- Economic Forecasting: Evaluate macro predictions against random walk
- Financial Markets: Test trading signals against buy-and-hold
- Model Selection: Choose models that beat naive benchmarks
- Forecast Validation: Ensure forecasting effort is worthwhile
Edge Cases
- Zero Naive Error: Returns infinity when series is perfectly flat (naive is perfect)
- NaN Handling: Uses last valid value substitution
- Single Input: Not supported (requires two series)
- Period = 1: Returns 0 (insufficient data for naive comparison)
- First Value: Needs at least 2 values for naive benchmark