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`))
wkt/fsp_backfill_sync
Gud Boi 2026-08-27 23:11:43 -04:00
parent 6ca6ab2a10
commit 7b51927f6b
4 changed files with 164 additions and 2 deletions

View File

@ -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.

View File

@ -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.

View File

@ -334,15 +334,16 @@ class SymbologyCache(Struct):
matches in a `dict` including the `MktPair` values. matches in a `dict` including the `MktPair` values.
''' '''
entries: dict[str, Struct] = getattr(self, table)
matches = fuzzy.extract( matches = fuzzy.extract(
pattern, pattern,
getattr(self, table), list(entries),
score_cutoff=50, score_cutoff=50,
) )
# repack in dict[fqme, MktPair] form # repack in dict[fqme, MktPair] form
return { return {
item[0].fqme: item[0] item[0]: entries[item[0]]
for item in matches for item in matches
} }

View File

@ -5,20 +5,26 @@
- config file IO - config file IO
''' '''
from decimal import Decimal
from pathlib import Path from pathlib import Path
from types import ModuleType
import tomllib
import pytest import pytest
from piker import config from piker import config
from piker.accounting import ( from piker.accounting import (
Account, Account,
Asset,
calc, calc,
open_account, open_account,
load_account, load_account,
load_account_from_ledger, load_account_from_ledger,
MktPair,
open_trade_ledger, open_trade_ledger,
Position, Position,
TransactionLedger, TransactionLedger,
) )
from piker.data._symcache import SymbologyCache
import tractor import tractor
@ -47,6 +53,82 @@ def test_account_file_default_empty(
assert path.parent.name == 'accounting' 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( @pytest.mark.parametrize(
'fq_acnt', 'fq_acnt',
[ [