Forecasting demo walkthrough 🚀
f“Goal: build a compelling demo using only composable pieces. As context evolved, new micro-tools appeared.”
What we’ll try
We’ll create synthetic time series with:
- Flat baseline (no noise)
- Linear growth (no noise)
- Flat/growth with occasional positive spikes (≤10% and ≤20%)
For each, we’ll produce forecasts for the next month, quarter, and year, then plot actuals vs predictions
Algorithms in plain language
Here’s what each method tries to do, in simple terms:
- SMA (Simple Moving Average): Average of the last N points. Very stable, slow to react.
- ES (Exponential Smoothing): Weighted average that favors recent data. Reacts faster than SMA.
- Holt‑Winters (Triple Exponential Smoothing): Tracks level and trend (and seasonality if present). Good when there’s a slope and repeating patterns.
- ARIMA: Statistical model for short‑term autocorrelation. Can capture persistence and mean‑reversion without seasonality.
- SARIMA: ARIMA with seasonality. Useful if there’s a repeating weekly/monthly pattern.
- Theta: Blend of level and trend lines. Often a strong baseline on simple series.
- Prophet: Piecewise linear trend with changepoints plus seasonality priors. Adaptable when trends change.
- NeuralProphet: Neural network version of Prophet—can fit flexible trends/seasonality; needs enough variation.
- Ensemble: Average of available models to reduce variance and over‑/under‑reaction.
Mini-map: how context shaped the tools
- Baseline – Needed clean daily data →
generate_series.py - Spikes appear – Added
add_spikes.pyto simulate noisy launches - Seasonality shows up – Built
add_seasonality.py - Forecast tuning – Tweaked parameters for specific business patterns
Each step uses the same pipe-first workflow so you can remix or extend it.
0) Setup (one time per shell)
source .venv/bin/activate
pip install -r requirements.txt
mkdir -p demo/input demo/out
1) Flat (no noise) — one‑year of daily data
First, a perfectly flat year with no noise.
python demo/generate_series.py \
--pattern flat --granularity daily --periods 365 \
--baseline 10 --noise 0.0 \
--out demo/input/daily_flat.csv
Flat
Now I run the forecaster and include an ensemble column which averages available models.
python forecast.py \
--input demo/input/daily_flat.csv \
--date-column PeriodStart --value-column Cost \
--ensemble > demo/out/daily_flat_forecasts.csv
The output CSV contains the original values and one column per forecasting method.
Forecasted data
Good news, every forecasting method manage flat well.
Growing a little each day
Second, a predictable linear growth pattern—half a unit per day.
python demo/generate_series.py \
--pattern upward_trend --granularity daily --periods 365 \
--baseline 10 --trend 0.5 --noise 0.0 \
--out demo/input/daily_growth.csv
The trend increases the baseline steadily; no noise keeps it clean for comparison.
Growing slowly
Now I run the forecaster and include an ensemble column which averages available models.
python forecast.py \
--input demo/input/daily_growth.csv \
--date-column PeriodStart --value-column Cost \
--ensemble > demo/out/daily_growth_forecasts.csv
The output CSV contains the original values and one column per forecasting method.
Forecasted data
3) Flat with spikes (≤10% daily)
Why it matters: Shows how small, infrequent spikes nudge forecasts and why ensemble smoothing helps avoid overreacting.
Third, I introduce occasional positive spikes up to ten percent on a flat baseline.
python demo/generate_series.py \
--pattern flat --granularity daily --periods 365 \
--baseline 10 --noise 0.0 \
--out demo/input/daily_flat_10_base.csv
python demo/add_spikes.py \
--input demo/input/daily_flat_10_base.csv \
--output demo/input/daily_flat_spikes_10.csv \
--max-pct 0.10 --prob 0.05
This applies bounded spikes (max ten percent) with a small daily probability, using a fixed seed for reproducibility.
Growing slowly
I'll run forecasts and show how each model handles transient spikes over the three horizons.
python forecast.py \
--input demo/input/daily_flat_spikes_10.csv \
--date-column PeriodStart --value-column Cost \
--ensemble > demo/out/daily_flat_spikes_10_forecasts.csv
Forecasted data
4) Flat with larger spikes (≤33% daily)
Why it matters: Bigger spikes with higher probability stress models—useful for promo-heavy or launch-driven workloads.
Larger spikes up to 33 percent on a flat baseline, stressing the models more.
python demo/generate_series.py \
--pattern flat --granularity daily --periods 365 \
--baseline 10 --noise 0.0 \
--out demo/input/daily_flat_33_base.csv
python demo/add_spikes.py \
--input demo/input/daily_flat_33_base.csv \
--output demo/input/daily_flat_spikes_33.csv \
--max-pct 0.33 --prob 0.15
This applies bounded spikes (max 33 percent) with higher daily probability, using a fixed seed for reproducibility.
Flat with larger spikes
I'll run forecasts and show how each model handles transient spikes over the three horizons.
python forecast.py \
--input demo/input/daily_flat_spikes_33.csv \
--date-column PeriodStart --value-column Cost \
--ensemble > demo/out/daily_flat_spikes_33_forecasts.csv
Forecasted data
5) Growth with spikes (≤33% daily) 15% change of happening
Why it matters: Real FinOps pipelines often mix trend + events; this section shows interplay of growth and burstiness.
Finally, growth with larger spikes up to twenty percent, stressing the models.
python demo/generate_series.py \
--pattern upward_trend --granularity daily --periods 365 \
--baseline 100 --trend 0.5 --noise 0.0 \
--out demo/input/daily_growth_33_base.csv
python demo/add_spikes.py \
--input demo/input/daily_growth_33_base.csv \
--output demo/input/daily_growth_spikes_33.csv \
--max-pct 0.33 --prob 0.15
Growing slowly
I'll run forecasts and show how each model handles transient spikes over the three horizons.
python forecast.py \
--input demo/input/daily_growth_spikes_33.csv \
--date-column PeriodStart --value-column Cost \
--ensemble > demo/out/daily_growth_spikes_33_forecasts.csv
Forecasted data
6) Daily seasonality — flat baseline (toys and holidays)
Why it matters: Retail-style cyclicality; illustrates how external adapters layer in context before forecasting.
Flat baseline (no noise) with two seasonal profiles applied externally, then forecasted.
python demo/generate_series.py --pattern flat --granularity daily --periods 365 --baseline 100 --noise 0.0 --out demo/input/daily_flat_base.csv && python demo/add_seasonality.py --input demo/input/daily_flat_base.csv --output demo/input/daily_flat_toys.csv --preset toys && python forecast.py --input demo/input/daily_flat_toys.csv --date-column PeriodStart --value-column Cost --ensemble > demo/out/daily_flat_toys_forecasts.csv && python demo/add_seasonality.py --input demo/input/daily_flat_base.csv --output demo/input/daily_flat_holidays.csv --preset holidays && python forecast.py --input demo/input/daily_flat_holidays.csv --date-column PeriodStart --value-column Cost --ensemble > demo/out/daily_flat_holidays_forecasts.csv
Flat + Toys seasonality
Forecasted data (Toys)
Flat + Holidays seasonality
Forecasted data (Holidays)
7) Daily seasonality — growth baseline (toys and holidays)
Why it matters: Growth + seasonality is a classic FinOps forecasting headache; compare outputs before tuning.
Upward trend baseline with the same two seasonal profiles.
python demo/generate_series.py --pattern upward_trend --granularity daily --periods 365 --baseline 100 --trend 0.5 --noise 0.0 --out demo/input/daily_growth_base.csv && python demo/add_seasonality.py --input demo/input/daily_growth_base.csv --output demo/input/daily_growth_toys.csv --preset toys && python forecast.py --input demo/input/daily_growth_toys.csv --date-column PeriodStart --value-column Cost --ensemble > demo/out/daily_growth_toys_forecasts.csv && python demo/add_seasonality.py --input demo/input/daily_growth_base.csv --output demo/input/daily_growth_holidays.csv --preset holidays && python forecast.py --input demo/input/daily_growth_holidays.csv --date-column PeriodStart --value-column Cost --ensemble > demo/out/daily_growth_holidays_forecasts.csv
Growth + Toys seasonality
Forecasted data (Toys)
Growth + Holidays seasonality
Forecasted data (Holidays)
Optional: tuning when seasonality is known
If you know your industry has yearly seasonality (e.g., toys or holidays), the defaults already work reasonably well for daily data. Prophet has yearly seasonality enabled by default and tends to capture these patterns without extra tuning. If you want gentle tweaks:
- Increase Prophet seasonality strength a bit
- Keep weekly seasonality off (unless you truly have weekly patterns)
- Leave Holt‑Winters as‑is for daily yearly seasonality unless you have ≥2 years of data (it needs at least 2×seasonal_periods)
# Daily data with yearly seasonality: nudge Prophet priors and keep ensemble
python forecast.py \
--input demo/input/daily_flat_toys.csv \
--date-column PeriodStart --value-column Cost \
--prophet-yearly-seasonality true \
--prophet-weekly-seasonality false \
--prophet-changepoint-prior-scale 0.05 \
--prophet-seasonality-prior-scale 12.0 \
--ensemble > demo/out/daily_flat_toys_forecasts_tuned.csv
Notes:
- Holt‑Winters on daily yearly patterns requires
--hw-seasonal-periods 365and ≥730 days of history; otherwise it falls back to simple ES (current demo has 365 days, so fallback is expected). - If your data also has a weekly cycle, try setting
--prophet-weekly-seasonality trueand consider--sarima-seasonal-order 1,0,1,7(requires statsmodels), but only if you truly expect weekly effects.
A couple of alternative tuning examples
# Slightly more responsive moving average and ES
python forecast.py \
--input demo/input/daily_growth_holidays.csv \
--date-column PeriodStart --value-column Cost \
--sma-window 14 \
--es-alpha 0.7 \
--ensemble > demo/out/daily_growth_holidays_forecasts_tuned_baselines.csv
# Add weekly effects (only if real weekly pattern exists) and SARIMA weekly seasonality
python forecast.py \
--input demo/input/daily_growth_holidays.csv \
--date-column PeriodStart --value-column Cost \
--prophet-weekly-seasonality true \
--sarima-order 1,1,1 \
--sarima-seasonal-order 1,0,1,7 \
--ensemble > demo/out/daily_growth_holidays_forecasts_with_weekly.csv
8) Tuned seasonal forecasts (examples)
Why it matters: Demonstrates parameter tuning (Prophet priors, SARIMA weekly) once you know the business rhythm.
Two tuned runs to illustrate parameter effects when yearly seasonality is known.
# Flat + toys seasonality (slightly stronger seasonality prior)
python forecast.py \
--input demo/input/daily_flat_toys.csv \
--date-column PeriodStart --value-column Cost \
--prophet-yearly-seasonality true \
--prophet-weekly-seasonality false \
--prophet-changepoint-prior-scale 0.05 \
--prophet-seasonality-prior-scale 12.0 \
--ensemble > demo/out/daily_flat_toys_forecasts_tuned.csv
# Growth + holidays seasonality (enable weekly; add SARIMA weekly component)
python forecast.py \
--input demo/input/daily_growth_holidays.csv \
--date-column PeriodStart --value-column Cost \
--prophet-weekly-seasonality true \
--sarima-order 1,1,1 \
--sarima-seasonal-order 1,0,1,7 \
--ensemble > demo/out/daily_growth_holidays_forecasts_tuned.csv