From 7b51927f6b33eadf807c02c4fa4f5e977d5ffb84 Mon Sep 17 00:00:00 2001 From: goodboy Date: Thu, 27 Aug 2026 23:11:43 -0400 Subject: [PATCH] Fix `SymbologyCache.search()` key matching RapidFuzz treated `mktmaps` values as fuzzy choices and called `len()` on `MktPair` structs during paper-ledger persistence. Search string keys explicitly and resolve each match back to its struct so native market-ID aliases survive legacy FQME qualification. Also, - cover alias preservation and the paper-ledger TOML round trip. Prompt-IO: ai/prompt-io/opencode/20260828T013057Z_6ca6ab2a_prompt_io.md (this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`)) --- .../20260828T013057Z_6ca6ab2a_prompt_io.md | 35 ++++++++ ...20260828T013057Z_6ca6ab2a_prompt_io.raw.md | 44 ++++++++++ piker/data/_symcache.py | 5 +- tests/test_accounting.py | 82 +++++++++++++++++++ 4 files changed, 164 insertions(+), 2 deletions(-) create mode 100644 ai/prompt-io/opencode/20260828T013057Z_6ca6ab2a_prompt_io.md create mode 100644 ai/prompt-io/opencode/20260828T013057Z_6ca6ab2a_prompt_io.raw.md diff --git a/ai/prompt-io/opencode/20260828T013057Z_6ca6ab2a_prompt_io.md b/ai/prompt-io/opencode/20260828T013057Z_6ca6ab2a_prompt_io.md new file mode 100644 index 00000000..638eda02 --- /dev/null +++ b/ai/prompt-io/opencode/20260828T013057Z_6ca6ab2a_prompt_io.md @@ -0,0 +1,35 @@ +--- +model: openai/gpt-5.6-sol +service: opencode +session: opencode-pid-469264 +timestamp: 2026-08-28T01:30:57Z +git_ref: 6ca6ab2a +scope: code +substantive: true +raw_file: 20260828T013057Z_6ca6ab2a_prompt_io.raw.md +--- + +## Prompt + +Investigate Piker's clearing, accounting, order-control, and +trade-ledger flows in depth while fixing a paper-engine crash where +RapidFuzz called `len()` on a `MktPair` during ledger persistence. + +## Response summary + +Changed `SymbologyCache.search()` to fuzzy-match table keys instead of +struct values, preserving matched aliases and preventing the paper +trade dialog from crashing when qualifying legacy ledger FQMEs. Added +an end-to-end regression for the implicated ledger-write path. + +## Files changed + +- `piker/data/_symcache.py` - searches string keys and resolves values. +- `tests/test_accounting.py` - covers aliases and paper-ledger writes. + +## Human edits + +The human supplied the production traceback, identified the affected +subsystems, and directed an implementation-level fix with deep system +familiarization. No follow-up correction or direct manual source edit +was identified during this interaction. diff --git a/ai/prompt-io/opencode/20260828T013057Z_6ca6ab2a_prompt_io.raw.md b/ai/prompt-io/opencode/20260828T013057Z_6ca6ab2a_prompt_io.raw.md new file mode 100644 index 00000000..8915c976 --- /dev/null +++ b/ai/prompt-io/opencode/20260828T013057Z_6ca6ab2a_prompt_io.raw.md @@ -0,0 +1,44 @@ +--- +model: openai/gpt-5.6-sol +service: opencode +timestamp: 2026-08-28T01:30:57Z +git_ref: 6ca6ab2a +diff_cmd: git diff HEAD~1..HEAD +--- + +## Prompt + +Become familiar with Piker's clearing, accounting, order-control, +and trade-ledger tracking subsystems in deep detail while fixing a +paper-engine crash. The supplied traceback showed +`TransactionLedger.write_config()` calling +`SymbologyCache.search()`, followed by RapidFuzz raising +`TypeError: object of type 'MktPair' has no len()` after a paper fill. + +## Response + +Fixed the crash at the symbology-cache contract boundary. +`SymbologyCache.search()` now fuzzy-matches the selected table's +string keys and resolves each matched key back to its struct value. +RapidFuzz therefore never receives `MktPair` values as sequences, +and native market-ID aliases remain intact in search results. + +> `git diff HEAD~1..HEAD -- piker/data/_symcache.py` + +Added a regression that recreates the failing paper-ledger write with +an unqualified Binance perpetual FQME. It also checks a native market-ID +alias, preventing a weaker `processor=str` workaround that would lose +the actual matched key. + +> `git diff HEAD~1..HEAD -- tests/test_accounting.py` + +Verification: + +`./py313/bin/python -m pytest -p no:xonsh -q -x --tb=short +--no-header tests/test_accounting.py::test_account_file_default_empty +tests/test_accounting.py::test_paper_ledger_fuzzy_qualifies_mktmap_keys` + +Result: `2 passed in 0.02s`. + +`git diff --check` also passed. Ruff was not available in the existing +`py313` environment, so no environment provisioning was performed. diff --git a/piker/data/_symcache.py b/piker/data/_symcache.py index 1f1cb9ec..0708b46c 100644 --- a/piker/data/_symcache.py +++ b/piker/data/_symcache.py @@ -334,15 +334,16 @@ class SymbologyCache(Struct): matches in a `dict` including the `MktPair` values. ''' + entries: dict[str, Struct] = getattr(self, table) matches = fuzzy.extract( pattern, - getattr(self, table), + list(entries), score_cutoff=50, ) # repack in dict[fqme, MktPair] form return { - item[0].fqme: item[0] + item[0]: entries[item[0]] for item in matches } diff --git a/tests/test_accounting.py b/tests/test_accounting.py index 1e22c91d..d806643f 100644 --- a/tests/test_accounting.py +++ b/tests/test_accounting.py @@ -5,20 +5,26 @@ - config file IO ''' +from decimal import Decimal from pathlib import Path +from types import ModuleType +import tomllib import pytest from piker import config from piker.accounting import ( Account, + Asset, calc, open_account, load_account, load_account_from_ledger, + MktPair, open_trade_ledger, Position, TransactionLedger, ) +from piker.data._symcache import SymbologyCache import tractor @@ -47,6 +53,82 @@ def test_account_file_default_empty( assert path.parent.name == 'accounting' +def test_paper_ledger_fuzzy_qualifies_mktmap_keys( + tmp_path: Path, +): + ''' + Qualify legacy paper-ledger FQMEs via symcache string keys. + + ``SymbologyCache.search()`` previously passed its ``mktmaps`` + mapping directly to RapidFuzz. RapidFuzz searches mapping values, + so a paper fill with an unqualified FQME made RapidFuzz call + ``len()`` on a ``MktPair`` while + ``TransactionLedger.write_config()`` exited. + This test installs both native-ID and FQME keys for one market, + proves search preserves the matched alias, then writes the same + unqualified paper transaction shape. The persisted fully + qualified keys prove the ledger exit path resolves the market + without treating ``MktPair`` values as fuzzy-search sequences. + + ''' + usdt = Asset( + name='usdt', + atype='crypto', + tx_tick=Decimal('0.00000001'), + ) + nvda = Asset( + name='nvda', + atype='stock', + tx_tick=Decimal('0.00000001'), + ) + mkt = MktPair( + dst=nvda, + src=usdt, + price_tick=Decimal('0.01'), + size_tick=Decimal('0.001'), + bs_mktid='NVDAUSDT', + broker='binance', + venue='usdtm', + expiry='perp', + ) + symcache = SymbologyCache( + mod=ModuleType('binance'), + fp=tmp_path / 'binance.symcache.toml', + mktmaps={ + mkt.bs_mktid: mkt, + mkt.fqme: mkt, + }, + ) + + matches = symcache.search(mkt.bs_mktid) + assert next(iter(matches)) == mkt.bs_mktid + assert matches[mkt.bs_mktid] is mkt + + ledger_path = tmp_path / 'trades_binance_paper.toml' + unqualified_fqme = 'nvdausdt.usdtm.perp' + ledger = TransactionLedger( + ledger_dict={ + 'fill-id': { + 'fqme': unqualified_fqme, + 'bs_mktid': unqualified_fqme, + }, + }, + file_path=ledger_path, + account='paper', + mod=symcache.mod, + tx_sort=lambda txns: txns.items(), + symcache=symcache, + ) + + ledger.write_config() + + with ledger_path.open('rb') as ledger_file: + saved = tomllib.load(ledger_file)['fill-id'] + + assert saved['fqme'] == mkt.fqme + assert saved['bs_mktid'] == mkt.fqme + + @pytest.mark.parametrize( 'fq_acnt', [