← Playbook · machine-readable: guide.md · openapi.json · lean.md
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 · LEANYou 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).
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 | milestoneIdea 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.
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.
Protected floor = realised − new debit − ~HK$120. State it before the next debit. Still max 1 open.
Max 1 open. No add_slot enum. No partial reduce. No Banker / Runner / scale_mode (1-lot cannot half-exit).
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.
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.
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).
Confirmed flat before re-open. No separate upper-TP +50–70% section.
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.
book=Patient → NO-GO reason=book_parked.balance_wait200 — dead. 200 = underlying index points. Long 400–800 OTM + width ~400 (400-wide) puts the short 800–1,200 from spot, not a ~100–150 or 150–250 journey. /check → NO-GO reason=job_parked. No fit under caps → pass.scalp_flush — use case_type=scalp. With Wait → 422 detail use case_type=scalp.job — not required on /check or /ledger/open. crash_tail is no longer sent as job; send packaging=crash_tail. A crash map without a proposed micro ticket is only a compass (directional bias).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.
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.
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.
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.
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.
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.
/check step 2; packaging-mismatch + bounce-no-roll warnings when spot / journey_pts / notes allow./manage reminds: bounce → keep or flatten only; no long roll; stop layers for the frozen job.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.
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| Piece | Owns | Must not |
|---|---|---|
| TradeScope | Panes, RSI, screenshots, analysis sessions | cool-off, one-open, case freeze, horizon_deadline, /ledger* writes |
| This VPS | Playbook version, ledger, journal, gates math | TV CDP, IB orders |
| IB (human) | Fills, live combo bid/ask | Auto from TradeScope |
| Grok Bot | Playbook text, tickets, post-mortems | Mandatory live path |
HSI-Reclaim-Debit-ledger.json, no TradeScope open-position flag that disagrees with VPS). Cool-off, one-open, case_type, named_level, wait_reason, horizon, horizon_deadline, lower_tp frozen fields live only on /ledger*.POST /ledger/open here.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.
scalp_flush | balance_wait200 | crash_tail. Field job (optional, v1.8.4): validated on POST /check and POST /ledger/open (422 invalid_job if supplied and not in the enum); stored verbatim on open_position (frozen); echoed in /manage reminders; auto-tagged in journal notes ([case:wait reclaim_tag lvl:25500 job:balance_wait200]).balance_wait200, conditional — not the default Tactical 100-wide reclaim): long ~400–800 OTM; width ~400; short = named target; Wait-case clock; debit caps + thin-meat still apply; TP ~200 pts / named tag / 70–80% of max.scalp_flush; 1,200-wide crash as profit engine → insurance only (crash_tail); buying first-slot strikes after the flush printed → declare the real job or pass.balance_wait200 with case_type=scalp, scalp_flush with case_type=wait, and crash_tail with width ≥ 1000 print as warnings[] on the work order — the human decides, the mismatch is always printed.Absorbs v1.8.2 packaging. Adds lean ownership to the live site, agent prompt, guide, and OpenAPI.
/check + /manage return structured quote_guidance (always mid and natural; entry suggested limit max mid+1; stops/live risk off fillable natural, never mid alone) and sot (TradeScope charts; VPS rules+ledger; human IB fills; Grok Bot = design).POST /ledger/open auto-journals frozen case_type + horizon (decision=OPEN, open_journal_id in the response). GO/NO-GO journal is unchanged (POST /journal).Wording + /check work-order steps + /manage reminders + horizon defaults on open. No loosening of B / VHSI / contract / caps.
warnings[] on /check if debit_pts + width are sent.named_level tags or spot through the short, work +30–60% of debit / upper credit. Frozen +20% / micro +7 is Scalp or backup.planned_tp_edge_hkd is sent.POST /ledger/open (v1.8.2 history): omitted horizon / horizon_deadline filled from case_type, and wait + by expiry used to copy expiry 16:00 onto the deadline. v1.8.10 stops that copy. See the v1.8.10 section and the live /ledger/open note.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.
case_type is required and frozen at entry — wait | scalp. Declare before entry, refine after — don't reclassify: no silent Wait↔Scalp flips or horizon/deadline morphs mid-hold. Story changed → flat first, or explicit written re-open with a new declared case (journal the why).named_level + wait_reason (reclaim_tag | fib_reversion | divergence_ongoing); horizon tomorrow | by expiry (never today); DTE prefer 7–10; working TP = named_level first, frozen lower_tp_pct is the backup; when the level tags, take more (+30–60% of debit); bounded by −35% stop and horizon_deadline.today only, horizon_deadline = same-day 16:00 HKT, explicitly chosen.POST /check and POST /ledger/open → 422 missing_fields / invalid_case_type / invalid_wait_reason / invalid_horizon_for_case. POST /manage echoes the frozen case in reminders; manage accepts no input, so a silent case flip is impossible by design.today horizon → declare Scalp (not NO-GO, not “wait anyway”); Wait-case DTE 7–10 is a soft prefer, not a hard floor; named_level frozen after entry — a changed level = changed story → flat first or written re-open.POST /check row carries horizon in its own column and the note is auto-tagged [case:wait fib_reversion lvl:25395] (or [case:scalp]). POST /ledger/open also auto-appends decision=OPEN with the frozen case + horizon.vhsi_missing); VHSI ≥ 25 → NO-GO (vhsi_high) — single hard threshold. Enforced server-side on POST /check.horizon_deadline (HKT timestamp) stored at entry — flatten at deadline, never hold past./marks shows “ENTRY CHECK AVAILABLE — NOT TRADE APPROVAL” (renamed from “CAN ENTER”). The public playbook page does not show live ledger status.Every endpoint requires a static bearer token:
Authorization: Bearer <TOKEN>401 {"ok": false, "error": "unauthorized"}{schema_version, account_hkd, updated_at, cool_off_until, cool_off_reason, open_position, last_closed, history[]}. Frozen at entry on open_position: case_type, named_level, wait_reason, horizon, horizon_deadline, lower_tp_*.open_position is null. Max 1 open position.can_new_entry) is unchanged — night still returns outside_day_session.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).GET /ledger/status — call this FIRST before any GOcurl -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 JSONcurl -s -H "Authorization: Bearer $TOKEN" https://playbook.db3hk.com/ledgerPUT /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 entrycurl -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/checkRequired 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):
vhsi missing/null → {"decision":"NO-GO","reason":"vhsi_missing"} — no B4 workaround; paste VHSI and retry.vhsi >= 25 → {"decision":"NO-GO","reason":"vhsi_high"} — single hard threshold (replaces "≥25–30 usually").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 fillcurl -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/openBody 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/manageFlat → {"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 exitcurl -s -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"result":"stop","pnl_pct":-40}' \
https://playbook.db3hk.com/ledger/closeresult ∈ tp | stop | scratch | manual (else 400 invalid_result)409 no_open_positionopen_position → last_closed, appends to history[]result=stop always; result=scratch with pnl_pct <= -15; sets cool_off_until = next HKT day-session date (weekends + configured HKEX holidays skipped)exit_natural, exit_mid, and quote_quality (live|prior_close|wide|missing) are stored on the final holding-mark row. result and pnl_pct stay required. A final row is written even when those marks are omitted.final_mark_id. POST /ledger/open includes baseline_mark_id.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 /ledger/marks accepts one sample or {"samples":[...]} (max 50). Idempotent on (position_id, ts_bucket) where the bucket is the HKT minute. quote_quality is required. Client source is watchdog|ibkr_connector|manual (default watchdog).409 position_closed. Unknown ids return 404 position_not_found.GET /ledger/marks/positions lists the open position, if any, then the latest closed rows for the dashboard picker.GET /ledger/marks/summary returns entry, peak natural and percent, exit, giveback_pp, and minutes from entry to +50%, +100%, the −35% mark, and the stored deadline.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/journaldecision ∈ GO | NO-GO (else 422 invalid_decision)reason, book, target, horizon, side, vhsi, b_score, debit_pts, debit_hkd, note (1 line), check_id (links to the auto row from POST /check){"ok": true, "journal_id": N}POST /check decision is journaled automatically — gate refusals as NO-GO, gate passes as WORK_ORDER (the response includes journal_id). POST /ledger/open auto-appends OPEN with frozen case + horizon (open_journal_id). Agents then POST /journal the final GO/NO-GO after the decision row.GET /journal?limit=50 — newest firstcurl -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.
POST /ledger/open accepts optional journal_id — marks that journal row filled (position_id set; debit fields backfilled from the fill if empty). Response includes journal_linked: true.POST /ledger/close writes result + pnl_pct back onto the linked journal row automatically.{"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.
GET /ledger/status — before any GO. If can_new_entry is false, stop. This VPS is the only cool-off / one-open SoT.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.POST /ledger/open with the fill (not a TradeScope write).POST /manage (or GET /ledger). Manage mode never proposes new entries; output includes mid + natural.POST /ledger/close with result + pnl_pct. Cool-off is enforced automatically.POST /check with case_type (Wait → named_level + wait_reason). VPS is rules + ledger SoT.POST /ledger/open here (anyone with the token — not a TradeScope requirement). Server auto-logs case + horizon. On exit: POST /ledger/close.check now.GET /guide.md (Markdown), GET /openapi.json (OpenAPI 3.0)./ shows live status (server-rendered, no token) — safe to fetch for a quick check, but /ledger/status is authoritative.