Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- **The canary runs against real venues (closes the `canary-roundtrip`
epic).** Venue identity oracle: our store-recorded fills == the
venue-reported fills (exact triples), per-asset balance deltas == the
fills math (fee-denomination-aware — never an assumed zero), position
flat, cost bounded. `--mode testnet` wired (Binance sandbox; Kraken
refused — no testnet); `--mode live` implemented behind the existing
`live_enabled` gate + typed confirmation. `doc/dev/09-go-live.md` names
the live canary as the road-to-1.0 #1 validation vehicle. **Proven on
real Binance testnet: 16/16 checks PASS** (real cancel accepted, venue
dedup on the duplicate client-order-id, cost 0.00511 USDT). (#223)
- **`Fill.fee_asset`** — the venue may charge the fee in a non-quote asset
(Binance charges a market buy's commission in BASE; found by the canary's
identity oracle refusing an unexplained −8E-8 BTC). Additive domain field
(`None` = quote, the historic meaning), threaded through the Binance
adapter, the store (migration) and the oracle's fills math. Also:
Binance HTTP-400 `{code,msg}` rejections now map to the same domain
errors as in-band bodies (they escaped as transport errors);
`BrokerConfig.symbols` threads the per-symbol trade-history scope Binance
requires for `fills()`. (#223)
- **`trading-bot canary`** — the platform self-test as a one-liner (paper by
default): self-contained factory engine funded with `--budget`, quantity
sized to the venue's REAL minimums (public-endpoint resolver + real last
Expand Down
22 changes: 22 additions & 0 deletions doc/dev/03-decisions.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,28 @@ rejected approaches as tombstones.

---

### 2026-07-12 The venue oracle is an accounting identity; fees carry their asset (PR #223) [accepted]
- **Choice**: on a real venue the canary asserts the **identity** — our
fills == venue-reported fills, per-asset balance deltas == the fills math
— never a precomputed PnL. Supporting discoveries hardened the domain:
`Fill.fee_asset` (Binance charges market-buy commissions in BASE; `None`
keeps the historic quote meaning), Binance HTTP-400 rejections mapped to
domain errors, `BrokerConfig.symbols` for Binance's per-symbol
trade-history scope, and a 15 % venue probe offset (the venue's
`PERCENT_PRICE_BY_SIDE` band rejects 50 %). A settle loop attributes the
venue's async executions to our client-order-ids and re-emits them on the
bus — venue truth flows through the ordinary fill plumbing.
- **Why**: spread and drift make absolute expectations flaky exactly where
correctness matters most; the identity is exact on any venue. The two
intermediate testnet failures WERE the canary working — each became a
domain fix with offline tests before the final green run. PnL folding
still values fees at face value when fee_asset ≠ quote (documented
approximation); the balance identity itself is exact.
- **Rejected alternatives**: absolute-PnL venue oracle (nondeterministic);
ignoring sub-1e-7 balance dust (it was the fee-denomination bug, not
dust); adapter-specific oracle code (the broker port suffices — the
oracle stays venue-agnostic).

### 2026-07-12 The canary: two oracles, probes before money, resting limits in strict paper (PR #221) [accepted]
- **Choice**: the canary runs probes FIRST (cancel, idempotency — both free)
and only then risks the round-trip; paper mode gets an EXACT oracle
Expand Down
16 changes: 14 additions & 2 deletions doc/dev/06-status.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,20 @@ and `CHANGELOG.md` for what shipped.
value / unrealised, shared expandable rows (SSE-surviving), orders with
Filled % / Avg fill / Value and click-to-expand fills (standalone fills
table retired from the detail page), honest last→next bar chip, timezone
label, filtered-vs-empty states. Known follow-up: `reject_reason` is
rendered defensively but not yet serialized by `/api/orders`.
label, filtered-vs-empty states. The `reject_reason` follow-up shipped in
#219.
- **`canary-roundtrip` (2026-07-12, PRs #221–#223)**: `trading-bot canary` —
the deterministic platform self-test. Probes first (real cancel = the
kill-switch path; duplicate client-order-id = the idempotency invariant),
then a sequential round-trip; paper oracle exact (PnL == −Σ fees, flat,
persisted terminals, balance deltas), venue oracle = the accounting
identity (fills/balances == venue-reported, fee-denomination-aware) +
bounded cost. **Proven on real Binance testnet: 16/16 PASS** (cost
0.00511 USDT). Two real findings hardened the domain: `Fill.fee_asset`
(Binance charges market-buy fees in BASE) and Binance HTTP-400 → domain
error mapping. `09-go-live.md` names the live canary as the road-to-1.0
#1 validation vehicle. Cadence: paper per release, testnet at will, live
once per venue at go-live.

## Pending

Expand Down
60 changes: 25 additions & 35 deletions doc/dev/07-roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,43 +97,33 @@ order — none started yet, each is a `/pick-task` candidate:
200-row tail + server timestamps on snapshot endpoints, then the public
contract is declared stable — 0.x-style breaking removals end at 1.0.

## UI consistency & accounting integrity (2026-07-11)
## UI consistency & accounting integrity (2026-07-11) — SHIPPED

A dashboard-driven audit (position ≠ Σ orders on `alloc1-binance`) uncovered two
engine bugs and a set of API/UX gaps. Shipped 2026-07-11 (plan trees archived):
the `paper-integrity` epic (durable paper ids, order↔fill lifecycle sync,
persisted orphan-closes — PRs #190–#194, v0.12.0; restart the daemon to heal
the live books), the `accounting-guardrail` epic (pure invariant checker,
startup + 60 s-TTL wiring with SSE alerts, per-strategy health surface incl.
the kill-switch fold — the *status* half of road-to-1.0 #3 — and the dashboard
health pill; PRs #197–#201), and the `venue-minimums` epic (keyless cached
spec resolver, round-up-or-skip order prep with the spot-sell cap, strict
factory paper — PRs #203–#206; sub-minimum orders are structurally
impossible on the portfolio path), and the `api-completeness` epic (per-engine
mark cache + bar-close marks with mandatory as-of on position rows, value /
unrealised / `fee_ccy`, server-side display currency, `GET /api/balances`,
`last_asof_ts` surfaced, additive-only contract sweep — PRs #209–#214; ready
for the road-to-1.0 #5 freeze), and the `dashboard-tables-ux` epic
(asset-first positions with as-of marks + shared expandable rows, orders with
Filled %/Value + fills as expanded detail, the honest last→next bar chip,
timezone label + filtered empty states — PRs #215–#218). In dependency order:

6. [ ] **`canary-roundtrip` — deterministic self-test strategy.** A minimal
round-trip canary run as its own strategy unit with its own tiny ledger
(~10 USDT): **sequential** market buy *x* then sell *x* (never simultaneous
— self-trade prevention would make it non-deterministic), plus two free
probes: a far-off-limit **cancel** (the real kill-switch path) and a
client-order-id **idempotency** re-submit. Two oracles: **paper** = exact
Decimal equality against the precomputed expectation (PnL = −2·fees, flat
position, terminal orders — CI-runnable, per release); **live** = exact
*accounting identity* (our fills/balances == venue-reported) + **bounded**
total cost (≤ 1–2 EUR) — never a precomputed absolute PnL (spread/drift are
not deterministic). Cadence: paper per release; testnet at will; live once
per venue at go-live + after any broker-adapter change. Depends on #1
(order lifecycle) + #2 (health surface for the pass/fail report) + #3
(venue minimums; Binance BNB-fee discount must be off or modeled). The live
canary is the validation vehicle for **Road to v1.0.0 #1** (real-key
enablement: venue idempotency, real cancel, balance reconciliation).
engine bugs and a set of API/UX gaps. **All six epics shipped 2026-07-11/12**
(plan trees archived; details in `06-status.md`, decisions in `03-decisions.md`):

1. `paper-integrity` — durable paper ids, order↔fill lifecycle sync, persisted
orphan-closes (PRs #190–#194, **v0.12.0**).
2. `accounting-guardrail` — pure invariant checker, startup + 60 s-TTL wiring
with SSE alerts, per-strategy health surface incl. the kill-switch fold
(the *status* half of road-to-1.0 #3), dashboard health pill
(PRs #197–#201, **v0.13.0**).
3. `venue-minimums` — keyless cached spec resolver, round-up-or-skip order
prep with the spot-sell cap, strict factory paper (PRs #203–#206,
**v0.13.0**).
4. `api-completeness` — mark cache + bar-close marks with mandatory as-of,
value/unrealised/`fee_ccy` per position, server-side display currency,
`GET /api/balances`, additive-only contract sweep — ready for the
road-to-1.0 #5 freeze (PRs #209–#214).
5. `dashboard-tables-ux` — asset-first positions, shared expandable rows,
orders with Filled %/Value + fills as expanded detail, honest bar-timing
chip, timezone + filtered empty states (PRs #215–#219).
6. `canary-roundtrip` — deterministic self-test (`trading-bot canary`):
probes + round-trip, exact paper oracle, venue identity oracle **proven
16/16 on real Binance testnet**; the live canary is the named validation
vehicle for road-to-1.0 #1 (PRs #221–#223; found `Fill.fee_asset` and the
Binance HTTP-400 mapping along the way).

## Not gating 1.0 (post-1.0 candidates)

Expand Down
48 changes: 42 additions & 6 deletions doc/dev/09-go-live.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,12 @@ missing any one refuses with a non-zero exit / a raised
ruff check trading_bot/
mypy trading_bot/
```
5. **Validate against a real-key sandbox.** This is the one remaining
prerequisite and is **not done in-repo**: before any real order, exercise the
private endpoints (AddOrder / OpenOrders / balances / fills) against a real
Kraken key (ideally a low-limit, throwaway key) and confirm the
venue-reported state matches what the engine requested — see *Proven vs
pending*. **Do not skip this.**
5. **Validate against the real venue — run the canary.** Before any real order,
exercise the private endpoints (place / cancel / open orders / balances /
fills) and confirm the venue-reported state matches what the engine
requested. The **canary** (`trading-bot canary`) is the named in-repo
vehicle for exactly this — see *The canary — the validation vehicle* under
*Proven vs pending*. **Do not skip this.**

Only after all five does a live `run` proceed to wire the live adapter.

Expand Down Expand Up @@ -133,6 +133,42 @@ The left column is what the offline suite demonstrates today. The right column i
the one remaining bridge to live — it requires a real key and is **out of scope
for this repository's automated tests** (which never hit a real venue).

### The canary — the validation vehicle

The pending column is exactly what the **canary**
([`application/canary.py`](../../trading_bot/application/canary.py), the
`trading-bot canary` command) proves against a real venue, in one gated run — a
minimum-size sequential round-trip plus two free probes, with an
expectation-vs-observation evidence table and a non-zero exit on the first
violation:

```bash
trading-bot canary --exchange binance --mode testnet # sandbox: fake money, real API
trading-bot canary --exchange <venue> --mode live -c cfg.yaml # the go-live act (all gates + typed ack)
```

- **Venue idempotency** — the probe re-submits the *same* client-order-id and
asserts, from **venue reads** (`open_orders` / `fills`), that no duplicate
order was created.
- **Real cancel (the kill path)** — the probe places a far-below-market limit
and cancels it **on the venue**: a real cancel accepted, no fill, no balance
move, `CANCELLED` persisted.
- **Balance reconciliation** — the venue **identity oracle**: our recorded
fills == the venue-reported fills (`fills()` re-fetch; ids/qtys/prices,
exact), the venue's per-asset balance deltas == what our fills imply (the
fills math), position flat, and the total cost (the venue's quote delta)
bounded by `--max-cost`. Never a precomputed absolute PnL — a real venue's
spread/drift are not deterministic.

`--mode testnet` runs the factory's sandbox path (hard-pinned testnet URL,
`BINANCE_TESTNET_*` keys, no `live_enabled` needed — Kraken has no spot testnet
and is refused). `--mode live` is the operator's **go-live act** for road-to-1.0
#1: it demands the full gate stack above (`--config` with `live_enabled: true`,
credentials, all risk limits) **plus a typed confirmation** (the dashboard's
go-live phrase), and only then trades the smallest venue-legal size. Cadence:
paper per release, testnet at will, live once per venue at go-live and after
any adapter change.

---

## Running LS1 (the first real portfolio strategy)
Expand Down
81 changes: 0 additions & 81 deletions doc/dev/plans/canary-roundtrip/00-plan.md

This file was deleted.

Loading
Loading