playitsmart.nl

Terug naar home

13 mei 2026 · 4 min lezen

Post #1

De dag dat ik mijn database architectuur leerde

Vier performance issues, drie verkeerde diagnoses, en één juiste fix

TL;DR: V3 backtest was 5x trager dan V2. Drie keer dacht ik de oorzaak te weten, drie keer fout. Pas toen ik echt ging meten met profiling, vond ik de echte bottleneck. De fix was geen quick patch maar een fundamentele tabel toevoeging. Dit is hoe je leert.

Het probleem

Ik bouw een systematic swing trading systeem dat over een paar weken live gaat met €10.000 eigen kapitaal. V2 was klaar en goed: +110% over 4 jaar in een €50K backtest, Sharpe ratio 1.05.

V3 zou nog beter worden. Vijf nieuwe filters om typische verliestrade patronen te blokkeren. Op papier solide.

Toen ik V3 backtest startte, bleek hij 5x trager dan V2. Een uur werk werd 5 uur. Onhoudbaar voor productie ontwikkeling.

Drie keer fout gokken

Fix 1: Connection refresh interval

Eerste hypothese: Supabase HTTP/2 stream limit (19999) raakt op door alle extra queries. Refresh elke 30 dates ipv elke 100 dates.

Resultaat: backtest crashte niet meer, maar bleef even traag. Symptoombestrijding, geen fix.

Fix 2: Een filter uitschakelen

Tweede hypothese: filter 3 (200-day moving average check) doet voor elke kandidaat ticker een aparte query. Schakel die uit.

Resultaat: nog steeds 20 sec/dag. Cursor flagde achteraf: "MA200 was al batched in load_daily_state, alleen een dict-lookup."

Verkeerde diagnose. Verkeerde fix.

Fix 3: Compute upgrade

Derde hypothese: Supabase Micro compute (2 vCPU shared) is te klein voor de query volume. Upgrade naar Medium (4 GB RAM, dedicated CPU).

Voor het project een aanvaardbare investering: ongeveer €60 per maand.

Resultaat na 5 minuten upgrade: timing per dag identiek. Geen verbetering.

Dan eindelijk: meten

Op dit punt was ik vier uur verder, tien keer gedacht "this is it", elke keer mis.

De juiste vraag was niet "waar denk ik dat het zit", maar "waar zit het werkelijk".

Profiling code toegevoegd in vijf minuten. Alle tijd-intensieve operations meten met een gewoon Python time.perf_counter() interval. Géén complex profiling tool, gewoon timing rond elke query.

Output liegt niet:

fetch_prices_history_range_200d_window:  13.85 sec  ← bottleneck
fetch_prices_highs_batched_90d:           3.78 sec
calculate_sector_medians:                 0.88 sec
fetch_eur_per_usd:                        0.04 sec
fetch_latest_fundamentals:                0.47 sec
TOTAL per dag:                           19.60 sec

70 procent van de tijd in één query. Niet de filter logic, niet de compute, niet de connection. Een query die 200 dagen × 542 tickers = 108.400 rijen ophaalt over de wire, elke dag opnieuw, voor 1015 simulatiedagen.

De echte fix: pre-compute caching

Mijn instinct dacht in code-optimalisaties: minder queries, sneller queries, slimmer queries. Maar de echte vraag was architecturaal: hoeveel data moet ik werkelijk over de wire halen?

Het antwoord: niet 108.400 rijen per dag voor de moving average. Eén waarde per ticker per dag. Pre-berekend en gecached.

Nieuwe tabel ticker_indicators_daily:

  • ticker, date als primary key
  • ma_200d (200-day moving average)
  • high_90d_max (max high last 90 days)
  • low_90d_min (min low last 90 days)

Backfill eens, daarna dagelijks bijwerken na price updates. Backtest leest één SELECT met alle 542 tickers in één keer, krijgt 542 numbers terug, niet 108.400 rijen.

Verwachte impact: 20 sec per dag naar 3 sec per dag. V3 backtest van 5 uur naar 75 minuten.

Wat ik leerde

Niet wat ik dacht te leren: dat code optimization werkt. Dat is duidelijk en bekend.

Wel wat ik leerde:

  1. Profile vóór je optimaliseert. Geen uitzonderingen. Elke gok zonder data is een mogelijkheid om uren weg te gooien.

  2. Symptomen behandelen lost niets op. Connection refresh aanpassen was symptoombestrijding. De query was nog steeds traag, alleen niet meer crashend.

  3. Cache wat je tijdens backtests honderden keren leest. Een 200-day moving average voor een vaste datum verandert niet. Honderdvierduizend rijen voor één getal ophalen is verspilling.

  4. Compute upgrade is geen architectuur-oplossing. Als je query design slecht is, helpt meer CPU niets. Het is hetzelfde probleem op duurdere hardware.

  5. Dit doet AI ook niet vanzelf goed. Cursor implementeert wat ik vraag, maar niet altijd wat ik nodig heb. Drie keer een verkeerde fix omdat ik de verkeerde fix vroeg.

De productie discipline les

Voor productie systemen, vooral met echt geld op de lijn, is dit een verplichte regel:

Voor elke nieuwe feature die data fetch nodig heeft, vraag eerst: "kan dit pre-computed?". Als dezelfde berekening elke dag opnieuw gebeurt voor historische data: cache het in dedicated tabel.

Geen workaround, geen quick fix. Architecturale keuze vooraf.

Deze regel staat in mijn .cursorrules zodat elke nieuwe feature er aan getoetst wordt. Defense in depth, ook tegen mijn eigen blinde vlekken.

Wat dit betekent voor de V3 launch

V3 wordt niet "iets sneller", V3 wordt echt productie-grade. Niet alleen voor de backtest, maar ook voor live trading. Vanaf 22 juni leest het systeem dagelijks die ene cached tabel, krijgt zijn signalen, plaatst orders, klaar. Geen wachten op data fetches.

Dat is wat productie discipline betekent. Niet werkende code. Schaalbaar werkende code.

Deze post is onderdeel van playitsmart.nl, een experiment waar Erik publiek een trading systeem bouwt met €10.000 eigen kapitaal. Live launch: 22 juni 2026.

Wekelijks volgen?