# Ledger API Guide — playbook.db3hk.com

Base URL: `https://playbook.db3hk.com`
Playbook: **HSI Reclaim Debit v1.6** · Ledger schema: **1.6** · All times **HKT (Asia/Hong_Kong)**

This service stores trading state (the ledger) and issues v1.6 work orders. It does **not** place orders, fetch market data, or compute indicators. Human transmits on IB.

## Authentication

Every endpoint requires a static bearer token:

```
Authorization: Bearer <TOKEN>
```

- Token lives in server config (outside web root); rotate by hand.
- HTTPS only — plain HTTP 301-redirects.
- Missing/wrong token → `401 {"ok": false, "error": "unauthorized"}`
- No broker credentials are stored in this service.

## Concepts

- **Ledger** = single source of truth. Shape: `{schema_version, account_hkd, updated_at, cool_off_until, cool_off_reason, open_position, last_closed, history[]}`
- **Flat** = `open_position` is `null`. Max **1 open** position.
- **Day session** = HKT 09:30–12:00 and 13:00–16:00, Mon–Fri (lunch = no new entries, per playbook v1.6). HKEX holidays are encoded via a hand-maintained list in server config — verify against the HKEX calendar before relying on it.
- **Cool-off** = after a `stop`, or a `scratch` with `pnl_pct <= -15`, no new entries until the next HKT day-session date.
- **`can_new_entry`** = in day session AND no open position AND (`cool_off_until` is null OR `< today`).

## Endpoints

### `GET /ledger/status` — call this FIRST before any GO

```bash
curl -s -H "Authorization: Bearer $TOKEN" https://playbook.db3hk.com/ledger/status
```

```json
{
  "can_new_entry": false,
  "reason": "outside_day_session",
  "cool_off_until": null,
  "has_open": false,
  "in_day_session": false,
  "now_hkt": "2026-09-07T16:00:18+08:00"
}
```

`reason` values: `ok`, `position_open`, `cool_off`, `outside_day_session`.

**Reason priority** (highest first): `position_open` → `cool_off` → `outside_day_session` → `ok`. So with an open position after hours, `reason` is `position_open` (not `outside_day_session`); during cool-off after hours it is `cool_off`. `in_day_session` and `has_open` are always reported separately — use them, not `reason`, if you need the raw facts.

### `GET /ledger` — full ledger JSON

```bash
curl -s -H "Authorization: Bearer $TOKEN" https://playbook.db3hk.com/ledger
```

### `PUT /ledger` — replace ledger (after manual reconciliation)

Body = full ledger JSON. `schema_version` must equal `1.6` or the request is rejected with `422 schema_version_mismatch`. Server overwrites `updated_at`.

### `POST /check` — single trigger for a new entry

```bash
curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"book":"Tactical","target":25500,"horizon":"tomorrow","side":"bull_call","vhsi":18.5,"notes":"optional"}' \
  https://playbook.db3hk.com/check
```

Required fields: `book`, `target`, `horizon`, `side`. **Input is validated before the session/state gate** — missing fields → `422 missing_fields` at any time of day (agents always get a clear error). Optional: `vhsi`, `notes`.

If `can_new_entry` is false → returns `NO-GO` and stops:

```json
{"ok": false, "mode": "check_now", "decision": "NO-GO", "reason": "cool_off", "cool_off_until": "2026-09-08", "status": { }}
```

Otherwise returns a **work order** (this service never trades):

```json
{
  "ok": true, "mode": "check_now",
  "book": "Tactical", "target": 25500, "horizon": "tomorrow", "side": "bull_call", "vhsi": 18.5,
  "steps": [
    "1. TradeScope/TV: Gates A/B/C + panic-print (1x 15m close)",
    "2. OpenD or IB: propose 1-lot HSI width-100 structure, DTE 4-10, remaining DTE>=3",
    "3. IBKR agent: authoritative combo mid; debit<=2500; NAV%<=25%",
    "4. Day session + tradable combo only",
    "5. Fill decision row; GO/NO-GO; if GO human transmits on IB",
    "6. POST /ledger/open after fill"
  ],
  "playbook": "v1.6"
}
```

### `POST /ledger/open` — record a fill

```bash
curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"book":"Tactical","side":"bull_call","expiry":"2026-09-11","long_strike":25400,"short_strike":25500,"width":100,"entry_debit_pts":43,"entry_debit_hkd":2150,"named_level":25500,"horizon":"tomorrow","stop_debit_pts":28,"lower_tp_debit_pts":52,"washout_low":25100,"remaining_dte_at_entry":4}' \
  https://playbook.db3hk.com/ledger/open
```

Body is stored as `open_position` verbatim; `entry_time_hkt` is added if absent. If a position is already open → `409 position_already_open`. Recommended fields: `book, side, expiry, long_strike, short_strike, width, entry_debit_pts, entry_debit_hkd, named_level, horizon, stop_debit_pts, lower_tp_debit_pts, washout_low|washout_high, remaining_dte_at_entry`.

### `POST /manage` — manage the open position (never suggests new entries)

```bash
curl -s -X POST -H "Authorization: Bearer $TOKEN" https://playbook.db3hk.com/manage
```

Flat → `{"ok": false, "reason": "flat"}`. Open → position fields + reminders (mark vs −35% stop, lower TP / named level, ½-DTE, day-session exits only, no new entry) + status.

### `POST /ledger/close` — record an exit

```bash
curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"result":"stop","pnl_pct":-40}' \
  https://playbook.db3hk.com/ledger/close
```

- `result` ∈ `tp | stop | scratch | manual` (else `400 invalid_result`)
- No open position → `409 no_open_position`
- Moves `open_position` → `last_closed`, appends to `history[]`
- Cool-off: `result=stop` always; `result=scratch` with `pnl_pct <= -15`; sets `cool_off_until` = next HKT day-session date (weekends + configured HKEX holidays skipped)

## Error format

```json
{"ok": false, "error": "unauthorized|not_found|schema_version_mismatch|missing_fields|invalid_result|position_already_open|no_open_position|server_error"}
```

HTTP codes: `200` ok · `400` invalid `result` on close · `401` auth · `404` unknown path · `409` state conflict · `422` missing fields on /check, or schema_version mismatch on PUT /ledger · `500` server error.

## Agent workflow

1. `GET /ledger/status` — before **any** GO. If `can_new_entry` is false, stop.
2. `POST /check` — with target + horizon → follow `steps` (TradeScope chart gates → OpenD/IB structure → IBKR combo mid → decision row).
3. If GO and human transmits on IB → `POST /ledger/open` with the fill.
4. While in trade → `POST /manage` (or `GET /ledger`). Manage mode never proposes new entries.
5. After exit → `POST /ledger/close` with result + pnl_pct. Cool-off is enforced automatically.

## Runtime process (shortest flow)

1. TradeScope `check now` → chart gates only. NO-GO stops the chain.
2. If pass: Grok IB `check now` (paste TS numbers + target + horizon). IB mid = debit. Open position → manage only.
3. Human transmits 1-lot combo on IB.
4. Human says "filled" or "closed …" to Grok.
5. Grok verifies on IBKR, then `POST /ledger/open` or `/ledger/close`. Mismatch → no write. 409 → reconcile, no new ticket.
6. playbook.db3hk.com is the only session / cool-off / one-open gate.

## Notes for AI agents reviewing this API

- Machine-readable versions of this guide: `GET /guide.md` (Markdown), `GET /openapi.json` (OpenAPI 3.0).
- The public front page `/` shows live status (server-rendered, no token) — safe to fetch for a quick check, but `/ledger/status` is authoritative.
- This API is a **state store + checklist orchestrator only**. Market data comes from OpenD/nd-futu-api (desktop, port 8001); chart analysis from TradeScope; fills from IB. Do not ask this API for quotes.
- HKEX holidays: encoded as a hand-maintained date list in server config (affects both the session gate and cool-off date math). Verify against the HKEX calendar periodically.
- Phase 2 (not implemented): webhook notify, apis.db3hk.com gateway proxy route.
