Skip to content

engine: order gaps for the terminal (list, in-doubt, cancel-after-fill, stop trigger, one TIF) - #31

Merged
YukiC0804 merged 10 commits into
codex/order-lifecycle-storagefrom
sp/order-gaps
Oct 21, 2026
Merged

YukiC0804 merged 10 commits into
codex/order-lifecycle-storagefrom
sp/order-gaps

Conversation

@YukiC0804

Copy link
Copy Markdown
Collaborator

Stacked on #30 (sp/serve-parity). Both land in codex/order-lifecycle-storage, not main; no release. Merge #30 first, then retarget this PR to codex/order-lifecycle-storage.

Found while putting Richard's stop orders into the terminal and testing them live on Kalshi. Each item is also in prediction_market terminal/specs/draft/ORDER_ARCHITECTURE_FEEDBACK.md.

Changes

  • S1, list every state: GET /trading/orders?scope=open|all (+ market_id, venue, book, since, limit, cursor), venue orders plus engine-held parents, newest first. journal.orders_page(), Alembic 0004 (indexes, schema 9), Client.fetch_orders().
  • S2, in doubt vs failed: a send with no venue answer raises SubmissionInDoubt; serve returns 202 submission_in_doubt / submission_pending (retryable, client_order_id); remote.py maps them. Retrying the same id never sends twice.
  • S3, cancel after a fill: a filled order stays closed; on the venue's not-found the order is read back; CancelUnconfirmed (409) when the venue has no record. 12 reproducing tests failed before the fix.
  • Stop trigger bug (found live): a stop on a market whose book was already loaded and quiet never triggered. Feeds.sync() and a new stop now evaluate the book they are handed. Verified live: a buy stop at the ask filled within ~0.25 s.
  • One time in force per stop order (IBKR model): stop_market and stop_limit take GTC/DAY/GTD; IOC/FOK refused; the child inherits the parent's TIF and expiry. Stored IOC/FOK stop limits still load.
  • Market orders: tests on SQLite and PostgreSQL (save-before-send, one IOC, idempotent retry, in-doubt adoption); lifecycle docs updated.

Verification

  • Full suite: 2411 passed, 169 skipped (PostgreSQL cases); with PostgreSQL: ~2577 passed.
  • Live on Kalshi (approved runs): waiting stop cancelled, stop triggered and filled, limit cancelled, exit filled; all flat.

🤖 Generated with Claude Code

Yuki Chu and others added 6 commits October 15, 2026 16:24
…, hosted, bucket books)

synpath serve now carries every data feature the terminal needs, so the
terminal's web backend can be a router with no venue connections or keys.

- Auth: every market-data route and stream requires the serve token.
- /ws/market: live books, trades and quotes for all venues, one shared
  reference-counted venue connection per market, shared with the
  engine's feeds; reconnect with a fresh snapshot.
- Batch reads and search: /markets?ids=, /books?ids=, /search/markets,
  /search/events, with per-id errors.
- Hosted routes: /matching/market|event|arbitrage and /history/* through
  serve's Synpath key, with mapped errors.
- Buckets: client ids, PATCH, a merged bucket book on /ws/market built
  with router.view/merge, and POST /trading/buckets/{id}/plan.
- BucketMember.enabled: an Off member is not streamed, merged or routed.
  Alembic revision 0003 adds bucket_members.enabled (default on); schema 8.
- tests/serve_harness.py: in-process serve with mocked venues, SQLite and
  PostgreSQL.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On CI's slower runners the bucket tests reached for the fake Polymarket
stream before the hub had opened it (KeyError: 'polymarket'). The
harness now waits for it, polling the dict directly so the helper also
works inside conditions that already run on the server's loop.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…l, stop trigger, one TIF)

- S1: GET /trading/orders?scope=open|all with market_id, venue, book,
  since, limit, cursor; venue orders plus engine-held parents, newest
  first. journal.orders_page(); Alembic 0004 (indexes, schema 9);
  Client.fetch_orders().
- S2: a send with no venue answer raises SubmissionInDoubt; serve
  returns 202 submission_in_doubt / submission_pending (retryable, with
  client_order_id); remote.py maps them. Retrying the same id never
  sends twice.
- S3: cancelling a filled order keeps it closed: on the venue's
  not-found the order is read back; CancelUnconfirmed (409) when the
  venue has no record.
- Stops evaluate a book they are handed: Feeds.sync() and a new stop
  check an already-loaded book at once (a quiet market never triggered).
- One time in force per stop order, like IBKR: stop_market and
  stop_limit take GTC, DAY or GTD; IOC/FOK refused; the child inherits
  the parent's TIF and expiry. Stored IOC/FOK stop limits still load.
- Market order tests on SQLite and PostgreSQL; lifecycle docs updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The 150 ms lease renewed every 20 ms could lapse on a slow CI runner
(Python 3.10, pure Python), so the stop's dispatch was refused for an
unrelated reason. 600 ms / 50 ms with a 0.9 s price wait keeps the
test's point: a wait longer than the lease must keep it renewed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Checkpoint of the order-gaps work the terminal runs on.

- Orders, fills and requests carry `outcome` (yes/no) and `action`
  (buy/sell) in place of `reduce_only`, which is removed everywhere,
  adapters included.
- Fair values are removed from the engine (migration 0005 drops the table).
- Positions are marked to the live book: the exit price (best bid for a
  long, best ask for a short), falling back to the last trade.
- Stops trigger on the last trade by default.
- An order log: every action on an order, newest first, served by serve.
- Docs and lifecycle notes updated to match.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Yuki Chu and others added 2 commits October 21, 2026 12:28
Keeps #30's venue_streams / market_stream / MarketStreams structure and fits
main into it:

- venue_streams accepts a MarketDataKey (Opinion, predict.fun, SX Bet) and
  builds the market stream alone from it, with a keyed catalog where no
  adapter supplies one; market_stream and default_streams(market_data) go
  through it.
- Betfair and Smarkets market streams (Smarkets without credentials).
- SX Bet's account stream only with an account address, warning otherwise.
- serve always loads market_data, passes it to default_streams and to the
  /ws/market factory, and gives the read app's default VenueRegistry its
  dotenv keys; #30's auth dependency, hosted routes and buckets stay.
- The dotenv read-app test sends serve's access token.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merges cleanly. Main's trading changes (SX Bet's key-only trading, Betfair
and Smarkets instruments, init/doctor) build no OrderRequest, Order or Fill
and bring back no reduce_only or fair values, so nothing needed porting to
outcome + action.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Yuki Chu and others added 2 commits October 21, 2026 12:49
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@YukiC0804
YukiC0804 changed the base branch from sp/serve-parity to codex/order-lifecycle-storage October 21, 2026 12:28
@YukiC0804
YukiC0804 merged commit 5ae2269 into codex/order-lifecycle-storage Oct 21, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant