Tactical Portfolio Intelligence
Tactical Portfolio Intelligence is an end-to-end portfolio management system built on a daily pipeline: data ingestion from market and macro sources (57 features including 24 FRED indicators), ensemble forecasting via five diverse models — CatBoost, LightGBM, Random Forest, Ridge, and N-BEATS — weighted by rolling Information Coefficient, and a two-stage allocation engine that blends strategic Black-Litterman views with IC-gated tactical overlays across 1-day, 5-day, and 20-day horizons.
The risk management layer scores market conditions across six signals (VIX, credit spreads, drawdown, breadth, yield curve, sentiment), classifies regimes from bull to crisis, and enforces graduated constraints that scale with market stress. A hard drawdown circuit breaker with hysteresis automatically shifts to defensive positioning during severe declines, while conformal prediction intervals provide honest uncertainty bounds on all forecasts.
The system runs as eleven Cloud Run services on GCP with Firestore persistence, processing a 14-ETF universe that includes equity, bond, gold, and hedge positions. A dynamic universe selector can activate tactical sector ETFs based on regime probabilities. Everything is served through a real-time Next.js dashboard with portfolio analytics, signal history, and model intelligence views.
Part II: The Reckoning
Part I described the machine: a five-model forecasting ensemble weighted by Information Coefficient, a two-stage allocation engine pairing Black-Litterman strategic views with a tactical overlay, a six-signal risk layer, conformal prediction intervals, eleven Cloud Run services, a Next.js dashboard. It read like a spec sheet because, for a long time, that was the point — getting the system to exist, end to end, every day, without a human in the loop.
This part is about the only question a spec sheet never answers: did it actually work?
The honest version of that answer took months longer to reach than the system took to build, and it is not the answer I wanted. I am writing it down anyway, because the most useful thing this project produced was not a portfolio strategy. It was a disproof — and the discipline to ship the truth instead of the story.
The unglamorous middle
Before any verdict, the system had to earn the right to be judged. A backtest of a flaky pipeline tells you nothing; you cannot distinguish a bad strategy from a bad data feed. So a large fraction of the work was the kind that never appears in a demo.
The market-data layer had a silent type-drift bug: some documents stored dates as strings, others as timestamps, and range queries quietly truncated themselves at the boundary. That one defect had been corrupting backfills for weeks before it surfaced. Fixing it meant migrating roughly 1,800 documents in production and proving, document by document, that zero remained wrong.
Then there was the two-era data defect — the worst of them. Historical prices had been ingested under two different adjustment conventions across eras, so a split looked like a 75% overnight crash and an ex-dividend day understated total return. Repairing it rewrote about 21,000 documents and deleted 369 orphans, then re-derived four downstream stores: conformal residuals, the Information Coefficient series, performance metrics, benchmarks. The served net asset value moved from $103,182 to $111,870 once the history was finally telling the truth. None of that is strategy. All of it is the precondition for trusting a single number the strategy later produces.
The daily pipeline had a single point of failure — one unguarded data fetch that, on a bad afternoon, blanked three dashboard cards. It got per-ticker retries, a minimum-coverage gate, and a dead-man’s switch: a scheduled job that pings a freshness endpoint every weekday evening and screams if the pipeline went dark. The inference path learned to fail loud instead of silently degrading from five models to four and serving a NaN. Conformal coverage — the property that an "80% interval" actually contains the outcome 80% of the time — went from unmonitored to tracked against its nominal target.
Dozens of pull requests, each one small, most of them defensive. By the end, the system was production-grade in the way that matters: when it said something, you could believe the plumbing. Which is exactly what made the next part unavoidable.
The reckoning
With the data finally clean, I ran the backtest I had been deferring — not the optimistic in-sample one, the brutal out-of-sample one, with every knob frozen as it would have been on each historical day.
The first result was a gift disguised as a disappointment: a lookahead leak. Somewhere in the feature alignment, information from the present was bleeding into the past, and it was worth about 0.23 of Sharpe ratio — an enormous amount, the difference between a strategy that looks clever and one that looks like nothing. I closed the leak.
Here is what was left, de-leaked and honest:
The ML ensemble: Sharpe ≈ 0.26
A boring 60/40 stock/bond portfolio: Sharpe ≈ 0.49
Just owning the S&P 500 (IVV): Sharpe ≈ 0.54
The cathedral lost to a two-line portfolio. It lost to doing nothing but holding the index. Every model, every FRED indicator, every Black-Litterman view, every Cloud Run service — and the edge over a coin you could buy in one click was negative.
The bug that wanted to save it
There was one more place to look. Maybe the ensemble was a bad return-timer but a good diversifier — uncorrelated enough that blending a little of it into 60/40 would lift the whole. For a brief, hopeful afternoon, the math agreed: a "+0.10 Sharpe overlay benefit" appeared in the blend.
It was a bug. The ML return series was indexed to the start of each period while the benchmark was indexed to the end; the merge was comparing a forecast to a return it had effectively already seen. Once I shifted the series to align them honestly, the benefit evaporated. The ensemble’s correlation to 60/40 was +0.86, and the optimal blend weight — the amount a mean-variance optimizer wanted to allocate to it — was zero.
I want to be clear about this moment, because it is the whole point. The seductive result was mine. I had built the thing, I wanted it to work, and the bug handed me exactly the answer I was hoping for. The job was to distrust it precisely because it was flattering. Retracting that "+0.10" was the most valuable line of analysis in the entire project.
The last stand
If prediction was dead, perhaps the framing was wrong. The thesis pivoted: maybe the value was never in forecasting returns but in sizing risk — volatility targeting, risk parity, a touch of leverage when conditions are calm. This is a respectable strategy that does not need to predict anything.
I tested it cleanly, in a separate sandbox built from scratch to avoid inheriting any of the original system’s assumptions or leaks. Volatility targeting, risk parity, leveraged and unleveraged variants, all leak-verified.
No variant beat 60/40 on both Sharpe and maximum drawdown. Volatility targeting did cut drawdowns — but it gave back more in Sharpe than it saved. The reframe failed the same test the original system failed, just more politely.
The verdict, and the freeze
The conclusion is plain, and I have stopped resisting it: for this universe and this horizon, buy 60/40 (with a little gold) and stop touching it. The diversifying sleeves that were supposed to give the model something to be clever about are mostly S&P proxies anyway; the native data window is too short to be confident about anything more exotic.
So the project is frozen. Not abandoned — frozen, deliberately, with the reasons written down. The infrastructure still runs; the dashboard still serves; the dead-man’s switch still watches. But I am not going to pretend it is earning its keep as an alpha engine, because the evidence says it is not, and I went to considerable lengths to give it every chance to say otherwise.
What did I actually build, then? Not a money machine. I built a rigorous apparatus for asking "does this work?" and answering it honestly — including the discipline to fix a data layer until it could be trusted, to hunt a leak worth a quarter of a Sharpe point, and to delete my own most flattering result the moment it failed to reproduce.
The hard, unsexy lesson of quantitative investing is that most edges are not real, and the ones that feel most real are often just leaks you have not found yet. The integrity of this project is not in the five models. It is in the freeze.
Coda: what continues
The strategy is closed. The curiosity is not.
There is a successor on my bench — call it TPI Lab — but it is not TPI again with the serial numbers filed off. It hunts nothing. It is a deliberately cheap, deliberately small re-creation of the same apparatus: a hand-rolled agentic loop, a local LLM on my own machine, the decision-math behind clean ports, and a cloud bill of exactly zero. Where TPI was eleven services trying to be right about markets, the Lab is a sandbox trying to teach me something about agents — with the financial question already settled and off the table.
That is the honest split. The thesis goes in the freezer with its reasons written on the lid. The engineering — the part I actually loved, the part that was real the whole time — gets to keep going somewhere that costs nothing and pretends nothing.