docs(kevin): trade-execution fix plan + go-live
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Documents the six fixes (Phases 1-4), the new-only cold-start, the migration handling + pipeline migration-gap, and go-live verification. This commit (no ci skip) triggers the CI build + rollout of the Kevin trading fix. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
82dc622544
commit
44132e9961
1 changed files with 80 additions and 0 deletions
80
docs/plans/2026-06-04-meet-kevin-trade-execution-fix.md
Normal file
80
docs/plans/2026-06-04-meet-kevin-trade-execution-fix.md
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
# Meet Kevin — trade-execution fix (go-live)
|
||||
|
||||
**Date:** 2026-06-04
|
||||
**Status:** shipped (Phases 1–4 merged) → deploying
|
||||
|
||||
## Problem
|
||||
|
||||
Phase 2 "paper trading" was enabled (`TRADING_KEVIN_ENABLE_TRADING=true`) but
|
||||
was a **silent no-op**: 89 Kevin signals reached the executor and every one was
|
||||
dropped at sizing. The Alpaca paper account had **zero** Kevin orders (all 50
|
||||
historical fills were `SIMPLE` orders from the normal signal-generator path).
|
||||
Six distinct breaks sat between "infer a call from Kevin's video" and "place +
|
||||
record + manage the trade":
|
||||
|
||||
1. Sizing read price only from `sentiment_context["current_price"]` (None for
|
||||
Kevin) → `qty=0` → skip.
|
||||
2. The bridge never put a price on the signal.
|
||||
3. Orders were always built `SIMPLE` — `stop_loss_pct`/`take_profit_pct` were
|
||||
ignored, so no brackets.
|
||||
4. Persisted trades hardcoded `strategy_id=None` → unattributed, invisible to
|
||||
the dashboard (which filters by Kevin's strategy id).
|
||||
5. The exit scanner was orphaned — the cron was wired nowhere, so exits never
|
||||
ran; it also lacked an "already-closed" guard.
|
||||
6. No realized P&L was ever written, and bracket stop/take-profit legs that
|
||||
fill at Alpaca never passed through our executor (close + P&L invisible).
|
||||
|
||||
## Fixes (TDD throughout)
|
||||
|
||||
| Phase | Change | Commit |
|
||||
|---|---|---|
|
||||
| 1 | `TradeSignal.current_price`; bridge sets it; sizing honors `target_dollars` and resolves price; BRACKET orders for LONG entries (EXIT stays SIMPLE) | `14407d3` |
|
||||
| 2 | Persist `strategy_id` on the trade row + execution (record + dashboard) | `a8b0d33` |
|
||||
| 3 | EXIT closes the full held position; realized P&L on close; wire exit scanner on its cron + offsetting-SELL guard | `52b3c76` |
|
||||
| 4 | `broker.get_order(nested)` + `list_orders`; `Trade.broker_order_id` (migration `f6a7b8c9d0e1`); reconcile loop books bracket auto-closes + P&L idempotently, syncs status, logs drift | `82dc622` |
|
||||
|
||||
Design decision: sizing **honors the bridge's `target_dollars`** rather than
|
||||
re-deriving in the executor — the bridge already applied conviction-weighting
|
||||
and per-ticker headroom the executor can't see. Brackets trigger on entry
|
||||
direction + presence of both pct legs (the bridge stamps pcts even on exits, so
|
||||
the LONG guard is what keeps exits simple).
|
||||
|
||||
## Cold start — "new signals only"
|
||||
|
||||
The live state was already new-only at go-live: bridge cursor
|
||||
`kevin:bridge:last_mention_id = 316` (= MAX mention id, all prior mentions
|
||||
behind the cursor) and `kevin:deferred_signals` empty. No flush required. The
|
||||
stale "deferred" backlog seen in `kevin_signal_bridge_state` was audit-row
|
||||
*classification*, not queued orders.
|
||||
|
||||
## Migration handling + a latent gap
|
||||
|
||||
The Woodpecker pipeline (`.woodpecker.yml`) has **no migration step** — it runs
|
||||
tests → builds images → patches the deployment. Migrations only run via the
|
||||
TF-managed `trading-bot-migrations` Job (`alembic upgrade head`), which the
|
||||
deploy does not trigger. Migration `f6a7b8c9d0e1` (nullable
|
||||
`trades.broker_order_id` + index) is backward-compatible, so it was applied to
|
||||
the live DB ahead of the rollout (atomic DDL + version stamp); the TF Job will
|
||||
no-op on its next run.
|
||||
|
||||
**Recommendation (separate, not done here):** add a `run-migrations` step to the
|
||||
pipeline (a K8s Job on the freshly-built image, before `update-deployment`) so
|
||||
future migrations apply automatically on deploy. Requires `create jobs` RBAC for
|
||||
the Woodpecker service account (infra repo).
|
||||
|
||||
## Guardrails (live)
|
||||
|
||||
Per-strategy caps already enforced in the risk manager: 10 trades/day, $20k/day
|
||||
allocation, 20% equity-drawdown halt (permanent pause), 5% daily-loss circuit
|
||||
(24h pause). Paper account only (`TRADING_PAPER_TRADING=true`).
|
||||
|
||||
## Go-live verification
|
||||
|
||||
1. CI green; `trading-bot-workers` 6/6 on the new image.
|
||||
2. Next Kevin video → mention (id > 316) → bridge emits → executor places a
|
||||
**BRACKET** paper order → `trades` row with `strategy_id = kevin` →
|
||||
dashboard `/meet-kevin/strategy` shows it; `#trading-bot` Slack posts the
|
||||
entry. (First real trade occurs on Kevin's next upload — the watcher polls
|
||||
every ~3h.)
|
||||
3. On a stop/take-profit fill, the reconcile loop books the closing SELL +
|
||||
realized P&L.
|
||||
Loading…
Add table
Add a link
Reference in a new issue