docs(experiments): assess cTrader Open API and refuse it on charter grounds - #203
Merged
Conversation
…rounds cTrader has surfaced as a candidate venue. This records why it is refused, so the question does not get re-litigated from scratch. The finding is structural, not a missing adapter. Every account cTrader Open API can express is a leveraged, short-permitting, financing-bearing CFD/FX account: `leverageInCents`/`maxLeverage` are core `ProtoOATrader` fields, `usedMargin`/`marginRate`/`swap` are baseline on every `ProtoOAPosition`, and `ProtoOATradeSide` SELL opens a short rather than disposing of a holding. There is no message anywhere in the protocol for an unleveraged spot holding, and no cash-balance-in-asset concept -- only deposit-currency P&L. The `swapFree` "Shariah account" does not rescue it: it substitutes a daily `rolloverCommission` for swap, and touches neither the leverage, the short symmetry, nor the absent custody. Mapped onto our own gates, three refuse it independently -- rail 17 (`qabd`/withdrawal, fails closed with nothing to ever withdraw), rail 19 (spot-only, whose comment states outright there is no config field to widen), and the curation screen (`riba_yield`, `dayn` backing). None of these move when the broker-port migration lands, which is why the doc separates that temporary blocker from the charter one rather than conflating the two. Deliberately no package, dependency, entry point or conformance test: an adapter for a venue every rail vetoes would be dead code that reads as capability -- the mistake rail 19's own notes record having made once already. Evidence is a documentation review, not a live probe; no account was opened and the doc says so rather than dressing it up as empirical. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…eview An independent review checked every citation and found the verdict sound but three of the gates described more strongly than they are. Correcting them matters more than the conclusion did -- a refusal argued from defences that do not hold is worse than no document. - Rail 17 is entries-only and one of two `LIVE_STATE_RAILS`, so it is skipped in paper; "vetoes every BUY unconditionally, forever" was wrong. Worse, the input is the OPERATOR's attestation (`executor.py:234-257`), not anything an adapter reports -- so it fails closed by default but is operator-honesty, not adapter-honesty. Said plainly rather than overclaimed. - Rail 19's grammar check does NOT fail cTrader: `EUR-USD` parses cleanly. This is exactly the two-segment residual `guards.py:725-736` already documents. The rail contributes its CHARTER comment, not its check, and the doc no longer contradicts its own table on this. - The curation screen is not a reliable gate against a CFD wrapper at all: `AssetAttestation` is keyed on the base asset with no field for an instrument wrapper, so a BTC CFD reuses BTC's admitted spot attestation. Recorded as an open modelling gap instead of a defence we do not have. - `BrokerCapabilities(asset_classes=frozenset())` constructs fine; the non-emptiness comes from `conformance/suite.py:89-98`, not `__post_init__`. Two additions the review argued for, both load-bearing: - A real `KB basis:`. The claim that this was "architectural, not a fresh halal question" threw away the strongest evidence in the repo. §56.1 as corrected by §66.3 is a written ruling on precisely this product -- retail "spot" FX perpetually rolled to avoid delivery, interest charged for the deferral -- with §66.3's "transfer of property, substantively or constructively" as the citable litmus test. §28.1-28.2 name CFDs outright. That converts the central claim from inference to citation. - A "(c) market data only" branch. Every gate here is execution-side, so a read-only feed trips none of them and would not touch the broker port. Answered on cost/benefit -- dealer-quoted marks, unmaintained SDK, disproportionate onboarding -- and explicitly NOT charter-barred, because claiming otherwise would be the same overreach as the three above. Also drops volatile GitHub star/issue counts, widens the evidence statement to name PyPI/GitHub, and fixes a precedent misattributed to `guards.py` that actually lives in the Coinbase study. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This was referenced Aug 9, 2026
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.
cTrader came up as a candidate execution venue. This records the assessment and the answer — no — so the question does not get re-litigated from scratch. No adapter, no dependency, no entry point: the deliverable is the finding.
Closes #201.
The finding
cTrader Open API has no unleveraged spot-ownership primitive. It is not a spot venue with leverage bolted on — leverage, margin, short-symmetry and cash-only P&L are load-bearing in every core message type, and no configuration of the account or the API removes all four at once:
ProtoOATradercarriesleverageInCents/maxLeverageas always-present account fields;ProtoOAPositioncarriesusedMargin/marginRate/swapas baseline fields on every position.ProtoOATradeSideSELL opens a short — it is symmetric with BUY, not a disposal of something held. There is no "sell what you own" path.swapFree"Shariah account" does not rescue it: it substitutes a dailyrolloverCommissionfor swap, and touches neither the leverage, the short symmetry, nor the absent custody. It is the answer to one question out of four, and not the one that matters.The KB already rules on this product class directly, which is what turns the argument from inference into citation: §56.1 as corrected by §66.3 — retail "spot" FX is "perpetually rolled to avoid the actual delivery of the currency," with the interest differential charged for the deferral, so a "spot" label is not evidence of spot settlement. §66.3's litmus test — "transfer of property, substantively or constructively" — is exactly what cTrader fails. §28.1–28.2 name CFDs outright in the gharar exclusion set.
What this changed about our own defences
The assessment was independently reviewed, and the review's most useful finding was that the first draft described keel's gates as stronger than they are. Those corrections are in this PR and matter more than the verdict did — a refusal argued from defences that do not hold is worse than no document:
LIVE_STATE_RAILS, so it is skipped in paper. Its input is the operator's attestation, not anything an adapter reports — it fails closed by default, but it is operator-honesty, not adapter-honesty.EUR-USDparses cleanly as a spot id; this is the two-segment residualguards.pyalready documents. The rail contributes its charter comment, not its grammar.AssetAttestationis keyed on the base asset with no field for an instrument wrapper, so a BTC CFD reuses BTC's admitted spot attestation. Filed as AssetAttestation cannot express an instrument wrapper, so the curation screen cannot see a CFD #202 rather than papered over.So the honest count is two gates plus the port's own vocabulary, not three independent gates.
Market data, answered rather than dodged
cTrader's market data is genuinely good, and every gate above is execution-side — so a read-only feed trips none of them and would not touch the broker port at all. The document says so and answers on cost/benefit instead: dealer-quoted marks rather than exchange prints, cross-venue basis against the Coinbase spot universe keel actually trades, an SDK with no release in ~2 years, and disproportionate onboarding. Not worth it today, but explicitly not charter-barred the way execution is — claiming otherwise would be the same overreach the review caught three times.
Evidence, stated honestly
A documentation review, not a live probe. No account was opened, no application registered, no connection made. Protocol claims cite
help.ctrader.com/open-api/*; SDK-maintenance claims cite PyPI and GitHub. There is no re-runnable probe script here, unlike the Coinbase asset-class study, and the document says so rather than implying empirical evidence it does not have.Why nothing was built
An adapter for a venue every rail vetoes would be dead code that reads as capability — it would imply cTrader is a live option pending only wiring. Note this is not a general rule against unwired adapters:
keel-broker-robinhood(#192) is deliberately built ahead of the broker-port migration, and it passes the charter gates cTrader fails. The distinction is charter compatibility, not wiring.Markdown-only; no
.py,.tomlor test file touched.