piker/.agents/skills/piker-clearing-expert/gotchas.md

5.4 KiB

Clearing And Ledger Gotchas

Trade dialog dies immediately after a fill

Likely cause: an explicit ledger/account write in the fill handler, or a later context-exit write, raised after event publication.

Inspect, in order:

  1. the first non-cancellation exception in the actor traceback;
  2. PaperBoi.fake_fill() or the live backend fill handler;
  3. explicit and context-exit write_config() calls;
  4. FQME/bs_mktid qualification and TOML-compatible values;
  5. the persisted ledger/account files for a partial update.

Tractor cancellation logs are often the consequence. Root-cause the inner serialization, lookup, or normalization exception first.

MktPair has no len() in RapidFuzz

Cause: a mapping of string keys to MktPair values was passed directly to rapidfuzz.process.extract(). Mapping values become fuzzy choices.

Fix: search a list/view of string keys and resolve matched keys back through the original mapping. Do not add sequence methods to MktPair and do not use processor=str when aliases must be preserved.

Fuzzy qualification raises IndexError

Cause: the caller selected the first match from an empty result after the score cutoff rejected every key.

Decide explicitly whether to retain the input identity, reject the record with a descriptive exception, or obtain missing market metadata. Do not lower the cutoff blindly; the wrong market corrupts accounting.

Cancel is ignored or sent with the wrong ID

Cause: confusion between client oid and backend reqid, commonly during cancel-before-ack ordering.

Verify the ack installed both mapping directions and that Status.cancel_called survives until reqid exists. Log both IDs and the actor/backend account at every transition.

Fills appear but positions do not move

Check these boundaries:

  • the backend emitted BrokerdFill but never normalized a transaction;
  • transaction size lost its buy/sell sign;
  • tid collided with an existing position event;
  • Transaction.bs_mktid selected a different Account.pps key;
  • mktmaps lacked the exact transaction identity;
  • position publication happened before accounting update;
  • the backend intentionally reports venue positions instead of local ledger-derived positions.

Duplicate or inflated position size

Do not deduplicate by price/time alone. Confirm stable transaction IDs, chronological sorting, and Position.add_clear() idempotence. Broker execution IDs and order IDs are not interchangeable.

For partial fills, several tids may correctly share one order reqid. Collapsing them loses clears; replaying one tid twice inflates nothing only if the dedupe boundary is preserved.

Current paper fills use tid=oid, so later partials overwrite the ledger record and are deduplicated out of the position. Treat paper partial fills as broken until each clear receives a stable unique ID.

Paper and live state disagree

Paper uses normalized local transactions and can use the full FQME as its account position key. A live backend may use native IDs, raw ledger records, or venue-reported positions.

Compare normalized Transactions and emitted BrokerdPositions, not raw TOML dictionaries across backends.

Account appears under the wrong name

Separate UI/EMS account aliases from backend account IDs and ledger file names. Backends may prefix account names for routing while persistence uses an unprefixed native account key.

Trace account identity through open_ems(), broker dialog startup, ledger opening, and BrokerdPosition.account before changing naming.

A module-global cache is unexpectedly empty

Symcache, clients, contracts, and order tables are actor-local. Data loaded in datad is not automatically available in brokerd or paperboi. Also, _symcache._caches is currently read but never populated, so do not assume repeated open_symcache() calls hit it.

Load piker-conc-expert and audit actor-local globals, async caches, and dialog startup warming. Never rely on an import side effect from a sibling actor.

Teardown emits a cancellation storm

Find the first application exception. Structured cancellation then closes feed streams, broker streams, and nested contexts. Avoid masking the originating exception with broad cancellation handling.

For stream ownership and context cancellation semantics, use piker-conc-expert.

Broker status is logged as unhandled

EMS currently accepts only ack-correlated open, closed, and canceled broker statuses. Declared pending and backend-produced fill/filled variants fall through, as do statuses arriving before ack. Verify producer vocabulary and ack ordering before adding another consumer branch.

Another client sees order or position events

Current translation broadcasts order updates by FQME and positions to all attached clients; Router.dialogs is not used as an event privacy filter. Treat subscriber isolation as incomplete, not guaranteed.

Debug breadcrumb

Capture one row per event with:

actor, backend, account, fqme, bs_mktid,
oid, reqid, venue_order_id, tid,
message_type, status, action, signed_size, price, timestamp

Then compare:

  1. client command order;
  2. EMS status history;
  3. broker event order;
  4. normalized transactions;
  5. position event IDs and computed size/PPU;
  6. ledger/account disk state;
  7. emitted position summaries.

This separates protocol loss, identity skew, accounting math, and persistence failure without guessing from UI state.