← Tilbage til The Vault
PoC⚡ AI-augmentedlive

Alpaca Trading System

Automatiseret aktiehandel med tre komplementære strategier — Momentum Trend, Mean Reversion og Pair Trading — kørende som systemd-services. Live deployment med Telegram-notifikationer, earnings-filter og Flask web UI.

#python#alpaca-api#systemd#algo-trading
Alpaca Trading System

Selvinitieret, selvhostet projekt. Bygget og drevet af mig som arkitekt, produktejer og reviewer, med AI som superviseret "lærling" i udviklingsprocessen.

DEL 1: Resume

Alpaca Trading System kører tre komplementære, fuldautomatiske aktiestrategier — Momentum Trend, Mean Reversion og markedsneutral Pair Trading — som selvstændige systemd-services mod én fælles Alpaca-konto. En fjerde service, aux_service, håndterer det strategierne ikke selv gør: en earnings-risikokalender, en ikke-blokerende Telegram/email-notifikationskanal, morgen- og EOD-rapporter samt et Flask web-dashboard med live ticker og markedsklokke. De tre strategier taler aldrig direkte sammen — de koordinerer risiko udelukkende via et fælles eksponeringstjek mod Alpacas egne live positioner.

Teknologistak

  • Kerne: Python 3.11+, uv pakkehåndtering, alpaca-py SDK mod Alpacas paper/live trading- og markedsdata-API'er
  • Kvant: statsmodels (OLS-regression, Engle-Granger ADF-cointegrationstest), egen RSI/SMA/ATR-signallogik, Ornstein-Uhlenbeck half-life-estimering af mean-reversion-hastighed for par
  • Data: yfinance til earnings-kalenderen, Alpacas screener-API (get_most_actives) til det ugentlige pair-trading-univers
  • Web: Flask-dashboard (aux_service/webserver.py) — live prisstrimmel, nedtælling til marked åbent/lukket, /morning- og /eod-rapportvisninger, JSON-API
  • Notifikationer: Telegram Bot API og SMTP-email, fodret af en ikke-blokerende JSONL-kø (notifications_queue.jsonl)
  • Drift: fire systemd-services (momentum_trend, mean_reversion, pair_trading, aux_service), eksplicit dev/prod-opdeling (trading/ vs. trading-prod/), deploy.sh (commit → push → pull i prod → restart), Gitea til versionsstyring

Største resultat At få tre uafhængigt skemalagte, uafhængigt genstartbare services til sikkert at dele én brokerage-konto uden fælles database og næsten uden fælles state. Hver service behandler Alpaca selv som eneste sandhedskilde: ved genstart rekonstruerer en strategi sin position ud fra brokerens faktiske beholdning i stedet for blindt at stole på sin egen state.json, og en uoverensstemmelse mellem de to (en position filen husker, som Alpaca ikke længere viser) udløser en eksplicit POSITION_FORSVUNDET-alarm frem for stiltiende afdrift. Det fælles porteføljeeksponeringsloft virker på samme måde — shared/exposure.py spørger Alpaca om samtlige åbne positioner på tværs af alle tre strategier og summerer live, i stedet for at hver strategi holder styr på et tal der kunne komme ud af sync. Resultatet er et system hvor enhver enkelt service kan crashe og komme tilbage uden at ødelægge state eller dobbelthandle — den egenskab der faktisk tæller, når systemet skal køre uden opsyn.

DEL 2 — Dybdegående baggrund

Problemet

Algoritmisk handel for privatpersoner fejler som regel på kedelige, driftsmæssige måder frem for på et dårligt signal: én strategi overfittet til den markedsregime den blev bygget i; ingen koordinering når mere end én bot handler på samme konto, så intet forhindrer den samlede risiko i at overstige hvad én strategi alene ville tillade; en crashet proces der genstarter med forældet eller duplikeret positionsstate; stilhed når noget går galt midt om natten fordi ingen overvåger en terminal; og positioner der sprænges af et earnings-gap fordi intet tjekkede kalenderen før entry. Ingen af disse er modelleringsproblemer — det er det driftsmæssige fundament der afgør om en strategi der virker på papiret, overlever mødet med en rigtig konto der kører uden opsyn.

Arkitektur og løsning

Overordnet form. Fire uafhængige systemd-services deler én Alpaca-konto og ét filsystem — ingen database, ingen RPC mellem services:

KomponentRolle
momentum_trendScheduler + signallogik + ugentlig watchlist-screening til trendstrategien
mean_reversionScheduler + signallogik + daglig post-close-screening til oversold-pullback-strategien
pair_tradingScheduler + signallogik + ugentlig cointegrationsscreening til den markedsneutrale strategi
aux_serviceEarnings-cache-opdatering, notifikationsafsendelse, morgen-/EOD-rapporter, Flask web-dashboard
shared/alpaca_client (retry/backoff-wrapper), exposure (fælles risikoloft), logger, live_status-skriver, notify (eventkø)

Dataflow. Hver strategi screener sit eget univers i sit eget tempo (ugentligt for Momentum og Pair Trading, dagligt efter lukning for Mean Reversion) → evaluerer et morgensignal kl. 09:00–09:29 ET ud fra foregående dags daglige bars → placerer en bracket limit-ordre kl. 09:45 ET hvis signalet rammer tærsklen → tjekker trailing stops, exit-betingelser og ordrefyldning hvert 15. minut → skriver til live_status.md. Sideløbende opdaterer aux_service earnings-cachen dagligt kl. 07:00 ET, tømmer den fælles notifikationskø hvert 5. minut og genererer morgenrapporten kl. 09:35 og EOD-rapporten kl. 16:30.

Designbeslutninger værd at fremhæve

  • Brokeren er databasen. Ingen strategi stoler blindt på sin egen state.json ved opstart — den afstemmes først mod Alpacas faktiske åbne positioner. Denne ene beslutning er det der gør genstarter uden opsyn sikre.
  • Statsløs koordinering på tværs af strategier. De tre strategier deler risikoeksponering uden at tale sammen eller dele en database — shared/exposure.py summerer simpelthen abs(market_value) på tværs af alle åbne positioner på kontoen (short-positioner tæller fuldt ud) og afviser nye entries over det konfigurerede loft. Ét live-opslag, ingen synkroniseringsfejl at have.
  • Volatilitetsskalerede stops frem for faste. Mean Reversions stop-afstand er clamp(1,5 × ATR(14)%, 2%, 6%) i stedet for en fast procentsats — et fast stop rammes af almindelig støj når VIX er forhøjet; ATR-gulvet og -loftet holder risikoen begrænset, mens stoppet kan ånde med den reelle volatilitet.
  • Cointegration, ikke korrelation, til parudvælgelse. To aktier kan bevæge sig ens i det uendelige uden at spreaden mellem dem nogensinde vender tilbage. Par-screeneren kræver både en Pearson-korrelation over 0,80 og en statistisk signifikant Engle-Granger ADF-test, estimerer dernæst en Ornstein-Uhlenbeck half-life og beholder kun par der vender tilbage på 5–30 dage — hurtigt nok til at være handlingsbart, ikke blot statistisk "ægte."
  • Notifikationer kan aldrig blokere en handelsbeslutning. Strategierne appender events til en JSONL-kø (notify.push_event) i stedet for at kalde Telegram eller SMTP direkte fra handelsloopet. aux_service tømmer køen hvert 5. minut; alt der fejler ved afsendelse forbliver i køen til næste forsøg, så et ustabilt netværkskald forsinker en notifikation — aldrig en exit-beslutning — og intet går stille tabt.
  • Bracket-ordrer med kontrolleret limit, ikke market-ordre. Entry, stop-loss og en bevidst uopnåelig take-profit sendes som én atomisk bracket-ordre, med entry-limitten sat 0,1% over ask — en lille, afgrænset slippage-omkostning mod til gengæld priskontrol. En ordre der stadig ikke er fyldt efter 10 minutter annulleres automatisk frem for at blive jagtet.
  • Deploy er et bevidst, separat trin. Services kører altid fra trading-prod/, aldrig fra arbejdstræet; deploy.sh committer, pusher, puller i prod og printer restart-kommandoen. Kodeændringer kan aldrig ved et uheld røre en live position.
  • Kill-switchen antager at serveren selv kan være det der er nede. close_all.py lukker alt interaktivt eller med --force, men live_status.md indeholder også en manuel, scriptfri lukke-guide i klartekst — præcis handling pr. åben position — til det tilfælde hvor intet kan køres overhovedet.

AI som lærling

AI inde i produktet. Ingen, bevidst. Hvert signal — RSI, SMA/EMA-kryds, ATR-baserede stops, OLS/ADF-cointegration, OU half-life — er en deterministisk statistisk regel. Når et system placerer rigtige brokerage-ordrer uden opsyn, skal hver entry og exit kunne forklares i bagklogskab uden at henvise til en models interne tilstand.

AI i udviklingsprocessen (human-in-the-loop).

  • Hver strategi har et dedikeret specifikationsdokument (Strategi.md, Mean-Reversion-Strategi.md, Pair-Trading-Strategi.md), skrevet og versioneret før den tilhørende signalkode — specifikationen er det implementeringen tjekkes imod, ikke omvendt.
  • En projekt-hukommelsesfil (CLAUDE.md) registrerer hvor live status, logs, state og watchlists reelt bor på tværs af dev/prod-opdelingen, så en AI-kodeassistent — og den næste session — ikke skal genudlede driftskontekst fra bunden.
  • Jeg reviewer enhver ændring der rører ordreplacering, positionsstørrelse eller eksponeringsloftet før den deployes; deploy.sh er en manuel, eksplicit port mellem arbejdstræet og den konto der reelt kan placere ordrer.
  • En fjerde strategi — Congress Trading, der handler på STOCK Act-disclosures med en komité-medlemskabsvægtning — har en skreven implementeringsplan (congress_plan.md) udarbejdet med AI-assistance, men er bevidst endnu ikke bygget; den er næste skridt, ikke leveret.

Lederens refleksion

  1. Lad statistikken afgøre hvad der er ægte, ikke hvad der ser godt ud på en graf. Pair trading gates af en cointegrationstest og en mean-reversion half-life, ikke "disse to aktier ser ud til at bevæge sig sammen" — forskellen mellem en reel edge og en korrelation der stille holder op med at virke.
  2. Design til det øjeblik to sandhedskilder er uenige, ikke kun til det øjeblik de er enige. At afstemme lokal state mod brokerens faktiske positioner ved hver genstart, med en eksplicit alarm når de afviger, er det der gør drift uden opsyn til at overleve.
  3. Afkobl det loop der aldrig må gå i stå fra det der godt må fejle. En ikke-blokerende notifikationskø betyder at et langsomt Telegram-API-kald kan forsinke en alarm; det kan aldrig forsinke en exit-beslutning.
  4. Skriv specifikationen før strategien går live, og bliv ved med at tjekke imod den. Tre versionerede strategidokumenter findes specifikt så en implementering kan reviewes mod et fast mål i stedet for mod sig selv.
  5. En kill-switch er kun så god som sit værste scenarie. Den scriptede version var de nemme 80%; at skrive en manuel, server-nede lukke-guide ind i live status-filen var den del der faktisk tæller når behovet opstår.
  6. Lever den mindste statistisk ærlige version først. Tre bevidst ukorrelerede strategier der har kørt i to uger er et mere forsvarligt udgangspunkt for kapitalallokering end én "bedste" strategi der er backtestet til den ser godt ud.

Ærlige begrænsninger. Dette er et to uger gammelt system (bygget 12.–26. april 2026, 63 commits) der p.t. kører mod Alpacas paper trading-API — skiftet til rigtig kapital er én konfigurationslinje, og det driftsmæssige fundament (state-afstemning, eksponeringskoordinering, notifikationer, kill-switchen) blev bevidst valideret først, før det skifte. Der findes endnu ikke et backtest-framework, så strategitærskler er ræsonneret frem for historisk validerede — det er det første punkt på projektets egen liste over fremtidige forbedringer. Positionsstørrelse er fast procentsats, ikke volatilitets- eller Kelly-justeret. En fjerde strategi er planlagt, men ikke implementeret. I en virksomhedssammenhæng ville backtesting gå forud for enhver strategi der går live med rigtig kapital; her var det bevidste valg at bevise garantierne for drift uden opsyn først, med paper trading som sikkerhedsnet mens det sker.