← back to projects

2026 · Live

Axis.

Portfolio analytics that tests its own methods — and reports when they don't beat the simple baseline.

Axis preview
RShinyPortfolio OptimizationBacktestingARIMATime SeriesGitHub Actions

Axis lets you build a weighted portfolio, benchmark it, and read its risk the way a careful investor would. It pulls adjusted prices from Yahoo Finance, then reports concentration and risk contribution, drawdowns, holding and sector attribution, and plain-language insights tagged with how much evidence backs them. Stress replays historical episodes like the COVID crash and the 2022 sell-off, and draws a bootstrap fan of forward paths. Allocation compares your weights with long-only model portfolios solved with quadratic programming: min variance on sample or Ledoit-Wolf shrunk covariance, inverse volatility, equal weight, and max Sharpe. The other half of the project is checking whether any of that holds up. A walk-forward backtest re-optimizes every month from the trailing year only, charges 10 bps per unit of turnover, and puts block-bootstrap confidence intervals on every Sharpe ratio, with all settings fixed before any results were computed. A rolling-origin test puts the app's ARIMA forecasts up against a random walk. Forty unit tests check the math against hand-computed values. The look-ahead tests change every price after a rebalance or forecast date and confirm that nothing chosen before that date moves. GitHub Actions blocks the deploy unless all of them pass.

Evaluation results

Worst drawdown, equal weight
−37.9%
Worst drawdown, min variance
−20.2%
Optimizers beating equal weight on Sharpe
0
ARIMA vs random walk, wins / losses
0 / 16
95% interval coverage, volatile weeks
87–93%
auto.arima chose a random walk
45%

Walk-forward backtests on asset-class and sector ETF universes, April 2007 to October 2026, with 10 bps costs and a 35% weight cap; drawdowns are from the asset-class universe. Forecasts were tested weekly from January 2015 on 47 tickers at 1-, 5- and 21-day horizons, with Diebold-Mariano tests corrected by Benjamini-Hochberg.

What I learned

The optimizers didn't beat equal weight

On both ETF universes, every 95% bootstrap interval for the Sharpe gap between an optimizer and equal weight included zero. Max Sharpe had the best asset-class Sharpe at 0.75, but it needed 176% turnover a year. On sector ETFs it fell from 0.73 when fitted once with hindsight to 0.57 when re-estimated walk-forward, about the same as equal weight. Where min variance did earn its place was risk: a worst drawdown of −20.2% against −37.9%. That changed how the app frames allocation. A model portfolio is now a trade-off between risk and return, shown next to its own track record, and no longer a promise of better returns.

Testing the forecasts instead of trusting them

The original version of this project compared two ARIMA configurations on RMSE, MAE and MAPE, but never against a naive baseline. Run against a random walk on 47 tickers, ARIMA won none of 141 ticker-horizon tests after correction and lost 16. When auto.arima re-selected its model order, it picked a plain random walk 45% of the time. Its 95% intervals held the actual price about 93–94% of the time overall, but only 87–93% in the most volatile third of weeks, exactly when you'd lean on them. The Explore page now shows each forecast alongside that track record.

Axis is educational tooling, not investment advice. Backtests on the app's sample portfolios carry survivorship and hindsight bias, which is why the results above come from fixed ETF universes chosen in advance.