Fast-forward main onto the fork - #409
Open
userFRM wants to merge 1514 commits into
Open
Conversation
This was referenced Aug 26, 2026
Closed
Closed
Closed
Closed
Closed
…f the message The venue states no length for a handful of series; its own reader takes what that series' shape needs and says how much it used, so where such a record ends is the reader's to know. This client treated the remainder of the message as the record's payload, on the reading that a message carries exactly one of them. Nothing in the venue's framing says that, and a message may carry more behind one. Nothing here reads any of those series, so the rest of the message now goes unread rather than being handed on as one record's payload with the records behind it mistaken for part of it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C9qDHkbkXiSNP7gjQ7PXxc
An advisor places for several models inside one account, and every report says which. Nothing read it, so each fill and each order came back attributed to the account at large and a caller could not tell one model's position from another's. It is stated on the same tag an order states it on going out, beside the account. A report naming an account-only specification states no model whatever else it carries, so the field is left empty there rather than filled from a tag that is about something else. The default sleeve is not stated on an order going out, but a report that names it is naming it, and what arrives is what a caller is told. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C9qDHkbkXiSNP7gjQ7PXxc
The price stream carries two entries this client stepped over: one saying whether each side may be dealt on without a human, one saying whether the quote is a pre-open indication and whether either side has run past its limit. Every bid and ask therefore went out with all three attributes false, whatever the venue said, and the note beside the delivery claimed the feed carries no attributes at all. The venue states both sides in one mask and a caller is owed an attribute beside each price, so the side is read off the number the price goes out under — including the delayed numbers, which are the same two prices from another feed. Pre-open is stated once and belongs to both. They are kept beside the quote rather than in it. A quote is read on the hot path and sized to the cache lines it occupies; these change seldom, so they are held where widening costs nothing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C9qDHkbkXiSNP7gjQ7PXxc
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C9qDHkbkXiSNP7gjQ7PXxc
The venue keeps an average option volume for each side and publishes the two added; either side left unstated leaves the whole figure unstated rather than counted as none. For a share yet to open it states up to three prices — the top, the middle and the bottom of its estimate — with a flag word saying which of them stand. The middle stands while the estimate does; the figure it finally opened at stands once the flags say it is final. Neither is published while the venue's own not-stated value is in the field. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C9qDHkbkXiSNP7gjQ7PXxc
The venue sends them as compressed text behind an eight-byte header it does not describe. Inflated and trimmed, they go out as the one string the reference client publishes them as. Held to what one inflated payload may become, as every other inflate here is: what arrives is bounded on the wire and what it becomes is not. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C9qDHkbkXiSNP7gjQ7PXxc
…s them in A ledger reply is not the name-and-value stream the other account messages are. It opens a bucket, names the currency it is in, and states each figure on a tag of its own. Read as name-and-value it matched nothing, so the standard way to ask for per-currency cash, exchange rate and profit came back with no rows — which a caller cannot tell from an account holding no cash at all. Each figure now goes out under the name the venue publishes it by, in the currency of the bucket it was stated in, at least two places and at most seven, as the venue writes them. An insured-deposit balance is stated apart and belongs to the cash balance unless the session splits the two, which this one does not, so it is added in rather than published beside it. The name is a ledger figure after all and goes back into the set a ledger request answers with. Five tags the venue states have a place in its own formatter and no name in the map it publishes from. They are left out. A figure with a name invented for it is worse than a figure withheld, because a caller reads it as the venue's. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C9qDHkbkXiSNP7gjQ7PXxc
A report carries every term the order holds, and this client read the handful that identify it. So an order read back from the venue came back as the defaults for the rest — no display size, no trigger method, not hidden, no discretionary amount, no clearing account — whatever the venue was actually working. A caller reading an order it had not placed itself, or reading one back after a reconnect, was handed a different order from the one on the book. Fifty-three more terms are read now, each onto the field a caller already has for it, and each only where the report states it: a term the report does not mention is one the order does not carry, and the default already says so. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C9qDHkbkXiSNP7gjQ7PXxc
…eclares This engine states when the venue said it, as the current reference client does. ib_async predates that argument and declares four, so a wrapper attached to this engine was handed five and raised on the first error or notice of the session — and a callback that raises there closes the session, which turned any refusal at all into a disconnection. Their wrapper is handed four now. The engine's own wrapper keeps the five it shares with the reference client, because that is what a caller writing against the current API expects. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C9qDHkbkXiSNP7gjQ7PXxc
The models a caller builds an order from carry this API's own "not set" value as the default for a trailing percentage, so an ordinary limit order arrives with it in that field. Checked as a number it fails every bound there is, and every order that never mentioned a trailing percentage was refused for the one thing it did not say. Exempted the way the dozen price fields beside it already are. What the check exists for — a percentage that is not a number, or negative, or past what the wire carries — still fails. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C9qDHkbkXiSNP7gjQ7PXxc
The venue states a rule id for each venue a contract trades on, and a caller reads the ids and the venues side by side to know which price increment applies where. This client parsed the field into a single number, so a contract listed on several came back with none at all: the parse of a list fails, and what a caller read was empty. The field is kept as the venue stated it. It is still read as a number as well, where it is one, because the rule lookup takes a number and a contract on a single venue states a single id. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C9qDHkbkXiSNP7gjQ7PXxc
The venue states a suggested size for the contract itself, apart from any rule, and the suggestion is the larger of that and the size the contract's rule deals in. This client stood it on the rule alone and never read the contract's own figure, so a contract dealt in fractions suggested a ten-thousandth of a share where the venue suggests one. The note beside the field claimed the counterpart falls back to the rule where nothing separate is stated. It does not; it takes the larger of the two. The note now says that. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C9qDHkbkXiSNP7gjQ7PXxc
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C9qDHkbkXiSNP7gjQ7PXxc
Two fields the venue states and this client dropped. A contract's definition names the reasons it may not be dealt in by number, and the reply names those numbers once, after the last contract in it. Read record by record only the last contract could say what its own numbers meant, so the table is read from the whole reply and every contract in it is named from that. Callers read them as `ineligibilityReasonList`, which raised an `AttributeError` before. A report names who entered the order on tag 109. It was being read as the number of the client that placed it, which it is not: a report naming a person left the order under client nought and lost the name as well. It now reaches `Order.submitter` and `Execution.submitter`, and the account the order sits in no longer stands in for the person who entered it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C9qDHkbkXiSNP7gjQ7PXxc
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C9qDHkbkXiSNP7gjQ7PXxc
…s on its own callback Two answers the venue sends that reached nobody. The venue answers an advisor configuration request with its own reply, carrying back the number the request went out under and either the configuration itself or, for a replacement, whether it stands. Nothing read it, so `receiveFA` and `replaceFAEnd` were documented as callbacks nothing fires. They fire now, matched to the caller by that number, and a venue that will not take a replacement says why on `error` under the code the reference client reports a failed save on. A lookup for fixed income — a bond, a bill, and the type the venue spells `FIXED` — is answered on `bondContractDetails`, not on `contractDetails`. It was answered on the ordinary one, so a program written against the reference client waited through its own answer. A bill and a fixed-income contract now also file their date as the details' maturity, as a bond already did, because the venue answers all three the same way. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C9qDHkbkXiSNP7gjQ7PXxc
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C9qDHkbkXiSNP7gjQ7PXxc
…eads Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C9qDHkbkXiSNP7gjQ7PXxc
Every one of these was asked for, served by the venue, and stepped over: the payload arrived and was logged as something nothing here reads. Now read, each under the number a caller reads it by: the extremes of the last quarter, half-year and year with the ordinary day's volume; an index's premium over its future; the mark, under either of the two numbers it is asked for by; what the contract pays out; a fund's value per share — last, frozen and the day's two extremes; what has traded over the last three, five and ten minutes; and what traded on a trade report, stated as a trade rather than as the totals it is read from. The running-volume reader already did that last computation for the other series and now keeps a baseline per series as well as per contract: one baseline for both stated each series' trade against the other's totals. Two things the venue does that this client was doing differently. A figure it does not hold is stated as the largest a double carries, and on the callback that takes a plain number nothing at all is sent rather than that figure. And a record that states no length of its own is not unreadable — its width is its reader's to know, which is how the mark is read. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C9qDHkbkXiSNP7gjQ7PXxc
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C9qDHkbkXiSNP7gjQ7PXxc
…tion A caller who watches a contract, stops, and watches it again was handed a print measured from a total the venue stated in the earlier subscription: everything that traded in between as one trade, or a negative number of shares where the venue had started its day over. The baseline is released with the contract now, and with the connection that issued it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C9qDHkbkXiSNP7gjQ7PXxc
On the callback that takes text the reference client sends a reading only where there is one, and an empty string is not a reading. Sent anyway, a program reading the dividend line or the ratios was handed a blank to parse. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C9qDHkbkXiSNP7gjQ7PXxc
… does A program that asks for delayed data is told the feed is delayed and then reads its option model under the number the reference client gives a delayed one. It was arriving under the live number, indistinguishable from a reading the caller had not been given. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C9qDHkbkXiSNP7gjQ7PXxc
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C9qDHkbkXiSNP7gjQ7PXxc
It rides the auction record, past the auction's own type and six figures the venue keeps for itself. Three of the record's fields were read and the rest stepped over, so a caller watching an auction was told what was crossing and which way, and not the one figure the venue is obliged to state. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C9qDHkbkXiSNP7gjQ7PXxc
…eduled Three tests spawn a thread to answer a registration and then wait the one millisecond a test states when it wants the wait to fire. Under a loaded suite the answer arrives late, the caller reads it as an engine that went away, and the answering thread dies on a send nobody is listening for. All three now state a wait that outlasts being scheduled, and none of them dies over a caller that stopped waiting. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C9qDHkbkXiSNP7gjQ7PXxc
The least size was read only off the tag the venue states for a contract dealt in whole units, so every fractional contract came back saying nothing about the smallest order it takes — the tag is for the other kind. A fractional contract takes it from its own size rule, where the least that can be dealt and the step between sizes are one figure. One case is left out and said so in the source: the venue holds a fund to whole units despite a fractional rule, on a flag this client does not read. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C9qDHkbkXiSNP7gjQ7PXxc
…t way A quote entry whose number does not fit five bits states it in a byte, then states how far to move the decimal point in four bits and how wide the value is in four more. This read the two nibbles as one byte of width, and they multiply: a value two places out and four bytes wide read as thirty-six bytes wide, which is wider than this decoder takes — so the entry was stepped over by two hundred and eighty-eight bits instead of thirty-two, and every tick behind it in the message was decoded out of step. The shift is carried on the entry. An entry that states one is not counted in the contract's own increments at all — the venue divides by ten to that power instead — so it is left out rather than published as a number nobody sent, and said once so the gap is visible. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C9qDHkbkXiSNP7gjQ7PXxc
Two more series a caller can ask for, and the first of the kind whose record says where it ends rather than stating a length in the framing. This client abandoned the rest of the message on one of those, so the mark arrived and reached nobody; the record is handed over now and read. The price is the field numbered two, on the venue's own field grammar — the same grammar the quote stream is written in, so it is read in one place now rather than twice. A word of flags numbered thirteen carries the venue saying the price does not stand, and one that says so is not published. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C9qDHkbkXiSNP7gjQ7PXxc
The two prices nobody has to deal in round lots at, their sizes, and where each is quoted. It arrives on a record that says where it ends rather than stating a length, which this client abandoned rather than read, so a caller who asked for it was told nothing at all. Where each side is quoted is read off the same bits over the same venue list the ordinary quote's exchanges are read from. A price with no size behind it is left out: the venue states one and withdraws it by leaving the size at nothing, so a side is handed over only where there is something to deal in. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C9qDHkbkXiSNP7gjQ7PXxc
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This is
userFRM:main, offered as a fast-forward.TL;DR
main..upstream/mainis 0, nothing of yours rewritten or droppedgit reset --hard 9367845Merge safety
git merge-base upstream/main main=9367845= the tip ofmainhere.git rev-list --count main..upstream/main= 0 — nothing to resolve, no conflicts.git reset --hard 9367845.7cfa6e56(see Taking it).How it was tested
Orders — 110 fixes
Python surface — 74 fixes
connectAckstill received the accounts and the next order idKeyboardInterrupt/SystemExitswallowed in callbacksEngine — 65 fixes
Historical data — 40 fixes
ADJUSTED_LASTTRADESmeant something other than the vendor'sTRADESMarket data — 41 fixes
tickReqParamsSession, second factor, connection — 41 fixes
Protocol — 17 fixes
Account, contracts, options — 42 fixes
con_id=0reported as a contractWhat the gateway does that this did not — 15
The sweep above asks where this client is more than the gateway. This one asks the reverse,
against the decompiled build: what does the gateway do that a caller here cannot reach? Every
row was verified by three independent readers before it was written.
Thirty-nine further differences were found and left alone, each recorded with what closing it
would take. Most are one shape: a family of order types, or a request whose answer has nowhere
to go — a new type, a new channel, or a refactor across files rather than a smaller diff.
Matching the gateway rather than improving on it — 13
This client stands in for the gateway, so a caller who switches must get the same
answers. In each of these it was being more careful than the thing it replaces.
Kept deliberately, with reasons: the SRP group check, the server-proof requirement
and the DH range check, since no caller can see them and dropping them would only
widen what a hostile peer can do; and the permanent-id derivation, because the
gateway's own parse throws on this venue's identifiers and zeroes the field.
Known gaps
request_fa/replace_fareach the server; their reply is not parsed. Pinning the shape needs an advisor account I do not have. These are the 1 of 78 not served on both languages.Taking it
7cfa6e56("tests move beside the code they test"), which moves twelve source files into directories. Commits before it are written against your current layout and cherry-pick with ordinary conflicts; commits after it assume the new paths.