← Playbook · machine-readable: guide.md · openapi.json · lean.md

Ledger API Guide — playbook.db3hk.com

Base URL: https://playbook.db3hk.com Playbook: HSI Reclaim Debit v1.8.11 · Ledger schema: 1.8 · All times HKT (Asia/Hong_Kong) · Ticket #802 (parents #801, #797, #795)

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

ROOT operating system Small debit → room to wait → short = named → bounded exit. Story date = horizon_deadline. ALWAYS (wait): expiry > horizon_deadline. NEVER (wait): expiry = story date. Rebuild that ticket. Scalp (today / flat 16:00) does not use that rule. Change the story → flatten first. 平價 · 等得到 · 岩方向 · 有牆. 照開倉條款做;要改故事,先平倉.
ROOT (destination → wait debit → bounded exit)
 └─ case_type: wait (default) | scalp (exception: today 16:00)
 └─ packaging (optional label on wait): reclaim | crash_tail
 └─ ops: 1-lot · keep|flatten · VPS SoT · LEAN

ROOT (locked — tickets #797 + #801, render #802)

You state a destination (named level + rough timing). The service helps propose one defined-risk debit first — small/fair debit, short = named, room to wait, wall = debit — and only then accepts a packaging label. The label must not block the proposal.

Pre-budgeted small debit for a bounded chance to monetize a named level / directional move before a fixed deadline — without predicting the exact arrival time, but with economics tested for both early and late arrival. Short = expiry payoff-cap boundary, not guaranteed pre-expiry cash-out. Target touch → exit decision; P&L = executable credit after costs. Debit = the wall. Small debit ≠ small account risk.

Within expiry is mandatory (European: mid-path touch is an exit decision if you flatten on/near tag, or settlement still favorable — waiting ≠ ignore expiry). The story clock is horizon_deadline. The contract date is later. Early arrival = tag before the story date. Late arrival = tag after the story date and before expiry.

Persona: 平價 (small/fair debit, not fat ATM) · 等得到 (no timestamp prophecy; expiry is later than the story date) · 岩方向 (named level / short) · 有牆 (debit spread, whole-combo).

Hierarchy: ROOT → case (wait default, scalp declared separately — not CORE) → packaging (reclaim default | crash_tail micro) → ops. 口號:照開倉條款做;要改故事,先平倉.

Propose first. Given a named destination + rough timing, output in this order: named → horizon_deadline (story) → expiry (> deadline) → strikes / debit / −35% / 1-lot → packaging reclaim|crash_tail. ALWAYS (wait): expiry > horizon_deadline. NEVER (wait): expiry = story date — that wait card is invalid and must be rebuilt. Scalp does not use this rule. GO or pass on economics. Never pass only because the label is crash. Two stations on one trendline = one named (the near station).

Agent playbook-check response (#801 fields, #802 render)

Classify the ask first: new ticket | open manage | idea review. Idea review does not call POST /check. The API body is unchanged.

New ticket — Table 1 is Field | Value, one row per field, in this order: 日期, 結構, 到期, 剩餘日, 現貨, named_level, horizon_deadline, bid/ask/mid, natural, 建議限價, 限價Debit, Max虧, 本金, NAV%, 闊度, −35% stop, GO/NO-GO. Do not print a single wrapping pipe header and value line. Verdict cell: GO, NO-GO, or pass, with one short reason when it is not GO. 建議限價 defaults to mid or mid−1 (still fillable). Hard max stays mid+1. Max虧 = full debit + ~HK$120. Packaging is a one-line footnote after Table 1, not a row.

Table 2 follows the footnote. Required rows: 50% DTE at named (estimate; name the assumption; not a fill guarantee) and Expiry at named (intrinsic only). Bear put: clamp(long_strike − spot, 0, width). Bull call: clamp(spot − long_strike, 0, width). HKD = pts × 50. vs entry uses 限價Debit. Spot is named_level on wait.

Do not revive as Table 1 rows: Book (T/P), case_type, wait_reason, 合約ID/乘數/結算已核實, Target Mark, 毛利點, 回報%, 打和, a separate 扣佣 column, thin-meat?, A/B score, VHSI, frozen lower TP % as a primary row, source notes, Max利.

Open manage — short row only. CONSIDER_EXIT is notify only, not an auto SELL. Manage does not print Table 2.

keep|flatten | NATURAL% | vs named | vs stop | vs deadline | milestone

Idea review: 平價 · 等得到 · 岩方向 · 有牆, each ✓ or ✗. Result PASS | FAIL | N/A. Decision: do not open | rebuild card | open with Table 1 and Table 2.

crash_tail stays as packaging. Debit cap HK$800. Over the cap, or a missing debit, is a warning — not a silent NO-GO. It is not a second profit engine and not the default. wait_reason stays reclaim_tag for both labels. Fib is a note under reclaim.

HSI index options are European. A mid-path touch pays if you exit on/near the tag, or if settlement is still favorable. Waiting is not ignoring expiry.

Bounce / failure path (hard)

On an adverse bounce: only keep (stops OK) or flatten (SL / cash invalidation / time stop). Forbidden: roll the long closer; switch strikes; add debit. After SL: cool-off → a new ticket only.

Giveback budget (TP then redeploy)

Protected floor = realised − new debit − ~HK$120. State it before the next debit. Still max 1 open.

Stop (primary = −35%)

  1. Premium stop: −35% planned on the natural path (not −30%; not max loss). TWS is the primary stop; watchdog is the second layer.
  2. Cash invalidation: closer than “back to the old entry.”
  3. Time stop: 2 day sessions with no new low/high and no named-level progress → flatten.

One-slot (hard) — 2-slot not allowed

Max 1 open. No add_slot enum. No partial reduce. No Banker / Runner / scale_mode (1-lot cannot half-exit).

CLASSIFIER ENUMS (v1.8.10)

case_type wait|scalp · packaging reclaim|crash_tail · decision GO|NO-GO|pass · manage keep|flatten · book Tactical · wait_reason reclaim_tag · stop −35% · slots = 1 (2-slot not allowed). Never invent roll / add-slot / change-short / −30% live stop / Banker / Runner / a job tree. scalp_flush and balance_wait200 stay parked. crash_tail is the micro packaging label, not a job.

Account risk honesty

Small debit ≠ small account risk. Concentrated speculative risk on ~HK$9–10k NAV. HK$2,000 debit ≈ 20% NAV; full debit + costs ≈ 21% NAV. Max debit HK$2,500 and NAV% ≤ 25% at the limit.

Fee vs micro-TP honesty

Comfortable width-100 debit is under 40 pts (HK$2,000). Fee floor ~HK$500 net. +20% cannot clear the fee floor inside the cap (best case HK$380).

EXIT PRECEDENCE

  1. Invalidation or deadline → flatten
  2. Else frozen case TP (Wait: named level first, take more +30–60% on the tag; backup % only if untagged. Scalp: % or intraday, flat by 16:00)
  3. Else keep

Confirmed flat before re-open. No separate upper-TP +50–70% section.

Light ops

VPS = policy + record; IB = evidence. Mismatch blocks new entry until reconcile. No second /check while an order is working. VPS down → no new entry; broker risk exit still allowed. Natural = quote, not a fill. Do not rewrite an already-open ticket’s frozen fields.

Parked (not the daily checklist)

Playbook v1.8.11 decision tables (ticket #802)

Display only, on top of v1.8.10. Schema stays 1.8. A new ticket prints Table 1 as Field | Value (the #801 fields, same order) and Table 2 scenario marks (50% DTE at named = labeled estimate; expiry at named = intrinsic). The single pipe header is not the required answer. #797 ALWAYS/NEVER timing is unchanged. An already-open ticket keeps the fields it was opened with.

Playbook v1.8.10 ROOT harden + decision row (tickets #797 + #801)

Hardens #795. Schema stays 1.8. Story date binds horizon_deadline. Wait expiry must be a later calendar date (ALWAYS expiry > horizon_deadline; NEVER expiry = story date). /check adds advisory warnings[] code expiry_not_after_deadline when a wait body sends an expiry date on or before the deadline. That warning is not a 422. Scalp does not use the rule. horizon=by expiry no longer copies expiry onto a blank horizon_deadline; the open needs an explicit story deadline. Assistant answers use the STANDARD DECISION ROW (#801). Default limit is mid or mid−1. Hard max stays mid+1. An already-open ticket keeps the fields it was opened with.

Playbook v1.8.9 ROOT (ticket #795)

Propose the ticket first. Optional packaging reclaim|crash_tail on wait. crash_tail micro cap HK$800 is a warning, not a silent NO-GO. Supersedes the v1.8.8 line that parked crash_tail as a note only. Sections titled v1.8.8 and earlier are history.

Playbook v1.8.8 MVP (ticket #793)

Daily card above. job dropped from the entry path. wait_reason is reclaim_tag only. Book is Tactical only. Schema stays 1.8. Sections titled v1.8.7 and earlier are history; where they disagree with this card, v1.8.8 wins.

Playbook v1.8.7 — consolidation (ticket #766, Astra log #632)

Parks balance_wait200, softens CORE touch language, adds NAV/fee honesty and one exit-precedence page. Coordinates with #764 / v1.8.6 (bounce keep|flatten, 1-slot, JEV enums kept). No gate changes. /check with job=balance_wait200 → NO-GO reason=job_parked. Optional account_hkd on POST /ledger/close.

Playbook v1.8.6 — strategic gaps / JEV-ready enums (ticket #764)

Additive wording + /check warnings[] + /manage keep|flatten reminders + live classifier object. No gate changes — contract gate, VHSI fail-closed (≥25 / missing), caps vs limit, one-lot, whole-combo, turns disabled, day default, optional job enum all unchanged. 2-slot is not allowed in the API.

Playbook v1.8.5 — CORE philosophy lead-in (ticket #732)

Additive wording + live core field. No gate changes — contract gate, VHSI fail-closed (≥25 / missing), caps vs limit, one-lot, whole-combo, turns disabled, day default, optional job enum all unchanged.

LEAN ownership (locked — ticket #724)

TradeScope = eyes (charts only). VPS = rules+ledger SoT. Human IB fills. Grok Bot = design, not every check now.

TV / TradeScope     →  chart truth (gates A/B/C, panic-print, named levels)
        ↓
playbook.db3hk.com  →  /check · /manage · /ledger · /journal  (rules + state SoT)
        ↓
You on IB TWS       →  combo quote + limit (default mid or mid−1; hard max mid+1) + whole-combo exit
PieceOwnsMust not
TradeScopePanes, RSI, screenshots, analysis sessionscool-off, one-open, case freeze, horizon_deadline, /ledger* writes
This VPSPlaybook version, ledger, journal, gates mathTV CDP, IB orders
IB (human)Fills, live combo bid/askAuto from TradeScope
Grok BotPlaybook text, tickets, post-mortemsMandatory live path

Playbook v1.8.4 — conditional balance slot / job slots (ticket #720, user-agreed item 5)

Additive packaging. No gate changes — contract gate, VHSI fail-closed (≥25 / missing), caps vs limit, one-lot, whole-combo, turns disabled, day default all unchanged.

Playbook v1.8.3 — lean SoT (ticket #724)

Absorbs v1.8.2 packaging. Adds lean ownership to the live site, agent prompt, guide, and OpenAPI.

Playbook v1.8.2 — mid vs natural, combo flatten, packaging (ticket #720 follow-ups)

Wording + /check work-order steps + /manage reminders + horizon defaults on open. No loosening of B / VHSI / contract / caps.

Playbook v1.8 — Wait-case vs Scalp-case (ticket #720)

Comfort zone (locked wording): not “hold forever = 100% win.” When structure says the index still owes a tag (rebound / mean-reversion level, or continuing divergence), waiting is the higher-probability path — and a debit spread gives the comfortable defined-risk room to wait for that tag. Intuition / Fib = destination story only; gates decide; intuition may only VETO. Setup map (mirror book): low + stable → bull call debit; high + stable → bear put debit; “stable” = VHSI pass + no washout/melt + destination still makes sense. Chart fit: TV 15m (timing) + 1H (structure) dual pane + Daily for Gate A — no new chart stack.

Playbook v1.7 must-fixes (ticket #711)

Authentication

Every endpoint requires a static bearer token:

Authorization: Bearer <TOKEN>

Concepts

Endpoints

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

curl -s -H "Authorization: Bearer $TOKEN" https://playbook.db3hk.com/ledger/status
{
  "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. (POST /check adds NO-GO reasons vhsi_missing, vhsi_high.)

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

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.8 or the request is rejected with 422 schema_version_mismatch. Server overwrites updated_at.

POST /check — single trigger for a new entry

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

Required fields: book, target, horizon, side, case_type. job is not required. Input is validated before the session/state gate — missing fields → 422 missing_fields at any time of day. Optional advisory fields (never a 422 by themselves): vhsi, notes, width, debit_pts, combo_mid, suggested_limit, planned_tp_edge_hkd, spot / journey_pts, giveback_budget_pts. Case rules (422 on violation): case_type ∈ wait|scalp; side ∈ bull_call|bear_put; book must be Tactical (Patient → NO-GO reason=book_parked; anything else → 422 invalid_book); wait requires named_level + wait_reason=reclaim_tag (fib/divergence → 422 invalid_wait_reason, journal them as a note) and forbids horizon: "today"; scalp requires horizon: "today". Legacy job: balance_wait200 → NO-GO reason=job_parked (before session/VHSI); scalp_flush with Wait → 422 use_case_type detail use case_type=scalp (with Scalp it is ignored); crash_tail aliases to packaging=crash_tail (not a NO-GO); any other value → 422 invalid_job. Optional packaging on wait: reclaim (default) or crash_tail (micro cap HK$800, warning only — not a silent NO-GO). Anything else → 422 invalid_packaging.

v1.7 VHSI fail-closed gate (server-enforced, journaled as NO-GO):

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

{"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):

{
  "ok": true, "mode": "check_now",
  "book": "Tactical", "target": 25500, "horizon": "tomorrow", "side": "bull_call", "vhsi": 18.5,
  "case_type": "wait", "named_level": 25500, "wait_reason": "reclaim_tag",
  "quote_guidance": {
    "always": "mid_and_natural",
    "mid": "always quote combo mid",
    "natural": "always quote entry/exit fillable natural",
    "entry_suggested_limit_max": "mid+1",
    "stops_and_live_risk": "fillable natural (or a working limit between natural and mid), never mid alone",
    "never": "mid alone"
  },
  "sot": {
    "charts": "tradescope", "rules_and_ledger": "vps", "fills": "human_ib",
    "grok_bot": "design_not_every_check", "no_tradescope_ledger_bridge": true
  },
  "core": {
    "wait_is_core_default": true,
    "scalp_is_not_core": true,
    "crash_is_one_instance": false,
    "hierarchy": ["root", "case", "packaging", "ops"]
  },
  "steps": ["… TradeScope charts only → VPS work order → human IB fill → POST /ledger/open …"],
  "playbook": "v1.8.11"
}

POST /ledger/open — record a fill

curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"book":"Tactical","case_type":"wait","wait_reason":"reclaim_tag","side":"bull_call","expiry":"2026-09-11","long_strike":25400,"short_strike":25500,"width":100,"entry_debit_pts":43,"entry_debit_hkd":2150,"order_limit_hkd":2150,"named_level":25500,"horizon":"tomorrow","horizon_deadline":"2026-09-09T16:00:00+08:00","stop_debit_pts":28,"lower_tp_pct":25,"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. Required (422 if missing/invalid): case_type (wait|scalp); wait also requires named_level + wait_reason=reclaim_tag. Do not send job. balance_wait200 and book=Patient → 422 (job_parked / book_parked) so they are never stored. scalp_flush without case_type=scalp → 422 use_case_type. Horizon defaults: omitted horizon / horizon_deadline fill from case_type (wait → tomorrow + next day-session 16:00 HKT; scalp → today + same-day 16:00). Wait + by expiry does not copy expiry onto the deadline — send an explicit horizon_deadline (the story clock) or the open is 422 missing_fields. The alias by_expiry is stored as by expiry. A wait expiry calendar date on or before that deadline is an invalid card; /check warns expiry_not_after_deadline and does not 422 for the pair. Scalp does not use that rule. After defaults, wait+today or scalp+not-today → 422 invalid_horizon_for_case. Recommended: book=Tactical, side, expiry, long_strike, short_strike, width, entry_debit_pts, entry_debit_hkd, order_limit_hkd (actual limit price the caps were checked against), stop_debit_pts, lower_tp_pct (ONE exact % frozen at entry; wait-case: backup to named_level; when tagged, take more +30–60%), lower_tp_debit_pts, washout_low|washout_high, remaining_dte_at_entry. Do not rewrite an already-open position to match this card.

Journal at open (v1.8.3): every successful open auto-appends a journal row decision=OPEN with case_type + horizon in the note (and horizon in its column), linked to the new position_id. Response includes open_journal_id. Optional journal_id still links an agent GO/NO-GO row. TradeScope is not required to call this endpoint.

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

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

Flat → {"ok": false, "reason": "flat"} plus quote_guidance + sot + classifier + standing bounce keep|flatten reminders. Open → the stored position (not rewritten) + reminders: frozen case_type; Wait named-first and take more (+30–60%) on the tag; Scalp flat by 16:00; bounce → keep or flatten only, no roll; mid AND exit natural; −35% planned stop on the natural path; TWS primary stop, watchdog second; horizon_deadline; whole-combo only; max 1 open. A stored legacy job is mentioned as history, not as a manage enum. Manage takes no input. Changing the story means POST /ledger/close then a fresh POST /check.

POST /ledger/close — record an exit

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

Holding marks — review store

Holding marks = review store on VPS (API+UI); samples POSTed by Bot watchdog; Notion SNAP remains daily archive only. No IB cron on the VPS. The public playbook page stays the card. The chart is the page /marks: the bearer token once, as a session, or a client IP listed in server config dashboard_allow_ips. The ledger API still requires the bearer token from every address.

Premium unit is points (entry_debit_pts / combo NATURAL). The server stores unrealized_pnl_pct as percent points: ((natural - entry_debit) / entry_debit) * 100. A client-sent percent is ignored.

curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"position_id":10,"natural":48,"mid":49,"quote_quality":"live","source":"watchdog"}' \
  https://playbook.db3hk.com/ledger/marks

curl -s -H "Authorization: Bearer $TOKEN" \
  "https://playbook.db3hk.com/ledger/marks?position_id=10"

curl -s -H "Authorization: Bearer $TOKEN" \
  "https://playbook.db3hk.com/ledger/marks/summary?position_id=10"

POST /journal — record a final GO/NO-GO decision (tickets #706 / #707)

curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"decision":"GO","reason":"gates A/B/C pass","book":"Tactical","target":25500,"horizon":"tomorrow","side":"bull_call","vhsi":18.5,"b_score":"3/4","debit_pts":43,"debit_hkd":2150,"note":"1-line note","check_id":12}' \
  https://playbook.db3hk.com/journal

GET /journal?limit=50 — newest first

curl -s -H "Authorization: Bearer $TOKEN" "https://playbook.db3hk.com/journal?limit=50"

Row fields: id, created_at (HKT), decision, reason, book, target, horizon, side, vhsi, b_score, debit_pts, debit_hkd, note, check_id, position_id, result, pnl_pct, auto.

Linking journal ↔ fills

Error format

{"ok": false, "error": "unauthorized|not_found|schema_version_mismatch|missing_fields|invalid_case_type|invalid_side|invalid_book|invalid_wait_reason|invalid_horizon_for_case|invalid_job|use_case_type|job_parked|book_parked|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, invalid decision on /journal, 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. This VPS is the only cool-off / one-open SoT.
  2. TradeScope: chart gates only. Then POST /check — with target + horizon + vhsi + case_type (Wait → named_level + wait_reason) → follow steps (contract gate → OpenD/IB structure → IBKR combo mid and natural → caps vs limit, default mid or mid−1, hard max mid+1 → Table 1 Field | Value, then Table 2 scenario marks). The request JSON is unchanged.
  3. If GO and human transmits on IB → POST /ledger/open with the fill (not a TradeScope write).
  4. While in trade → POST /manage (or GET /ledger). Manage mode never proposes new entries; output includes mid + natural.
  5. After exit → POST /ledger/close with result + pnl_pct. Cool-off is enforced automatically.

Runtime process (shortest flow — lean)

  1. TradeScope analyze → charts only (gates A/B/C, panic-print, named level). No ledger write. NO-GO on charts stops the chain.
  2. Paste IB mid and natural (or OpenD ref + IB confirm) into VPS POST /check with case_type (Wait → named_level + wait_reason). VPS is rules + ledger SoT.
  3. Human transmits 1-lot combo on IB (default limit mid or mid−1; hard max mid+1; whole-combo only).
  4. On fill: POST /ledger/open here (anyone with the token — not a TradeScope requirement). Server auto-logs case + horizon. On exit: POST /ledger/close.
  5. Mismatch vs IB → no write. 409 → reconcile, no new ticket.
  6. Grok Bot is for playbook/design tickets — not a mandatory hop on every check now.

Notes for AI agents reviewing this API