Wanneer de GPU loont: de parameter-sweep-roofline, waar een headline van 167x in werkelijkheid 27x algoritme maal 6.2x hardware is
Onderdeel van de serie "Backtests zonder illusies".
De snelheidsladder eindigde op een bewust onbevredigende noot. We hadden een parameter-sweep van 80 combo's van 69.9 seconden pandas teruggebracht naar 0.23 seconden parallelle numba op een laptop-CPU — een gemeten 298x — en betoogden vervolgens dat een GPU niet de ontbrekende sport van de ladder was. De reacties namen daar geen genoegen mee, en terecht. "Niet de ontbrekende sport" is een uitspraak over een werklast, op een schaal. Het is geen natuurwet. Dus doet dit artikel het eerlijke ding en legt de GPU op de testbank.
Het resultaat is geen verdict, het is een curve. De voorsprong van de GPU op de CPU is niet een enkel getal dat je op een slide kunt zetten; het is een functie van hoeveel werk je hem per call toeschuift. Op onze multi-timeframe indicator-precompute loopt de speedup van de GPU ten opzichte van de CPU-engine van 54.5x wanneer we een parametercombinatie per keer vragen tot 359.6x wanneer we er 61 tegelijk vragen. Dezelfde kernel, dezelfde data, dezelfde hardware — het enige dat veranderde is de batch. Een benchmark die een van die getallen rapporteert en het andere verzwijgt, meet niet de GPU, maar kiest een headline.
En zelfs 359.6x is niet wat het lijkt. Pel het uiteen en een grote multi-timeframe-headline van 167x valt uiteen in 27x algoritme — een herschrijving die ook de CPU 27x sneller maakt — maal 6.2x echte hardware. De GPU deed de 27x niet. De wiskunde deed dat. Dit artikel gaat over het uit elkaar houden van die twee, want ze door elkaar halen is hoe een grafische kaart van $2,000 wordt verkocht om een probleem op te lossen dat een git commit gratis had opgelost.
Herkomst: alle getallen hieronder zijn gemeten op een Apple M2 Max, fp32 op de Metal-GPU via MLX versus fp64 numba op twaalf CPU-threads, uit onze benchmarkscripts engine_multitf_gpu.py en bench_param_sweep.py, elk bewaakt door een equivalentiecheck die bevestigt dat het GPU- en het CPU-pad dezelfde trades produceren. Geen publiek begeleidend paper voor dit artikel — de scripts zijn het archief.
De vraag is een roofline, geen benchmark

De reden dat "hoeveel sneller is de GPU" geen enkelvoudig antwoord heeft, is het roofline-model (Williams, Waterman & Patterson, 2009). De haalbare prestaties van een processor worden tegelijk begrensd door twee plafonds: een vlak plafond bepaald door piek-compute (FLOP/s), en een hellend plafond bepaald door geheugenbandbreedte maal arithmetic intensity — het aantal nuttige operaties per verplaatste byte. Werk dat goedkoop per byte is leeft onder het hellende plafond en is bandbreedtegebonden; werk dat rijk per byte is bereikt het vlakke plafond en is compute-bound. Het vlakke plafond van de GPU torent boven dat van de CPU uit, maar zijn hellende plafond en, cruciaal, zijn vaste kosten per call schalen niet even gracieus mee naar beneden.
Voor een parameter-sweep domineert een derde term de linkerkant van de grafiek: launch- en transfer-overhead. Elke GPU-call betaalt een ruwweg vaste prijs — de kernel dispatchen, inputs over de (op Apple Silicon unified, maar niet gratis) geheugengrens verplaatsen, resultaten terug verplaatsen. Modelleer de wall time voor een batch van combinaties als
waarbij die vaste overhead is, de marginale kosten per combo van de GPU, en die van de CPU. De speedup is dan
Deze ene breuk verklaart het hele artikel. Bij kleine verplettert de in de noemer de verhouding — je hebt betaald om de GPU wakker te maken en hem nauwelijks gebruikt. Naarmate groeit, amortiseer je over meer combo's en klimt de speedup naar zijn asymptoot , de echte hardwareverhouding. Het halverwegepunt ligt bij : een "ridge point" in de batch-ruimte dat je vertelt hoe breed je sweep moet zijn voordat de GPU ook maar de helft is van wat hij kan zijn. Een sweep van enkele tientallen combo's zit ver links van die ridge. Een sweep van duizenden zit op het vlakke deel waar de GPU eindelijk zijn plafond verdient.
De juiste vraag is dus nooit "is de GPU sneller". Het is "zit mijn sweep rechts van de ridge, en is mijn werk per combo compute-bound genoeg om het vlakke plafond te bereiken als hij daar aankomt". Beide moeten waar zijn. De rest van dit artikel meet waar die drempels werkelijk liggen.
Het single-timeframe-verdict: de GPU wint maar net

Begin met de werklast die de snelheidsladder gebruikte: een single-timeframe HMA/HMA3-sweep, 80 combinaties over 150,000 bars. We voegden een zesde sport toe aan die ladder — M5, indicatoren op de Apple-GPU via MLX, trades nog steeds geëxtraheerd op de CPU. Opgewarmd, beste van drie, de equivalentie-gate op groen:
| Methode | Wall time | vs pandas | Combo's/s |
|---|---|---|---|
| M0 pandas + loop | 287.08s | 1.0x | 0.3 |
| M2 numba (1 core) | 2.02s | 142x | 39.7 |
| M4 mp + numba (12 cores) | 0.33s | 883x | 245.9 |
| M5 MLX GPU (fp32) | 0.10s | 2796x | 779.2 |
Tegenover de naïeve seriële baseline oogt de GPU heroïsch — 2,796x. Maar dat is een vergelijking die niemand met eerlijke bedoelingen zou moeten maken: ze zet een goede GPU-implementatie tegenover de slechtste CPU-implementatie. Zet de GPU naast de CPU die je werkelijk zou inzetten — dezelfde kernel op alle twaalf cores, M4 — en de winst zakt in tot een nuchtere 3.2x (779 versus 246 combo's per seconde). Een complete grafische kaart, die de hele sweep draait, verslaat een twaalfcore-CPU-pool met een factor drie.
Die 3.2x is niet niets. Het is ook niet de reden waarom iemand een GPU koopt. En het is precies wat de roofline voorspelt voor een sweep die zo smal is: 80 combo's is links van de ridge. De vaste launch-en-transfer-overhead is nog steeds een betekenisvol deel van een klus van 0.10 seconde, dus we bereiken de asymptoot nooit. Erger nog: een deel van de kosten per combo is de O(n) trade-extractiepass, die we bewust op de CPU lieten — een term die de GPU helemaal niet kan versnellen (waarom, daarover meer in de volgende sectie). Voor een single-timeframe-onderzoekslus van deze omvang blijft het oorspronkelijke verdict van de snelheidsladder staan: de GPU is niet de ontbrekende sport. Parallelle numba bracht je al op 0.23–0.33 seconden, en dat afschaven naar 0.10 is niet wat een onderzoeker deblokkeert. De orkestratie rond de sweep wel.
De interessante woorden in dat verdict zijn deze omvang. Schuif naar rechts langs de batch-as en het verhaal verandert.
Waar de kosten werkelijk zitten
Voordat we de batch opschalen: kijk eerst waar we eigenlijk voor betalen, want de roofline beloont je alleen als het dure deel compute-bound is. Profileer de sweep en vrijwel alles is een ding: weighted-moving-average-convoluties. Een HMA is drie WMA's; een HMA3 is er vier; elke combinatie draait ze opnieuw over de hele reeks. Trade-extractie — loop over de twee indicatorarrays, vind de tekenwisselingen van hma - hma3, boek de fills — is een enkele goedkope O(n)-pass. De sweep is een convolutiewerklast in het kostuum van een handelsstrategie.
Die splitsing is precies de tweedeling van de roofline:
- Convoluties zijn compute-bound. Elke prijs wordt ingelezen in vele overlappende venstersommen, dus de arithmetic intensity — operaties per verplaatste byte — is hoog. Dit werk reikt naar het vlakke compute-plafond, en het vlakke plafond van de GPU is het plafond dat torent. Beter nog: de vensters overlappen over combinaties heen: een WMA van lengte 40 is nuttig voor tientallen combo's, dus een gebatchte precompute deelt hem een keer in plaats van hem per combo opnieuw te berekenen. Batchen amortiseert niet alleen launch-overhead; het verhoogt de arithmetic intensity door loads te hergebruiken. Dit is het deel dat op de GPU thuishoort.
- Trade-extractie is bandbreedtegebonden en vol branches. Een sequentiële pass, data-afhankelijke branches bij elke kruising, in wezen geen hergebruik. De arithmetic intensity zit vlak bij de vloer, en de control flow is vijandig voor een SIMD-apparaat. Hem naar de GPU duwen zou weinig opleveren en veel kosten; hij blijft op de CPU. Wat betekent dat het de seriële staart van een wet-van-Amdahl-splitsing is — een vaste vloer waar de GPU-speedup nooit doorheen kan, en deel van waarom het single-TF-getal verzadigde op 3.2x.
Er schuilt een tweede, scherpere les in de multi-timeframe-versie van deze kernel, en die is de bron van de 27x die we steeds beloven uit te leggen. De multi-TF-engine lijnt een HMA van een hoger timeframe uit op de basisindex van 1 minuut, zonder look-ahead. Geschreven op de voor de hand liggende manier is dat per bar O(length^1.5) werk — herbereken de moving averages van het hogere timeframe bij elke basisbar. Maar de uitgelijnde HMA is lineair in een korte buffer van de laatste paar gesloten candles van het hogere timeframe plus de lopende close, dus de hele berekening per bar klapt ineen tot een vaste gewichtsvector: een conv1d over de reeks van gesloten candles, gevolgd door een O(n) gather. Honderden miljoenen redundante operaties worden een convolutie over een veel kortere reeks.
Die collapse is een algoritmische winst, geen hardwarewinst. Het is een betere formule. Ze draait op de GPU, en ze draait net zo goed op de CPU — np.correlate plus een gather, in fp64. Houd dat stevig vast: de grootste factor in de multi-TF-headline is een herschrijving die ook beschikbaar is voor een machine zonder enige GPU. Wanneer we de 167x straks eindelijk ontleden, is dit de 27x.
De voorsprong groeit met de batchgrootte

Nu de meting waartoe de roofline ons opdroeg. Neem de dure as — de uitgelijnde hoger-timeframe-HMA-precompute op de basisreeks van 1 minuut, de langste candle-stream die we hebben — en voer de GPU een toenemend aantal lengtecombinaties per call, . De CPU-baseline is hier de eerlijke productie-engine: numba met prange over alle twaalf cores. Voor elke batch meten we beide en nemen we de verhouding.
| Batch (combo's/call) | GPU-speedup vs 12-core CPU-engine |
|---|---|
| 1 | 54.5x |
| 2 | 102.5x |
| 4 | 129.5x |
| 8 | 187.4x |
| 16 | 267.4x |
| 32 | 245.0x |
| 61 | 359.6x |
Dit is , getekend in echte metingen. Bij ligt de GPU al 54.5x voor — omdat deze vergelijking tegen de naïeve per-bar-engine is, dus de algoritmische collapse zit er zelfs bij een combo al in gebakken — maar hij zit nog lang niet aan zijn plafond: de vaste overhead domineert een call met een combo nog steeds. Verdubbel de batch en de speedup verdubbelt bijna, naar 102.5x; bij is het 267.4x; bij is het 359.6x en zichtbaar nog steeds stijgend. De voorsprong groeit met de omvang van het probleem. Dat is de belangrijkste zin over GPU's en parameter-sweeps, en het is precies het tegenovergestelde van hoe GPU-speedups meestal worden geciteerd — alsof ze een constante eigenschap van de chip zijn.
Twee eerlijkheidsnoten, want dit is de serie Backtests zonder illusies en een keurig monotone tabel verdient altijd argwaan.
Ten eerste de dip: leest 245.0x, onder de 267.4x bij . Dat is geen ruis om onder het tapijt te vegen — het is een chunk-grens-artefact. Onze conv1d pakt 32 lengtes in een kernel-chunk, dus vult precies een chunk zonder speling, terwijl overloopt in een tweede chunk die het device toevallig beter verzadigt. Het punt van de roofline is de trend, en een echt device heeft kwantisatiestappen; we rapporteren de wiebel in plaats van eromheen te cherry-picken.
Ten tweede, en belangrijker: 54.5x en 359.6x zijn beide gemeten tegen de naïeve CPU-engine, en geen van beide is de hardwarewinst. Beide getallen bevatten nog steeds de algoritmische collapse van 27x. Zou je de CPU-baseline vervangen door het ineengeklapte algoritme op de CPU — dezelfde formule, fp64, beide kanten geoptimaliseerd — dan zou elke rij met ruwweg die factor krimpen. En dat is precies de decompositie die de volgende sectie exact maakt.
De eerlijke decompositie: 27x algoritme maal 6.2x hardware

Om het algoritme van het silicium te scheiden moet je drie paden meten op hetzelfde multi-timeframe-grid, niet twee. Dus draait de benchmark:
- cpu-engine — de productie-numba-engine, per-bar uitgelijnde HMA over alle cores. De naïeve-maar-parallelle baseline.
- cpu-collapsed — de ineengeklapte gewichtsvectoren,
np.correlateplus gather, fp64, op de CPU. Dezelfde hardware als (1), beter algoritme. - gpu-mlx — de ineengeklapte gewichten als een gebatchte
conv1dop de Metal-GPU, fp32. Hetzelfde algoritme als (2), andere hardware.
Zet ze naast elkaar en de volledige multi-timeframe-headline factoriseert netjes:
De linkerfactor, 27x, is het algoritme — de collapse van per-bar naar convolutie uit de vorige sectie. Die heeft niets met de GPU te maken. Implementeer hem in numpy en je laptop-CPU wordt 27x sneller op deze werklast, voor de prijs van een refactor. De rechterfactor, 6.2x, is de hardware — de eerlijke, gelijkwaardige winst van de Metal-GPU ten opzichte van hetzelfde geoptimaliseerde algoritme op twaalf CPU-cores. Die 6.2x is het enige deel waarvoor je werkelijk een GPU nodig had.
Dit is de hele moraal van het artikel, uitgedrukt als rekensom. Wanneer een vendor-benchmark, een library-README of een enthousiaste collega je "167x op GPU" laat zien, hoort de reflex een enkele vraag te zijn: wat was de CPU-baseline? Als de baseline de naïeve implementatie was — en dat is hij vrijwel altijd, want een trage baseline maakt een betere slide — dan is het grootste deel van de headline een algoritmische winst waar de CPU evenzeer recht op had, en is alleen het residu hardware. Hier is het residu 6.2x. Een claim van 167x overdrijft de bijdrage van de hardware met ruwweg een factor 27.
En merk op hoe de hardware-factor zelf meebewoog met de probleemgrootte. Op de kleine single-timeframe-sweep was de echte GPU-versus-beste-CPU-winst 3.2x. Op de grotere multi-timeframe-precompute was het 6.2x — dezelfde twee chips, bijna het dubbele hardwarevoordeel, puur omdat de grotere werklast verder omhoog duwt langs de roofline, richting het vlakke compute-plafond van de GPU, voordat de CPU kan bijblijven. Ook de hardwarevoorsprong is geen constante. Het is een punt op dezelfde stijgende curve, en de manier om op die curve naar rechts te bewegen is de batch groter en het werk per combo rijker maken.
Een beslisgids: hoe breed moet de sweep zijn?

Vouw de roofline terug tot een beslissing die je kunt nemen voordat je geld uitgeeft. De GPU loont wanneer beide voorwaarden van de roofline tegelijk gelden: je sweep zit rechts van de batch-ridge (, zodat de vaste launch-en-transfer-overhead wordt geamortiseerd), en je werk per combo is compute-bound (rijk genoeg aan arithmetic intensity om het vlakke plafond te bereiken, geen dunne O(n)-pass). Concreet, uit wat we hebben gemeten:
- Enkele tientallen combo's van een single-timeframe-strategie: sla de GPU over. Je zit links van de ridge; de eerlijke winst ten opzichte van parallelle numba is ~3.2x op een klus die al een tiende van een seconde duurt. Het knelpunt is niet de kernel, het is alles eromheen.
- Duizenden combo's, of een werkelijk multi-timeframe / multi-indicator precompute: de GPU verdient zijn plaats. De overhead amortiseert, de gedeelde convoluties verhogen de arithmetic intensity, en de hardwarewinst klimt naar 6.2x en blijft stijgen met de batch. Dit is het regime waarin een GPU een nachtelijke sweep verandert in een koffiepauze.
- Beklim eerst de CPU-ladder — die is goedkoper en komt eerst. De 298x op de CPU en de algoritmische collapse van 27x zijn gratis of bijna gratis, en het zijn randvoorwaarden, geen alternatieven: de 6.2x van de GPU komt bovenop het ineengeklapte algoritme, dat je toch al moest schrijven. Een GPU die op een naïeve pipeline wordt geschroefd, meet vooral de naïviteit.
Er is ook een belasting op de GPU-tak die niets met snelheid te maken heeft, en je moet haar meerekenen: Apple's Metal-GPU heeft helemaal geen fp64. Alles draait in fp32, ~1.2e-7 relatieve precisie. Dat sloopt de schoolboektruc voor snelle moving averages — de O(n) prefix-sum-WMA — want op een prijsschaal rond 30,000 over 150k bars bereiken de lopende sommen ~1e14, zeven ordes van grootte voorbij het veilige gehele bereik van fp32; we hebben relatieve fouten tot ~2e2 gemeten (een factor tweehonderd, geen twee procent). De werkende formulering is de directe gevensterde convolutie, waarbij elke venstersom een begrensd aantal termen van vergelijkbare grootte is en fp32 nauwkeurig blijft tot ~8e-7. Zelfs dan zal een strategie die beslist op het teken van hma - hma3 af en toe een kruising omklappen op een grensgeval-bar waar de twee curven elkaar bijna raken, omdat fp32-afronding een bijna-gelijkspel doet kantelen. Daarom wordt het GPU-pad geleverd met een equivalentie-gate die meet hoeveel de trades zijn afgeweken — PnL-delta in basispunten, relatieve verschuiving in het aantal trades — in plaats van bit-identieke output te beweren die het nooit kan leveren. Op onze run was die afwijking 90 verschoven fills op 479,016 (0.019%), ruim binnen de tolerantie, maar de last is echt: naar de GPU gaan betekent eigenaar zijn van een numeriek pariteitsverhaal, niet alleen van een snellere klok. Ook die engineeringkosten horen bij het break-evenpunt.
De getallen zijn Apple-vormig; de curve niet
Elk cijfer hierboven is een Apple M2 Max: een unified-memory-apparaat waar GPU en CPU een pool delen, en een fp32-only GPU zonder double precision. Een discrete NVIDIA- of AMD-kaart verandert de constanten, en het loont om expliciet te zijn over welke kant elk van hen beweegt, want de vorm van het argument overleeft, ook al doen de getallen dat niet.
- Transfer-overhead wordt slechter, niet beter. Een discrete kaart zit achter PCIe, dus inputs en resultaten maken een echte kopie over de bus die unified memory vermijdt. Dat duwt de batch-ridge naar rechts — je hebt een nog bredere sweep nodig voordat een discrete GPU zijn launch amortiseert. De linkerrand van de roofline is steiler op een PCIe-apparaat, niet zachter.
- Het vlakke plafond wordt hoger. Een datacenter-GPU heeft veel meer FLOP/s en bandbreedte dan een geïntegreerde, dus de asymptotische hardwarewinst op een verzadigende sweep is groter dan onze 6.2x. De beloning voor het bereiken van de rechterkant van de curve groeit; de tol voor het blijven zitten aan de linkerkant groeit ook.
- fp64 komt terug, en daarmee de prefix-sum-truc. Op een kaart met echte double precision is de O(n) prefix-sum-WMA weer levensvatbaar en kan de pariteits-gate worden aangescherpt richting bit-exactheid. De specifieke fp32-belasting die wij betaalden — directe convolutie in plaats van prefix-sums, een gate die de afwijking meet in plaats van een assert — is een Apple-Silicon-detail, geen wet.
Niets hiervan verandert de these. Op elk apparaat geldt : een vaste overhead die je moet amortiseren, een asymptoot die je alleen van rechts benadert. De constanten zijn hardware; de curve is rekenkunde. Meet je eigen , en met een batch-sweep van vijf regels code voordat je iemands headline vertrouwt — inclusief de onze.
Waar dit op aansluit
Dit is de vierde meting in een kleine subserie over waar backtest-snelheid werkelijk vandaan komt, en de stukken passen in elkaar als een enkel argument over wat je moet optimaliseren voordat je geld uitgeeft:
- De snelheidsladder klom van pandas naar parallelle numba voor 298x op de CPU alleen, en liet de GPU achter als open vraag. Dit artikel beantwoordt die: de GPU is een echte maar voorwaardelijke vijfde sport, goed voor 3.2x–6.2x boven de hoogste CPU-sport, en alleen zodra de sweep breed genoeg is om de roofline te beklimmen.
- De IPC-belasting maakte dezelfde beweging in de andere richting — meten wat het kost om het process te verlaten — en kwam tot dezelfde vorm van conclusie: de grens (een socket, een GPU-launch) is goedkoop; de belasting zit in hoe vaak en hoe breedsprakig je hem oversteekt. Batch je GPU-calls om dezelfde reden als waarom je je IPC batcht: om een vaste prijs per oversteek te amortiseren.
- De geaggregeerde parquet-cache is de CPU-kant van wat de GPU-precompute doet — bereken de gedeelde indicatoren een keer, hergebruik ze over elke combo. De GPU trekt dat hergebruik-en-batch-principe alleen door tot in het silicium.
- En de fp32-pariteits-gate is een backtest-live-pariteitsprobleem in het klein: zodra je snelle pad iets subtiel anders berekent dan je referentie, ben je een gekwantificeerde verantwoording van de afwijking verschuldigd, geen handgebaar.
De verbindende discipline is dezelfde die deze hele serie uitdraagt: meet het ding dat je daadwerkelijk verkocht wordt. Een speedup is een verhouding, en een verhouding heeft een teller en een noemer. De meeste GPU-teleurstelling komt van een noemer — de CPU-baseline — die gekozen is om te vleien, en de meeste GPU-verspilling komt van een sweep die te klein is om de linkerrand van de roofline te verlaten.
Conclusies
- Een GPU-speedup is een curve, geen getal. Op onze multi-timeframe-precompute liep de voorsprong op de CPU van 54.5x bij een combo per call tot 359.6x bij eenenzestig — dezelfde chip, dezelfde data. Elk los cijfer is een punt op die curve; vraag bij welke batchgrootte het is gemeten.
- Ondervraag altijd de CPU-baseline. De multi-timeframe-headline van 167x factoriseert netjes in 27x algoritme (een collapse van per-bar naar convolutie, die de CPU evenveel versnelt) maal 6.2x echte hardware. De faire GPU-versus-beste-CPU-winst is hier 3.2x single-timeframe, 6.2x multi — geen 167x.
- De voorsprong groeit met de probleemgrootte, en de hardwarefactor ook. Een grotere batch en rijker werk per combo duwen je omhoog langs de roofline: de eerlijke hardwarewinst zelf steeg van 3.2x naar 6.2x, puur door de werklast groter te maken. Kleine sweeps zitten links van de ridge en profiteren nauwelijks.
- Repareer eerst het algoritme en beklim de CPU-ladder — de GPU-winst komt daarbovenop, niet in de plaats ervan. De 6.2x is gemeten tegen het ineengeklapte algoritme dat je toch al moest schrijven. Schroef een GPU op een naïeve pipeline en het meeste van wat je meet is de naïviteit, niet het silicium.
- Naar de GPU gaan betekent eigenaar zijn van een numeriek pariteitsverhaal. Geen fp64 op Metal, de prefix-sum-WMA-truc sneuvelt op prijsschaal (relatieve fout ~2e2), en strategieën op het teken van een kruising klappen om op grensgeval-bars. Lever een equivalentie-gate mee die de afwijking kwantificeert in basispunten; reken die engineeringkosten mee in je break-even.
Wanneer iemand je vertelt dat de GPU zijn backtest honderd keer sneller maakte, heeft hij je bijna niets verteld. Vraag naar de batchgrootte en de CPU-baseline, en die honderd lost meestal op in een enkelcijferige hardwarewinst, gewikkeld rond een algoritmische winst die hij gratis had kunnen hebben — de moeite waard, op een voldoende brede sweep, om precies de redenen die de roofline geeft en geen enkele reden meer.
Auteurs
Trading-systems engineer
Trading-systems engineer building bots since 2017: cross-exchange arbitrage (connected up to 30 venues), cointegration-based pairs arbitrage across spot and futures, scalping, news and sentiment-driven strategies, trend algorithms, and portfolio management and balancing algorithms. Also builds sub-millisecond order execution, big-data warehouses, backtesting engines, AI agents, and trading interfaces (incl. open-source profitmaker.cc). Stack: JS/TS, Python, Rust/Zig/Go, DevOps, backend, frontend, architecture.