paper trading · us-east-1 · open backtests

atchdip.
Catch the dip. Automatically.

Bot który czeka na paniczne spadki ≥3% na 50 tickerach US. Kupuje limit orderem z us-east-1, sprzedaje przy +5% odbiciu, stop-loss −10%. WebSocket fills · real-time KPI · otwarte backtesty.

  1. 50 tickerów. Scan co 30s.
  2. −3% dip → limit BUY · +5% → SELL.
  3. 690 trade'ów · 71.3% win rate.
  4. +159.75% w 25 m-cy backtest. SPY: +17%.
online · /12 slotów · scan co 30s
dlaczego bot, nie ty

Cztery rzeczy które bot robi lepiej.

Bo nie ma FOMO, nie panikuje przy czerwonych świecach i nie czeka aż "rynek się ustabilizuje".

01 — DANE
Decyzje z backtestu, nie z newslettera.
25 miesięcy walk-forward. Każdy miesiąc out-of-sample — strategia nie widzi danych testowych. SPY: +17%. CatchDip: +159%.
+159.75% / −7.71%
02 — RISK
Trzy circuit breakery, jeden trigger.
Strata −2% dziennie. Drawdown −10% od ATH. Max 12 pozycji. Pierwszy z trzech wyłącza nowe BUY. Otwarte pozycje zamykają się normalnie.
−2% · −10% · 12
03 — SPEED
us-east-1 latency. WebSocket fills.
Bot stoi 70ms od Alpaca matching engine. Limit ordery przy snap_price (low slippage), WebSocket push fills (~50ms vs 1000ms polling). Avg slippage <15 bps.
70ms · <15 bps
04 — TAX
CSV pod PIT-38, bez Excela.
Każdy zamknięty lot przeliczany kursem średnim NBP D-1 (art. 11a ustawy o PIT). FIFO, LIFO albo HIFO — wybierasz raz na rok.
FIFO · LIFO · HIFO
metryki, nie obietnice

Tyle pokazał walk-forward.

Każdy miesiąc out-of-sample — strategia nigdy nie widziała danych testowych. Implementacja w produkcji jest 1:1 z backtestem.

Zwrot łączny
+0%
2.60× kapitału startowego
CAGR roczny
+0%
vs SPY: +17.34%
CAGR miesięczny
+0%
geometryczny, nie aritm.
Max drawdown
0%
od ATH do dołka
Win rate
0%
492 z 690 trade'ów
Risk-adjusted
return ÷ |DD|
vs S&P 500

Indeks: 17%. CatchDip: 159%.

Te same 25 miesięcy. Ten sam start. CatchDip kupuje gdy rynek panikuje — indeks tylko czeka i bywa drogi.

CatchDip bot
+159.75%
rocznie+58.12% drawdown−7.71%
SPY benchmark
+39.53%
rocznie+17.34% strategiabuy & hold
stack

Production-grade, open source.

Docker z healthcheckami. WebSocket realtime fills. SQLite WAL. 155 testów na CI. Pre-commit z secret scanning. us-east-1 deployed. Otwarte backtesty.

Risk manager
Cztery niezależne triggery: daily loss, drawdown, cooldown po stop-lossie, max positions. Każdy z osobna wystarczy by wstrzymać nowe BUY. Open positions zamykają się normalnie.
Walk-forward framework
Cztery warianty alokacji (baseline, dynamic, wider, pyramid) testowane side-by-side na tym samym budżecie. Każdy miesiąc to izolowany test set — bez lookahead bias.
Daily digest
Briefing po close (16:00 ET) generowany przez Claude API: trade'y, P&L, risk events. Bez API key fallback na deterministyczny template — zawsze coś dostajesz.
PIT-38 export
FIFO/LIFO/HIFO matching closed lots. USD→PLN po kursie średnim NBP D-1 (art. 11a ustawy o PIT). CSV gotowy do urzędu — bez ręcznego liczenia kursów na 100 trade'ach.
data flow

Broker → strategia → ty.

Alpaca = source of truth dla pieniędzy. SQLite = tylko historia (trade'y, snapshoty equity, sygnały). Stan rachunku nigdy nie cache'owany — co widzisz, to co masz.

broker
Alpaca
paper api
strategy
B_wider
scan_universe()
risk
Manager
4 limity
history
SQLite
trades · snapshots
ui
Flask + JS
5 zakładek

Zobacz CatchDip live.

Aktualne pozycje · P&L w czasie rzeczywistym · slippage bps per trade · KPI dashboard · otwarte backtesty. WebSocket push, bez polling'u.

Otwórz dashboard →
Disclaimer CatchDip działa na Alpaca paper trading API — symulowane środki, zerowe ryzyko. Past performance ≠ future results. Strategia walidowana na danych 2024–2026, nie ma gwarancji że zadziała przy innym reżimie rynkowym. Trading akcjami wiąże się z realnym ryzykiem utraty kapitału. To nie jest porada inwestycyjna ani podatkowa.
next scan
pozycje
—/12
aktywny
akcje · b_wider · paper ·
$
dziś
Realized P&L
Unrealized P&L
otwarte pozycje
Win rate
Cash
Zainwestowane
Wartość pozycji
Equity w czasie
Kapitał strategii
Seed + cumulative realized P&L · niezależnie od $100k Alpaca paper · · PDT —
P&L per transakcja + linia kumulatywna
Słupki = realized P&L każdej zamkniętej pozycji. Linia = narastający wynik.
Otwarte pozycje (live z Alpaki)
Kliknij nagłówek aby posortować · aktualizacja co 1–3s
Ticker Ilość Entry Kupione Cena Wartość Δ % Unrealized P&L Exit
Brak otwartych pozycji
Ostatni skan
50 tickerów universe · BUY/SELL/STOP/HOLD/WAIT
Ticker Cena Δ dnia Sygnał Skan
Czekam na pierwszy skan...
📊 dziś · fill quality
Trades dziś
BUY + SELL fills
Limit fill rate
marketable limit takeable · cel ≥80%
Avg slippage
|fill − snap| / snap · 10000 · cel ≤8 bps
Transakcje
Kliknij nagłówek aby posortować · ostatnie 50
Czas (UTC) Ticker Akcja Fill Slip Ilość Cena P&L
Bot startuje...
walk-forward · 25 miesięcznych okien · out-of-sample
+159.75%
vs SPY 4.0× SPY +17.34% 25 m-cy out-of-sample
Strategia
B_wider
50 tickerów · max 12 slotów
CAGR roczny
+58.12%
geometryczny
CAGR miesięczny
+3.89%
średnio na miesiąc
Max drawdown
-7.71%
peak-to-trough
Risk-adjusted
20.7×
return / |DD|
Trade'y
— win rate
⚖ Compound vs Flat allocation
Test wpływu reinwestowania zysków: stały $13k vs alokacja rośnie z każdym SELL'em. 38 m-cy, 51 tickerów, 2023-2026.
Metryka FLAT $13k stały COMPOUND dynamiczny Δ
Ładowanie...
Equity curve — flat vs compound
Capital w czasie · widoczny wykładniczy charakter compound vs liniowy flat
Flat Compound
⚡ Crash periods stress test
Test strategii w historycznych korektach — czy +1006% bull marketu to genuine alpha czy tylko sprzyjający rynek?
Period Return CAGR Max DD Win rate vs SPY
Ładowanie...
~ Co dał backtest — kluczowe wnioski 5 lat · 124 tickery

BTD to strategia defensywna. Świetnie radzi w bessach i sideways — wbija dipy które market mean-reverts. W silnym uptrendzie przegrywa z buy & hold (brak dipów). To cecha, nie bug.

Wyniki na 5-letnich danych
+1 293%SEZLBot vs +492% B&H — najlepszy ticker w portfelu.
+1 098%APLDBot vs +612% B&H.
77%Pobicie B&HW 77% testowanych tickerów strategia bije buy & hold.
9/9Bessa 2022W trudnym roku pobiła B&H w każdym z 9 tickerów.
3/10Hossa 2024Przegrała 7/10 — defensywna z natury, nie ma dipów do łapania.
Główne ryzyka
riskBag-holdingKupisz dip, spółka pada na trwałe → zombie pozycja. Stop-loss -10% mityguje, nie eliminuje.
riskNarrownessDziała tylko na zmiennych spółkach. KO/JNJ nie generują wystarczająco dużych dipów.
liveSlippagePaper Alpaca = $0 fee. Real broker doda 0.05-0.2% slippage per trade.
execution quality · last 500 trades
Avg slippage
bps roundtrip · cel ≤25
Limit fills
% trades z limit · cel ≥80%
Max slippage
worst trade · bps
Trades count
z snap_price recorded
Avg slippage bps per dzień
Niebieska = avg, czerwona = max. Spadek po Phase 1 fixes (us-east-1, limit orders, scan 30s).
Per ticker
Średni slippage per akcja — wysokie wartości = często stale snap (volatile stocks)
TickerAvg bpsTrades
Ładowanie...
Recent trades z slippage
Last 100 trades · |fill - snap| / snap × 10000
CzasTickerAkcjaFill SnapFill priceDiff $Slippage bps
Ładowanie...
📈 performance · since inception
Realized P&L
Win rate
Profit factor
Σ wins / Σ |losses| · cel ≥1.5
Avg win / loss
$ per trade
Max drawdown
peak → trough
Best day
Worst day
Trades closed
sells z pnl
🔄 activity · trade flow
Open positions
Closed positions
tickery zamknięte (net_qty=0)
Trade events 7d
last week (rows)
Trade events 30d
last month (rows)
Trade events total
— · partials inflate sells
Trades today
od 00:00 UTC
⚡ execution quality · vs snap_price
Avg slippage
bps · cel ≤25
Limit fill rate
% trades z limit · cel ≥80%
Max slippage
worst trade · bps
Trades z snap
recordowane od v1.9
🛰️ system health · WS + bot uptime
Trading WS
order events
Market WS
price feed
Bot uptime
od ostatniego restartu
Last scan
trigger 30s · event-driven (Phase 2.2)
Last backtest
cron 22:00 UTC daily
lighthouse · frontend audit
Last run
commit · status
Performance
cel ≥85
Accessibility
cel ≥85
Best Practices
cel ≥85
SEO
cel ≥85
Workflow uruchamia się po każdym deploy → audit https://catchdip.com/. Pełny raport: otwórz w GitHub Actions →
Bot control
Start/stop bota. Stop tworzy plik data/KILL_SWITCH — bot pomija scan + nowe BUY. Otwarte pozycje zostają nietknięte (Alpaca trzyma ich stan).
RUNNING
scan co 60s
🟢 Trading mode
Paper / Live + endpoint Alpaca
ModePAPER
Endpoint
Konto
💰 Kapitał strategii
Compound growth: seed + zrealizowany P&L
Seed (wpłata)
Realized P&L
Aktualny kapitał
Alokacja per slot
Slotów aktywnych
📊 Parametry strategii (read-only)
Edycja: config.yaml + docker compose restart bot
strategiaStrategy nameB_wider
strategiaUniverse size
strategiaMax slots
strategiaScan interval60s
strategiaDip threshold (entry)
strategiaBounce target (SELL)
strategiaStop loss
⚠ Risk manager
Circuit breakery — halt nowych BUY przy przekroczeniu limitu
riskDaily loss limit
riskMax drawdown (od ATH)
riskCooldown po stop-lossie
riskMax day trades / 5d (PDT)
liveAktualny status risk
liveDay trades w 5d
📈 Universe — 50 tickerów
Edytuj config.yamlstrategy.ticker_universe
📋 Wersje
Source of truth: CHANGELOG.md w repo
Ładowanie…
pit-38 · auto-konwersja nbp d-1 ·
dochód 19% PIT-38
Zamknięte loty
Przychód PLN
Koszt PLN
Rok
Metoda
⬇ CSV export
Closed lots
— · NBP rate per dziesiętne miejsce
TickerKupnoSprz.Qty Cena kup.Cena sprz. Zysk USD NBP kup.NBP sprz. Koszt PLNPrzychód PLNZysk PLN Dni
01 B_wider — strategia produkcyjna +159.75% / -7.71% DD · walk-forward 25m

Bot kupuje akcje gdy spadną ≥ 3% w jeden dzień, sprzedaje przy +5% odbicia lub -10% stop-loss. Z 50 tickerów dziennie wybiera top dipy — wypełnia max 12 slotów. Każdy slot = ~$1083 z budżetu $13k.

Cykl skanu (co 1 min)
FETCHSnapshoty + pozycje50 snapshots + 1 bulk positions call. Total ~52 calls/min (limit Alpaki: 200/min).
EXITZamknięcia firstKażda otwarta pozycja: ret ≥ +5% → SELL · ret ≤ -10% → STOP-LOSS + 24h cooldown.
ENTRYTop dipy fill slotsTickery bez pozycji & cooldown z day_chg ≤ -3% → sortuj po głębokości → fill puste sloty top kandydatami.
Universe — 50 tickerów (V5 walk-forward)
baseline SEZL APLD ASTS HUT CVNA ANET PANW CRWD crypto RIOT MARA CLSK COIN MSTR ai/data PLTR AI IONQ SOUN SNOW MDB DDOG ev/clean RIVN LCID ENPH PLUG fintech AFRM SOFI HOOD megacap NVDA AMD TSLA META GOOGL AMZN NFLX ORCL ADBE AVGO QCOM cyclical BA DIS JPM BAC F GM consumer UBER ABNB DASH cybersec NET ZS OKTA
Parametry (config.yaml)
dip_threshold: -0.03 # ≤ -3% → BUY candidate bounce_target: +0.05 # ≥ +5% od entry → SELL stop_loss_pct: 0.10 # ≤ -10% → emergency exit max_slots: 12 # max koncurrent (V5 tuning S4) alloc_per_slot: ~$1 083 # $13k / 12
Czemu B wygrywa nad baseline
+61ppDiversyfikacja50 vs 8 nazw — mniejsze idiosyncratic risk, znacznie więcej okazji dziennie.
+8ppTop-dip selectionBierzemy najgłębsze dipy dnia z 50 nazw — nie czekamy aż konkretny ticker dipnie.
×2.6Risk-adjustedreturn/|DD| = 20.7× vs baseline 7.9× — lepszy stosunek zwrot/ryzyko.
02 Walk-forward backtest 25 miesięcznych okien · out-of-sample

Klasyczny backtest cierpi na lookahead bias: optymalizujesz na 5 latach → świetnie → live klapa. Walk-forward eliminuje to — każdy miesiąc testowy nigdy nie był widziany przez strategię w treningu.

Algorytm
01Pobierz 2 lata barsDaily OHLCV z Alpaki dla wszystkich 50 tickerów (V5 expansion).
0225 miesięcznych okienKażdy miesiąc to izolowany test set. Strategia jest deterministyczna — brak fazy treningu.
03Symuluj close-to-closeday_chg ≤ dip → BUY na close. Hold do bounce/SL. Pozycje otwarte na koniec miesiąca: nie liczone (uczciwie).
04Stitch equityCumulative = $13k + suma P&L wszystkich okien. Realistyczna trajektoria którą bot mógłby osiągnąć.
Czego backtest NIE zawiera
missingL1 filterPokazuje raw strategy. Z L1 będzie mniej trade'ów ale lepsza jakość.
missingRisk manager halt-ySymulacja może mieć DD > 10% (w live byłby HALT).
missingSlippage / feeAlpaca paper $0 fee; real broker doda 0.05-0.2% slippage.
caveatSurvivorship biasTestujemy tickery które dziś istnieją; te które padły nie są w portfelu.
Re-run
docker compose run --rm bot python walk_forward.py
03 Daily digest — LLM raport po sesji $0.01-0.03 per dzień

Po zamknięciu sesji NYSE bot raz dziennie wysyła dane do Claude API. Claude pisze 5-7-zdaniowy briefing po polsku — co bot zrobił, kluczowe trade'y, otwarte pozycje, risk events. LLM nie podejmuje decyzji handlowych, tylko podsumowuje.

Pipeline
TRIGGERPo close NYSEBot main loop: clock.is_open == False + brak digestu na dziś → wywołaj. Idempotent (kolejne iteracje skip-ują).
PROMPTSkłada portfolio_snapshots + trades + scan_analysis + bot_status + opis strategii.
APIclaude-sonnet-4-5POST /v1/messages, max 1024 tokens output, anthropic-version 2023-06-01.
PERSISTdaily_digestsPRIMARY KEY = date. 1 digest/dzień. Renderuje się w zakładce Raport.
Bez API key

Brak ANTHROPIC_API_KEYgraceful skip + auto-fallback na template digest (statystyki z DB, deterministyczny, bez kosztu). Bot nie crashuje.

04 L1 Filter — anti-falling-knife RSI · volume · ATR · domyślnie OFF

BTD ma jedną piętę: łapanie spadającego noża. Spadek -5% może być technicznym dipem (mean revert) albo złą wiadomością (dalej -20%). L1 próbuje rozróżnić 3 regułami przed BUY.

3 reguły (wszystkie muszą się zgodzić)
RSI< 35 oversold14-dniowy momentum. <30 = oversold, >70 = overbought. Chcemy oversold.
VOL> 1.3× śr. 20dPanic selling = duże volume. Drift down z małym volumem = no buyers, prawdziwy problem.
ATRdip < 4× ATRJeśli dziś dip jest >4× normalna zmienność = news event, nie naturalna oscylacja.
Status w UI

Filter blokuje BUY → ticker dostaje FILTER w sygnałach. Powód w logach. Następny skan reevaluuje.

Fail-open

Brak historical bars (timeout) → filter przepuszcza BUY + warning. Lepsze niż blokować trade'y przez problemy infra.

05 Risk Manager — kill-switch 4 limity · HALT blokuje BUY tylko

Strategia decyduje co kupić; risk manager — czy w ogóle wolno kupować teraz. To osobna warstwa, bezpiecznik na bug, flash-crash, złą passę. HALT blokuje tylko BUY — otwarte pozycje zamykają się normalnie SELL/SL.

4 limity
-2%Daily losstoday_pnl ≤ -2% startowego equity → HALT do końca dnia. Resetuje się o 00:00 UTC.
-10%Max drawdownequity ≤ 90% all-time-high → HALT. Wymaga ręcznego restartu po fixie.
24hCooldown / tickerPo stop-lossie blokuje BUY tego tickera przez 24h (anti-falling-knife).
8Max otwartychSanity check — bot nie może mieć > 8 pozycji równocześnie.
Przykład HALT
equity start: $100,000 equity now: $97,800 today P&L: -2.20% → HALT: daily loss limit -2% przekroczony → nowe BUY zablokowane do końca dnia → otwarte pozycje wciąż mogą się zamknąć
06 Compound growth — alokacja rośnie z zyskami +1006% backtest vs +263% flat

Bot reinwestuje zyski. Alokacja per slot = (seed + realized P&L) / max_slots. Po każdym SELL'u kapitał strategii rośnie, więc kolejne BUY mają większy sizing — wykładniczy wzrost zamiast liniowego.

Mechanika
Seed (wpłata)Z config.yaml: strategy.total_budget_usd. To kotwica — alokacja nie spadnie poniżej seed/max_slots.
Realized P&LSuma pnl ze wszystkich zamkniętych SELL'i (tabela trades). Tylko zamknięte — unrealized celowo POMINIĘTY (żeby alokacja nie skakała co minutę).
AlokacjaLiczona dynamicznie w current_alloc_per_ticker(). Aktualna wartość widoczna w zakładce Konfiguracja.
Hard capPre-BUY check: jeśli cost_basis + alloc > capital × 1.02 → BUY pominięty. Zapobiega overspendowi gdy stare pozycje używały innego sizingu.
Backtest 38m (zakładka Backtest → Compound)
FLAT $13k stały: +263% $47k DD -5.0% COMPOUND: +1006% $144k DD -15.0% Win rate identyczny: 70.2% w obu

Compound 4× więcej zarabia bo każdy zysk powiększa pozycje. Caveat: max DD podwojony; przy live z compound'em możliwe -25% spadki w bear marketach (vs -5-10% przy flat).

07 PDT rule — Pattern Day Trader Kluczowe dla seed <$25k

FINRA: 4+ day trades w 5 dniach roboczych w margin account z equity <$25k = konto blocked na 90 dni (no day trading). Bot blokuje 4-tym SELL'em zanim się zdarzy.

Co to day trade?

BUY i SELL tej samej akcji w tym samym dniu giełdowym ET. Strategia często to robi — kupuje dip o 9:35, SELL na +5% o 14:22 = day trade.

Bot enforcement
Trackercount_day_trades_last_5d() — liczy unique (ticker, ET-date) z BUY+SELL tego samego dnia w ostatnich 5 dni roboczych.
BlokadaW process_exit: jeśli SELL byłby day-tradem ORAZ count ≥ 3 (próg config risk.max_day_trades_per_5d) → SELL pominięty, "pdt_hold" w sygnałach. Pozycja zamknie się następnego dnia.
WyjątekSTOP-LOSS zawsze przechodzi — capital preservation > PDT compliance. Jeden 90-dni block lepszy niż -10% strata.
CounterAktualny stan widoczny w zakładce Konfiguracja: "Day trades w 5d: X/3". Również jako kolorowy badge pod kapitałem strategii w Akcjach.
Ucieczka

Wpłać $25k+ → margin equity ≥ $25k → unlimited day trades, brak PDT. Lub zostań w cash account (no PDT) ale wtedy T+1 settlement zablokuje 30-50% trade'ów.

08 Margin vs Cash account — który wybrać Margin (bez używania marginu) wygrywa

Bot oczekuje margin account w Alpaca, ALE bez używania marginu (zero leverage). Powód: omijasz T+1 settlement bottleneck który zabiłby strategy.

T+1 settlement (od maja 2024)
Cash accountPo SELL'u cash JEST UNAVAILABLE przez 1 dzień roboczy (FINRA rule). Bot zamyka SOFI o 10:00, do BUY o 14:00 → blocked. Strategia z 31 trade'ami/m stałaby 30-50% czasu.
Margin accountBroker pożycza unsettled funds bezpłatnie. Cash z SELL'u dostępny natychmiast. Bot nie wykonuje margin orders — używa "as if cash" — ale dostaje T+0 effective settlement.
Pułapka: free riding rule (cash only)

W cash account: kupisz akcję A za unsettled funds, sprzedasz przed T+1 → 90-day cash restriction. Margin account omija to całkowicie.

Onboarding Alpaca

Wybierz Margin Account w aplikacji (nawet jeśli nie planujesz używać marginu). Wymaga $2k min equity. Test inwestorski: kilka pytań about derivatives experience.

09 Kill switch — emergency stop 1 klik = bot stoi w <30s

Gdy bot wariuje (bug, anomalia, panic) — natychmiastowy stop bez SSH. Plik flag data/KILL_SWITCH + dashboard endpoint.

Jak użyć
UIDashboardKonfiguracja → 🛑 ZATRZYMAJ BOTA. Confirm dialog. Banner pokazuje stan.
CLISSH/terminaltouch data/KILL_SWITCH (alternatywa gdy dashboard niedostępny).
APIcurlcurl -X POST http://<host>/api/kill i /api/unkill
Co się dzieje gdy active
  • Bot wykrywa flag w pętli (max 30s opóźnienia)
  • Pomija scan + nowe BUY blocked
  • Otwarte pozycje zostają nietknięte — Alpaca trzyma stan
  • Stop-lossy NIE są wykonywane (bo bot nie iteruje) — pozycje mogą rosnąć w stratę bez ochrony
  • Dashboard pokazuje banner "🛑 KILL SWITCH ACTIVE"
Ostrzeżenie

Kill switch wyłącza wszystko, włącznie ze stop-loss protection. Używaj tylko w nagłych przypadkach. Przy długiej pauzie zamknij ręcznie pozycje przez Alpaca dashboard.

10 LIVE_TRADING_ENABLED — przełącznik paper→live Default paper, dwa zamki przed real money

Bot DOMYŚLNIE handluje na paper-api (zero ryzyka). Przełącz na live wymaga 2 niezależnych zmian — jedna pomyłka literówki ≠ katastrofa.

Aby włączyć live
1ALPACA_BASE_URLZmień na https://api.alpaca.markets (bez "paper-")
2ALPACA_KEY / SECRETKlucze z brokerage dashboard (NIE paper)
3LIVE_TRADING_ENABLED=trueExplicite flaga w env. Bez tego bot wymusza paper-api niezależnie od ALPACA_BASE_URL.
Startup banner gdy live
🟢🟢🟢 LIVE MODE — REAL MONEY TRADING 🟢🟢🟢 Endpoint: https://api.alpaca.markets Bot startuje za 5 sekund. Ctrl+C aby przerwać. 5... 4... ...

5-sekundowe opóźnienie + countdown daje czas na "moment, czemu live?" myśl. Plus banner wyraźnie rozróżnialny od paper logu.

11 Sanity checks — pre-BUY safety net 4 warstwy ochrony przed buggy BUY

Przed każdym BUY: 4 niezależne sanity checks. Każdy może zablokować order. Inspirowane Knight Capital (-$440M w 30 min, 2012) — bug w sizing rozsadził firmę.

1Min priceCena ≥ $1.00 (no penny stocks). Penny stocks = większy slippage, częste LULD halts.
2Max qty hard capqty ≤ 10000. Defensywny ceiling — chroni przed bug w alloc/price calc.
3Notional rangeqty × price musi być w [alloc × 0.5, alloc × 1.5]. Łapie zaokrąglenia / rozsynchronizowanie alloc i price.
4Stale snapshotReal-time quote vs snap price max ±2% odchyłka. Jeśli snap stary o >30s, ceny często się rozjeżdżają. Bot odrzuca BUY zamiast kupować "po nieaktualnej cenie".
Trigger rate

W normalnej pracy żaden trigger NIE powinien się odpalać (oznaczałoby bug w computacji). Każde "sanity fail" w logu = sygnał do investigation.

12 DB backup — automatyczne snapshoty SQLite Co 6h · retencja 30 dni

Osobny container backup robi sqlite3 .backup co 6h. Atomic, nie blokuje WAL. Stare backupy >30 dni purge automatycznie.

Lokalizacja
DB livedata/trades.db
Backupydata/backups/trades-YYYYMMDD-HHMM.db
Skryptbackup_cron.sh (alpine container)
Restore
# 1. Lista backupów ./restore_db.sh # 2. Restore wybranego ./restore_db.sh data/backups/trades-20260430-1932.db # Skrypt: stop bot+dashboard → safety copy current → restore → restart
Co backup chroni
  • Trade'y (potrzebne do PIT-38)
  • Snapshoty equity (chart historyczny)
  • Status (ATH, last_scan, risk)
  • Daily digests, scan analysis, cooldowns
Po przejściu na AWS

Container backup zastąpiony przez EBS snapshots via DLM (Data Lifecycle Manager) — zero overhead, snapshot całego volume co 6h, retention auto-managed.

13 PIT-38 — rozliczenie podatkowe (PL) 19% · FIFO/LIFO/HIFO · NBP D-1

Polski rezydent rozlicza zyski kapitałowe z US stocks na PIT-38, stawka 19%. Bot exportuje CSV ready dla urzędu.

Jak liczone
MatchingFIFO (default), LIFO lub HIFO — wybierasz raz na rok. Każdy SELL "jada" lots z BUY w tej kolejności.
Kurs USD→PLNŚredni NBP D-1 (dzień przed transakcją) — zgodne z art. 11a ustawy o PIT.
Edge caseŚwięto NBP (D-1 brak kursu) → fallback na D-2. Implementowane w tax.py.
W-8BEN — 15% withholding zamiast 30%

Złóż w Alpaca przed funding'iem konta live. Bez tego US-IRS bierze 30% z każdej dywidendy (np. NVDA, JPM). Z W-8BEN: 15% (PL-US tax treaty). Ale zysk z SELL'u (capital gain) idzie do PL — zero US withholding.

Net projection po PIT (z 38m backtest)
Gross compound: +1006% $13k → $144k Realistic gross: +850% (po slippage -15%) Net (po 19% PIT): +685% ($13k → $102k) Pesymistic: +243% (z crash + slippage)
14 Production deployment — AWS EC2 + Terraform ~$15/m · Frankfurt · IaC

Target hosting dla live: EC2 t4g.micro (ARM, tani) w eu-central-1 (Frankfurt — najbliżej NYSE w Europie). Terraform module = repeatable, version-controlled.

Stack
networkVPC + public subnetPojedyncza AZ, security group port 22 (SSH) + 443 (HTTPS) z whitelisted IP
computeEC2 t4g.micro2 vCPU ARM, 1GB RAM, ~$6/m. Bot zużywa ~150MB RAM = z naddatkiem.
storageEBS gp3 20GB~$2/m. SQLite + logs + bars cache
backupDLM snapshotsAuto-snapshot every 6h, retention 30d. Zastępuje lokalny backup container.
secretsSSM Parameter StoreALPACA_KEY/SECRET encrypted, IAM role na EC2 daje access. Zero plaintext .env w prod.
domainRoute53 + ACMOptional: custom domain + Let's Encrypt cert dla HTTPS dashboard
logsCloudWatchBot logs ship out z journald

Status w todo: INFRA-1 (Terraform module) + INFRA-2 (userdata bootstrap). Deploy: terraform apply + DNS update.

15 Backtesty — walk-forward, compound, crash periods 3 niezależne testy · zakładka Backtest

Strategia walidowana przez 3 różne podejścia. Wszystkie dostępne w zakładce Backtest jako osobne taby z tabelami i wykresami.

Walk-forward (38m) Każdy miesiąc out-of-sample — strategia nigdy nie widzi danych testowych. 4 warianty alokacji (baseline, A_dynamic, B_wider, C_pyramid). B_wider winner: +159.75%, max DD -7.71%, 71.3% win rate.
Compound vs Flat 38m, 51 tickerów, 2023-2026. Flat $13k stały: +263%. Compound (alloc rośnie z realized): +1006%, te same trade'y, większe pozycje. DD -5% vs -15%.
Crash periods 3 historyczne korekty: GFC 2008 (-12.7%, +27pp vs SPY), COVID 2020 (+21.3% — V-shape recovery!), Bear 2022 (-16.7%, +3pp). Z halt -15% wszystkie ≤-25% DD.
Re-run backtestów
docker compose run --rm bot python walk_forward.py docker compose run --rm bot python compound_backtest.py docker compose run --rm bot python crash_backtest.py

Pierwszy run pobiera bars z Alpaca + Yahoo (cache w data/yf_cache/) — re-runs są szybkie.

16 BUY flow — od scan do filled order 5 etapów · Sanity → Limit → WS fill → Market fallback

Pełna ścieżka od momentu kiedy bot widzi dipa do momentu kiedy ma pozycję. Każdy etap ma fallbacki — bot stara się minimalizować slippage, ale nigdy nie blokuje się jeśli któryś krok zawiedzie.

Diagram
flowchart TD S(["SCAN co 30s"]) --> K{"day_chg ≤ -3%?"} K -->|NIE| W["wait next scan"] K -->|TAK| PS["1. _parse_snapshot"] PS --> WS{"WS buffer fresh?
(top 30, age <30s)"} WS -->|TAK| MID["mid quote z WS
(in-memory)"] WS -->|NIE| RQ{"REST latestQuote
fresh <60s?"} RQ -->|TAK| MID2["mid = (bid+ask) / 2"] RQ -->|NIE| RT{"latestTrade
fresh <60s?"} RT -->|TAK| TP["trade.p
(bid/ask=null)"] RT -->|NIE| SKIP(["SKIP ticker
NIE kupujemy
na stale data"]) MID --> SC["2. sanity_check_buy"] MID2 --> SC TP --> SC SC --> SC1{"price ≥ $1.50
qty ≤ cap
notional 50-150%
snap vs ask ≤ 2%"} SC1 -->|NIE| BLOCK(["BUY pominięty"]) SC1 -->|TAK| POS["3. place_order_smart"] POS --> TWS{"Trading WS
connected?"} TWS -->|NIE| MKT["market order
direct"] TWS -->|TAK| LIM{"bid + ask
dostępne?"} LIM -->|TAK| ML["marketable limit
BUY: ask × 1.0005
SELL: bid × 0.9995"] LIM -->|NIE| SL["limit @ snap_price
(legacy ~0% fill)"] ML --> WAIT["4. wait_for_fill
(timeout 10s)"] SL --> WAIT WAIT --> RES{"Result?"} RES -->|FILL| OK(["fill_method = limit ✓
target ≤5 bps"]) RES -->|PARTIAL| PART(["cancel rest
partial filled"]) RES -->|TIMEOUT| FB["5. Fallback:
cancel + market"] MKT --> FBOK FB --> FBOK(["fill_method = market
typowy 10-25 bps"]) classDef good fill:#0a2b1e,stroke:#34d399,color:#6ee7b7 classDef warn fill:#2b1f0a,stroke:#fbbf24,color:#fcd34d classDef bad fill:#2b0e14,stroke:#fb7185,color:#fda4af classDef skip fill:#161b22,stroke:#8b949e,color:#8b949e class OK,MID,MID2 good class PART,FBOK warn class SKIP,BLOCK skip
Co może pójść źle
staleStale IEX quoteIEX free plan ma ~3% wolumenu — niektóre tickery mają quote sprzed minut. Quote freshness gate (60s) eliminuje BUY na takie tickery (return None w _parse_snapshot).
2%Sanity stale snapJeśli snap_price odchyla >2% od fresh quote.ask → BUY pominięty. Chroni przed kupowaniem na anomalii cenowej.
10sLimit timeoutPaper Alpaca symulator nie zawsze fillnie marketable limit (zwłaszcza poza top 30 IEX). Po 10s bot fallbackuje do market — payup ~15-25 bps zamiast 5.
PDTDay-trade trackerPo 3 day-tradach w 5d bot blokuje SELL (nie BUY). Profit-taking przesuwany na jutro — pozycja zostaje otwarta.
Live monitoring

W Historia tab widać per-trade: fill_method badge (LIMIT zielony / MARKET neutral) + Slip column (signed bps). W Slippage tab — agregaty (avg/max/limit fill rate). Cele: limit fill rate ≥80% + avg slippage ≤8 bps.

17 Slippage diagnostics — czerwone flagi i fixes Per-trade signed bps · stale data detection

Slippage = różnica między ceną którą bot widział (snap_price) a faktycznym fill. Signed bps cost-positive: BUY paid more = positive (cost), SELL got less = positive (cost). Negative = lucky.

Formuła
BUY: bps = (fill - snap) / snap × 10000 SELL: bps = (snap - fill) / snap × 10000 +5 bps = expected (marketable limit + 5 bps offset) +15-25 = market order na liquid stock >25 = stale snap suspect (REST IEX zwrócił old quote) negative = lucky (cena drift'owała na nasz favor)
Czerwone flagi
>100 bpsStale snapBot widzi cenę X ale faktyczny rynek to Y. Najczęściej: ticker poza top 30 WS, REST IEX zwraca stale quote. Fix: quote freshness gate (60s) odrzuca takie ticker w scan.
0% limitMarketable nie fillniePaper Alpaca symulator może nie fillnąć limit nawet gdy limit_price > ask. Realny test = live trading. Tymczasem bot fallbackuje do market.
avg >15Drift długoterminowyŚrednia z 7 dni rośnie → infrastructure issue (latency, WS down). Sprawdź /api/admin/alpaca_pool bench + WS uptime.
2026-05-04 BA case (real example)
snap_price: $218.05 (stale REST IEX latestQuote) fill_price: $221.97 (market fallback) diff: +$3.92 → +180 bps slippage limit_price było 222.08 (marketable na fresh quote 221.97 × 1.0005), ale paper Alpaca nie fillnał limit → 10s timeout → market fallback. Fix v1.24: quote freshness gate w _parse_snapshot. Jeśli latestQuote.t >60s → return None → bot pomija ticker. Lepsze 0 trade'ów niż +180 bps slippage.
Live widgets

Historia hero panel (DZIŚ): trades count, limit fill rate %, avg slippage bps.
Slippage tab: per-trade table z signed bps + per-ticker avg + daily chart.
Konfiguracja: WS health widgets (Trading + Market) z heartbeat 5s.

18 Architektura — co siedzi pod spodem Bot + Dashboard + Backup w docker

Trzy osobne kontenery dockerowe + jedno volume. Każdy ma minimal scope, można restart niezależnie.

bot paper_bot.py 24/7 loop, scan co 60s, składa orders przez Alpaca REST. Healthcheck: ostatni scan <180s temu.
dashboard dashboard.py Flask, port 5050. Endpointy: /api/account, /api/positions, /api/trades, /api/snapshots, /api/walkforward, /api/compound_backtest, /api/crash_backtest, /api/config, /api/kill, /api/stream (SSE realtime push).
backup alpine + cron SQLite .backup co 6h, retention 30d w data/backups/
Storage
SQLite (WAL)trades, portfolio_snapshots, scan_analysis, daily_digests, cooldowns, bot_status, tax_lots
JSON cachewalkforward.json, compound_backtest.json, crash_backtest.json, bars_cache_ideas.json, yf_cache/
Logsdata/bot.log (rotated 10MB × 5)
External
  • Alpaca REST API — orders, positions, account, market data (paper-api lub api)
  • NBP API — kursy USD/PLN dla PIT-38 export
  • Anthropic API — daily digest LLM (opcjonalne, fallback na deterministic template)
  • Yahoo Finance v8 — historical bars dla crash backtest 2008/2020