FakeTradingSession — cTrader Open API fidelity contract
tests/UnitTests/CopyTrading/FakeTradingSession.cs = in-memory IOpenApiTradingSession που
όλα τα copy-trading unit tests τρέχουν ενάντια. Job: mimic real cTrader Open API server
close enough ώστε unit tests να καλύπτουν behavior που μόνο live tier μπορούσε να πιάσει.
Αυτό το doc = fidelity contract: τι modelάρει το fake, πόσο faithful, και κανόνας για να
παραμένει honest.
Binding rule (CLAUDE.md): το fake παραμένει cTrader-faithful. Επεκτείνετέ το, ποτέ μην το αποδυναμώσετε για να περάσει test. Κάθε νέα πραγματική συμπεριφορά που βασίζεστε πρέπει να modelάρεται εδώ, καρφωμένο από fidelity test.
Fidelity matrix (F1–F13)
Παρακολουθεί το plan plans/copy-trading-overhaul.md §7.6. Legend: ✅ modeled · ◑ partial
(opt-in / extending) · ⬜ not yet modeled.
| # | Real Open API behavior | Fake status | How it is modeled |
|---|---|---|---|
| F1 | Market order can partial-fill | ◑ | PartialFillFractionForCtid[ctid] = f fills only f×volume; reconcile then shows gap Phase‑1 true‑up (G5) closes. Accept→fill event pair still to come. |
| F2 | Volume normalized to step, rejected below min / above max | ✅ | VolumeBoundsForCtid[ctid] = (Step, Min, Max) rounds down to step, throws CtraderRejectException(VolumeTooLow/High). |
| F3 | Invalid SL/TP rejected (side + digits) | ⬜ | Planned Phase 0a/1 (pairs with M6 SL/TP precision normalization). |
| F4 | Prices integer-scaled by digits; pipPosition | ◑ | SymbolDetails now carries Digits (and MaxVolume), populated from real symbol; PipPosition drives market-range tolerance, Digits drives SL/TP precision normalization (M6). Full integer price scaling still pending. |
| F5 | Market-range fills only if spot within base ± slippage, else rejects | ✅ | IsMarketRangeRejected compares live spot (SetSpot) to baseSlippagePrice ± slippageInPoints. Legacy RejectMarketRangeForCtid flag still forces reject. |
| F6 | Pending trigger→fill dual event (Order carries positionId + OPEN Position) | ◑ | PushOpen(..., orderId:) reproduces filled-pending event; FX‑Blue/cMAM double-copy dedupe covered in CopyEngineHostTests.Filled_pending_does_not_double_open. |
| F7 | Server-driven closes (SL/TP hit, stop-out) | ⬜ | Today closes test-pushed (PushClose); price-driven SL/TP-hit + stop-out closes planned. |
| F8 | Per-account symbol tables / details | ◑ | Symbol names/ids per-fake; per-account divergent tables (cross-broker) pending. |
| F9 | Full account state (balance, equity, margin, freeMargin) | ◑ | Balance + LoadPositionValuationsAsync (entry/swap/commission via SetPositionValuation) + SetSpot feed real equity into proportional-equity sizing (G2, unit-tested in CopyEquitySizingTests). Used margin not exposed by reconcile API, so free-margin reported as equity. |
| F10 | Events carry server timestamps | ✅ | ExecutionEvent.ServerTimestamp (unix ms) — real session reads from deal's ExecutionTimestamp; PushOpen/PushPending accept serverTimestamp: so FakeTimeProvider-driven test drives real copy latency (G1). |
| F11 | Trading mode / schedule (disabled / close-only / closed) | ⬜ | Planned Phase 2b. |
| F12 | Typed error taxonomy (ProtoOAErrorRes codes) | ✅ | RejectReasonForCtid[ctid] = CtraderRejectReason.X throws one-shot CtraderRejectException(reason) (NotEnoughMoney, MarketClosed, PositionNotFound, …). |
| F13 | Token invalidation — stale token → auth error | ✅ | InvalidateToken(ctid) marks attached token stale; trading calls throw real OpenApiException with OpenApiErrorKind.TokenInvalid (code CH_ACCESS_TOKEN_INVALID), exactly like live server, until SwapAccessTokenAsync installs fresh token. Feeds M1 token-robustness test. |
Fidelity tests live in tests/UnitTests/CopyTrading/FakeTradingSessionFidelityTests.cs.
Opt-in, defaults preserve legacy behavior
Κάθε fidelity knob off by default ώστε το fake κρατά simple always-fill behavior για tests που δεν νοιάζονται. Test opts in per account:
session.VolumeBoundsForCtid[slave] = (Step: 10, Min: 10, Max: 1000); // F2
session.PartialFillFractionForCtid[slave] = 0.6; // F1 / G5
session.RejectReasonForCtid[slave] = CtraderRejectReason.NotEnoughMoney; // F12 (one-shot)
session.InvalidateToken(slave); // F13
Characterization + conformance (planned, keeps the fake ≡ real)
Δύο μηχανισμοί κρατούν το fake honest έναντι moving real server (tracked, landing across Phase 0a):
- Live characterization (
LiveApiCharacterization, demo accounts, secrets-gated,Inconclusiveon closed market): drive real Open API, record exact wire truth (event sequences, scaling, reject codes) into golden fixtures checked into test project. No secrets in fixtures — only observed shapes. - Conformance harness: run same scenario suite twice — once against
FakeTradingSession, once against live session (when secrets present) — assert identical observable outcomes. Real server changes → live leg fails → update fake. This makes "unit tests cover everything" trustworthy.
Live credentials: secrets/dev-credentials.local.json (ή legacy split files) — δείτε
docs/testing/dev-credentials.md.