A few weeks ago, a friend asked me a question that perfectly summed up the current state of tech:
"So, did you build a trading bot yet? Everyone has one now. Mine hasn't started earning yet, but soon... oh, very soon."
My immediate, instinctive thought was: Nope. I’m not building a trading bot.
Right now, the internet points you in exactly one direction: wrap a Python script around an LLM, write a moving-average crossover strategy, hook it up to a broker API, and wait for the passive income.
Trusting a generic momentum script with live execution seemed like a great way to aggressively automate losing money.
But that conversation planted a seed. I didn't want a "bot." But what about a systematic copilot?
So, I didn’t build a trading bot - I built a lab where a physics-style equilibrium has to earn its keep against daily market data, on paper, before anyone trusts it with money.
I don't have a background in finance, but I know mathematics and backend architecture. I wanted to see if I could take a mental model from a completely different industry, port it over, and test it against the European ETF market.
The result is FunTrade - an open-source, paper-first, long-only daily research simulator for UCITS mutual funds and ETFs. No on-chain crypto trading, no HFT, and no broker hooks.
Here's how I built it by mashing different domains together and going against the hype.
The Hypothesis: Rubber bands vs. Momentum
I previously used perturbation theory to model energy markets - defining a stable baseline and measuring localized shocks against it to predict shifts.
I had a hypothesis: What if the ETF market isn't just a momentum trend, but a rubber band?
Instead of assuming "the trend is your friend," I wanted to test if extreme market movements eventually snap back to a slow-moving baseline (mean-reversion).
I also had to sharpen the metaphor. A market doesn’t behave like a perfect “always snap back to yesterday’s price” machine: every stretch changes its effective state, and repeated extreme pulls start to look less like elasticity and more like a regime shift.
In FunTrade that’s not hand-waving - it’s enforced with two layers.
First, the equilibrium anchor isn’t fixed; it’s a seasonal OU baseline whose “μ” can drift as the calibration window updates (the band slowly creeps).
Second, I only treat large deviations as potentially temporary when the market still looks “elastic”: spike + consecutive-bar regime gates explicitly block mean-reversion buys when the dislocation looks structural rather than a short-lived stretch.
If you keep pulling hard enough, the model decides the band has stopped behaving like a spring - and buys are disabled.
Translating the Math
To test this, I split the logic into distinct layers:
- (The Anchor): This is the baseline equilibrium. Instead of just smoothing the last 50 days, I model a seasonal Ornstein–Uhlenbeck process on log prices. Then I “nudge” the equilibrium using macro inputs (interest rates, credit spreads, and FX = foreign-exchange rates), because currency moves change the EUR value of funds priced elsewhere - even if the local NAV hasn’t “explained” the move.
- (The Perturbation): This is the fast, daily deviation score. I compute a blended z-score (price vs. the band, plus volume/strength signals) to measure how statistically distant today is from the equilibrium.
Crucially, a high or low ε doesn't mean the asset is "fundamentally" cheap or expensive in the real world. It just means it is statistically dislocated from the modelled band.
Hitting a Wall: The "Falling Knife" Problem
When you test a hypothesis against real data, you quickly find the holes in your logic.
My initial failure mode was obvious: pure mean-reversion is dangerous. If a fund drops off a cliff due to a massive structural market panic, the pure math screams, "It’s a massive statistical deviation! Buy!" But in reality, catching that falling knife will destroy your portfolio, at least in the short term.
I had to adapt the system so it understood context. I went back to the architecture and added two things:
- Regime Validity Filters: Spike and consecutive-bar gates that explicitly block buy signals when the dislocation looks structural rather than temporary.
- (The Trend Dampener): An overlay that adjusts the ε score and optionally gates sell signals in strong uptrends, based strictly on the medium-term trend.

Figure 1: Grafana visualizing the slow
equilibrium band against actual price, with the fast ε perturbation scores tracking the deviations below. Red annotations mark stretches where regime filters block mean-reversion buys (possible structural dislocation, not a "buy now" signal).
The Laboratory: Strict Boundary Architecture
To actually run these tests, you can't just run a Jupyter notebook. You need a strict boundary that runs walk-forward backtests without accidentally leaking tomorrow's data into today's calibration—otherwise, you are just lying to yourself.
I built the v1 stack purely for offline rigor:
- Storage: TimescaleDB via Docker. Essential for handling daily OHLC bars and keeping calibration stored per symbol without memory bottlenecks.
- Engine: Python 3.12 (managed via uv) to crunch the daily matrices and sweep the data.
- Observability: Grafana to visualize the bands vs. ε deviations over time, plus a Streamlit UI for human-readable daily recommendations.
Mixing it all together
Adapting a physics model taught me more about financial regime shifts and structural market failures than reading a dozen finance books ever could. Seeing those regime filters actually step in to block the system from blindly buying every crash-day ε spike was the exact moment this went from a math experiment to a functional copilot.

Figure 2: The Streamlit frontend: abstracting the TimescaleDB and Python calibration matrices into daily, human-readable tactical overlays.
Going against the traditional lines of thought forces you to build better systems. If you want to poke around the time-series pipelines, look at the math, or run the offline demo on synthetic data, I open-sourced the whole thing.
GitHub Repository: https://github.com/SpektrNO/fun-trade/
To spin it up locally:
make setup && make run && make demo SYMBOL=VWCE.DE
make demo uses synthetic bars so you can run offline; swap in make ingest for real symbols.
Has anyone else successfully (or disastrously) ported a mental model from a completely unrelated field into software architecture?
Top comments (0)