Skip to content

feat(executor): the increment read goes through the port, and stops fetching 900 products for one - #563

Open
eaitbrahim wants to merge 1 commit into
mainfrom
feat-524-increment
Open

feat(executor): the increment read goes through the port, and stops fetching 900 products for one#563
eaitbrahim wants to merge 1 commit into
mainfrom
feat-524-increment

Conversation

@eaitbrahim

Copy link
Copy Markdown
Contributor

#561 gave the port get_instrument and wired nothing to it. This wires it, which was the point.

Third additive step on #524, and like the two before it no live behaviour changes — the same
increments reach the same quantization through the same cache.

The read the port was built for

_base_increment_for called broker.list_products() — about 900 rows on Coinbase — to use one
field of one of them
, then picked through raw dicts for product_id and base_increment. That
was the pre-port CoinbaseClient's only catalogue read.

CoinbaseClient.get_instrument() now answers the port's type too — the same bridge get_balances
is — so the executor asks one question whether it holds this client or a real adapter.
list_products stays exactly where it belongs: keel assets discover, which genuinely wants the
catalogue.

A caching argument that no longer applies

The old docstring defended writing one cache row per miss "even though the response carries every
product"
, because Repository.set_state commits per call and caching all ~900 would mean ~900
fsyncs inside the order-placement path. The argument was sound and the shape was not. Asking
for one product removes the temptation rather than resisting it — there is no longer a catalogue to
decline to cache.

The test that pinned it stays, with its reasoning updated rather than deleted: one venue answer must
still write one row.

And the adapter that refuses

keel-broker-alpaca raises NotImplementedError from get_instrument, deliberately (#561) —
None would tell a caller a symbol is unlisted when the truth is nobody wrote the read.

On this path that must land as unknown, not as a crash. _base_increment_for never raises by
contract: unknown means send the quantity unquantized, and an exit that refused to place because a
catalogue lookup was unimplemented would be a protective order withheld over a missing convenience.
Now pinned with a test.

Verification

4304 passed / 3 skipped, ruff clean, mypy clean across 347 files.

What's left of #524

Only order placement. Every read the executor makes — balances, increments — now goes through
port shapes and needs no further change at the flip. What remains is preview_order/place_order
taking OrderSpec instead of raw dicts, and _build_broker resolving through load_broker.

That one is not additive. It rewrites every live order path, and it's where
_bracket_order_configuration, the ConfirmFn dual arm and the capabilities() grandfather clause
finally get deleted. Worth doing as its own reviewed change rather than tacked onto this.

🤖 Generated with Claude Code

https://claude.ai/code/session_01T6yA5khYnJ2qzheArRToQ2

…etching 900 products for one

#561 gave the port `get_instrument` and wired nothing to it. This wires it, which
was the point. Third additive step on #524 and, like the two before it, no live
behaviour changes: the same increments reach the same quantization by the same
cache.

── THE READ THE PORT WAS BUILT FOR ────────────────────────────────────────────

`_base_increment_for` called `broker.list_products()` -- about 900 rows on
Coinbase -- to use ONE field of ONE of them, then picked through raw dicts for
`product_id` and `base_increment`. That was the pre-port `CoinbaseClient`'s only
catalogue read.

`CoinbaseClient.get_instrument()` now answers the port's type too, the same bridge
`get_balances` is, so the executor asks one question whether it holds this client
or a real adapter. `list_products` stays exactly where it belongs: `keel assets
discover`, which genuinely wants the catalogue.

── A CACHING ARGUMENT THAT NO LONGER APPLIES ──────────────────────────────────

The old docstring defended writing one cache row per miss "even though the
response carries every product", because `Repository.set_state` commits per call
and caching all ~900 would mean ~900 fsyncs inside the order-placement path. The
argument was sound and the SHAPE was not: asking for one product removes the
temptation rather than resisting it, and there is no longer a catalogue to decline
to cache. The test that pinned it stays -- one venue answer must still write one
row -- with its reasoning updated rather than deleted.

── AND THE ADAPTER THAT REFUSES ───────────────────────────────────────────────

`keel-broker-alpaca` raises `NotImplementedError` from `get_instrument`,
deliberately (#561): `None` would tell a caller a symbol is unlisted when the
truth is that nobody wrote the read. On THIS path that must land as unknown, not
as a crash -- `_base_increment_for` never raises by contract, and an exit that
refused to place because a catalogue lookup was unimplemented would be a
protective order withheld over a missing convenience. Pinned.

Gates: 4304 passed / 3 skipped, ruff clean, mypy clean across 347 files.

── WHAT IS LEFT OF #524 ───────────────────────────────────────────────────────

Only order PLACEMENT. Every read the executor makes -- balances, increments --
now goes through port shapes and needs no further change at the flip. What remains
is `preview_order`/`place_order` taking `OrderSpec` instead of raw dicts, and
`_build_broker` resolving through `load_broker`. That one is not additive and
rewrites every live order path.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T6yA5khYnJ2qzheArRToQ2
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