Skip to content

fix(brokers): map Robinhood's partially_filled state to OPEN - #229

Merged
eaitbrahim merged 1 commit into
mainfrom
fix/robinhood-partially-filled
Aug 11, 2026
Merged

fix(brokers): map Robinhood's partially_filled state to OPEN#229
eaitbrahim merged 1 commit into
mainfrom
fix/robinhood-partially-filled

Conversation

@eaitbrahim

Copy link
Copy Markdown
Contributor

What

STATE_TO_PORT_STATUS in packages/keel-broker-robinhood/keel_broker_robinhood/translate.py only covered Robinhood's query-filter state enum (open, canceled, filled, failed, pending). The order-response enum instead carries partially_filled and omits pending. A partially_filled order therefore fell through to_port_status's default and read as PENDING.

partially_filled now maps to OPEN: a partially-filled GTC limit is still resting and working at the venue, so OPEN is the honest status; PENDING reads as not-yet-working. OPEN is already in the port's accepted status vocabulary (see the conformance suite's status assertion).

The map now covers the union of both enums (pending was kept, not removed) since either shape could plausibly arrive in a response.

Unverified spelling

Per #198, no Robinhood order object has ever been observed live, and Robinhood's own docs disagree about which state enum applies to the order-response object. partially_filled's exact spelling is therefore doc-sourced, not confirmed. The added comment says so explicitly and names which of the two documented enums it came from, rather than presenting it as verified.

to_port_status's fallthrough-to-PENDING default is unchanged -- its docstring's reasoning (an unrecognised state means the adapter doesn't know the outcome; FAILED would falsely declare a terminal result) still holds and applies to any future unmapped state.

Latent, not a behaviour fix

Confirmed by reading keel/execution/reconcile.py: it branches only on _FILLED = "FILLED" (line 36) and _DEAD = frozenset({"CANCELLED", "CANCELED", "EXPIRED", "FAILED"}) (line 37). The status != _FILLED check at line 102 treats every non-terminal status identically -- OPEN and PENDING take the exact same "still resting, keep polling" path today. This PR corrects a factual claim about the venue's vocabulary; it does not change behaviour until some future consumer distinguishes OPEN from PENDING.

Closes #226
Refs #198

Test plan

TDD: added test_to_port_status_maps_partially_filled_to_open_not_pending, confirmed red before the fix (to_port_status("partially_filled") returned "PENDING"), green after. Existing test_to_port_status_defaults_an_unknown_state_to_pending_not_failed and test_to_port_status_defaults_none_to_pending pin that the fallthrough default is unchanged and unnarrowed.

  • uv run ruff check keel tests packages scripts -- all checks passed
  • uv run pytest -q -- 2445 passed, 1 skipped (baseline 2444 passed / 1 skipped + 1 new test; the one skip is the conformance candles probe, suite.py:254, as expected)
  • uv run mypy -- Success: no issues found in 219 source files

🤖 Generated with Claude Code

STATE_TO_PORT_STATUS took only Robinhood's query-filter state enum
({open, canceled, filled, failed, pending}), so partially_filled --
which the order-response enum carries instead -- fell through
to_port_status's default and read as PENDING. A partially-filled GTC
limit is still resting and working at the venue, so OPEN is the
honest status; PENDING misdescribes it as not-yet-working.

The map now covers the union of both enums, since either shape could
arrive. partially_filled's spelling is doc-sourced only -- no order
object has ever been observed live (#198) -- and the docs disagree on
which enum applies, so the comment names that explicitly rather than
presenting it as verified.

Confirmed latent, not a behaviour fix: reconcile.py branches only on
FILLED and the terminal/dead set, so OPEN and PENDING already take
the identical "still resting, keep polling" path. This becomes live
only once a consumer distinguishes the two.

Closes #226
Refs #198

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@eaitbrahim eaitbrahim added the fix Bug fix (groups under Fixes) label Aug 11, 2026
@eaitbrahim
eaitbrahim merged commit 5eb111d into main Aug 11, 2026
1 check passed
@eaitbrahim
eaitbrahim deleted the fix/robinhood-partially-filled branch August 11, 2026 12:16
eaitbrahim added a commit that referenced this pull request Aug 11, 2026
…the CTS scoring fix (#241)

A minor bump, not a patch, for three reasons that each require operator action or
change behaviour the deployment is currently relying on.

SCHEMA. `SCHEMA_VERSION` goes 9 -> 10 (#223). Both deployed databases are at 9 and
must be migrated before this build can use them.

BEHAVIOUR REQUIRING OPERATOR ACTION. #223 adds a second attested claim -- what
CONTRACT a venue listing is, not only what the underlying asset is. It fails closed
with no backfill, deliberately, so after this lands `keel assets screen` REJECTS
every product with `instrument_wrapper: UNATTESTED` until `keel assets
attest-instrument` is run once per product. Live trading is unaffected: rail 1 gates
buys on `config.allowlist`, not on the screen.

LIVE SCORING CHANGED. #227 fixed `is_round_number`, which returned True for every
2dp-quoted price and so handed BTC/ETH/PAXG a free CTS point on every bar. Scores on
those three assets are genuinely lower under this build than under 0.5.7.

Also ships: the Robinhood crypto adapter behind the broker port (#216/#218/#222/#229,
not wired to the live path), the TUI activity feed (#235/#237), the CTS factor
collinearity study (#224), `Preview.synthetic` at the confirm gate (#221), rail 9
seeing a bracket's own stop (#212), and CI gating merges on the `test` check
(#234/#238).

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fix Bug fix (groups under Fixes)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Robinhood STATE_TO_PORT_STATUS omits partially_filled — latent, not currently harmful

1 participant