You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Filed separately from #17 because this is a behaviour question, not that defect. (#17's phantom stop-out is fixed in 5.9.3-alpha — confirmation posted there.)
Setup. A small probe cBot opens one position sized to ~85% margin utilisation and simply holds it,
logging Account.MarginLevel every tick, with Account.StopOutLevel read from the engine at OnStart
(= 50.00). It sets no SL/TP and never closes or modifies the position itself, so any close or volume
reduction observed must be the engine's. Image ghcr.io/spotware/ctrader-console:5.9.3-alpha, digest sha256:0f9341ccd7005fad073f88c950d6bd5c9b71fe905bd0aa659cfe37cc745facd7 (CLI 5.9.3.0), US30, tick
data mode, balance 10000, podman 5.8.4 on Fedora.
Observed on 5.9.3-alpha. The run ends with:
CBot instance [MarginProbeBot, US30, m15] Backtesting STOPPED because Margin Level dropped below Stop Out Level.
At that moment the breach is genuine — equity 2821.25 against used margin 5784.45 = 48.77%, below
the account's 50.00 — and the position was open. But:
No Positions.Closed callback fired — 0 occurrences, so no PositionCloseReason.StopOut was ever
delivered to the cBot.
No Positions.Modified callback fired — no partial / "smart" stop-out volume reduction either.
The final report has positions.items = [] and positions.marginUsed = 0, and the single history item
has an empty closeReason.
Reproduced bit-identically on a repeat run: same abort tick index (147845), same timestamp, and an
identical report-JSON md5 (b1c8e76f8aa6bc478e616aad8c77e12a both times).
The cBot's own last samples before termination were margin level 58.3 → 55.3 → 61.3; the crossing itself
happened between ticks, so from inside the cBot the run simply ends.
For contrast, the same probe on an earlier 5.7.x CLI showed no enforcement at all: margin level fell to 7.9% against the same 50.00 threshold and stayed at or below it for 1,000+ consecutive ticks with
the position open — zero closes, zero reductions, zero aborts. (That contrast run was a local CLI install
rather than this container image, so treat it as indicative of the older behaviour rather than a
digest-pinned comparison.)
Questions.
Is terminating the backtest the intended behaviour on stop-out, rather than closing the position (or
reducing volume) and letting the run continue, as a live account would?
Should Positions.Closed fire with PositionCloseReason.StopOut in backtest? At present a cBot cannot
observe or react to a stop-out at all, which differs from live behaviour and makes stop-out handling
impossible to test.
Why it matters for us. In an A/B comparison, a margin breach silently voids a cell, and the loss is
correlated with the arm under test (the arm that draws down more loses more cells), which biases paired
results unless every cell is separately checked for the abort marker.
Attached: probe stdout for both 5.9.3-alpha runs, the report JSON, the older-engine contrast run, and a
provenance file with all image digests, binary hashes and measured values. Account identifiers are redacted.
Filed separately from #17 because this is a behaviour question, not that defect. (#17's phantom stop-out
is fixed in
5.9.3-alpha— confirmation posted there.)Setup. A small probe cBot opens one position sized to ~85% margin utilisation and simply holds it,
logging
Account.MarginLevelevery tick, withAccount.StopOutLevelread from the engine atOnStart(=
50.00). It sets no SL/TP and never closes or modifies the position itself, so any close or volumereduction observed must be the engine's. Image
ghcr.io/spotware/ctrader-console:5.9.3-alpha, digestsha256:0f9341ccd7005fad073f88c950d6bd5c9b71fe905bd0aa659cfe37cc745facd7(CLI5.9.3.0),US30, tickdata mode, balance 10000, podman 5.8.4 on Fedora.
Observed on 5.9.3-alpha. The run ends with:
At that moment the breach is genuine — equity
2821.25against used margin5784.45= 48.77%, belowthe account's
50.00— and the position was open. But:Positions.Closedcallback fired — 0 occurrences, so noPositionCloseReason.StopOutwas everdelivered to the cBot.
Positions.Modifiedcallback fired — no partial / "smart" stop-out volume reduction either.positions.items = []andpositions.marginUsed = 0, and the single history itemhas an empty
closeReason.147845), same timestamp, and anidentical report-JSON md5 (
b1c8e76f8aa6bc478e616aad8c77e12aboth times).The cBot's own last samples before termination were margin level
58.3 → 55.3 → 61.3; the crossing itselfhappened between ticks, so from inside the cBot the run simply ends.
For contrast, the same probe on an earlier 5.7.x CLI showed no enforcement at all: margin level fell to
7.9% against the same
50.00threshold and stayed at or below it for 1,000+ consecutive ticks withthe position open — zero closes, zero reductions, zero aborts. (That contrast run was a local CLI install
rather than this container image, so treat it as indicative of the older behaviour rather than a
digest-pinned comparison.)
Questions.
reducing volume) and letting the run continue, as a live account would?
Positions.Closedfire withPositionCloseReason.StopOutin backtest? At present a cBot cannotobserve or react to a stop-out at all, which differs from live behaviour and makes stop-out handling
impossible to test.
process exits 0 with a well-formed report covering only part of the requested period — the same
silent-failure shape as [5.9.0.0 regression] Backtest stops with "Margin Level dropped below Stop Out Level" while zero positions are open — residual marginUsed is exactly -1 ULP after partial closes #17 — so an automated harness records it as a successful run unless it greps
stdout for that message.
Why it matters for us. In an A/B comparison, a margin breach silently voids a cell, and the loss is
correlated with the arm under test (the arm that draws down more loses more cells), which biases paired
results unless every cell is separately checked for the abort marker.
Attached: probe stdout for both 5.9.3-alpha runs, the report JSON, the older-engine contrast run, and a
provenance file with all image digests, binary hashes and measured values. Account identifiers are redacted.
CONTROL_5.7.10.0_US30_Q1_fstar_SAMEDAY_report.json
CONTROL_5.7.10.0_US30_Q1_fstar_SAMEDAY_SUMMARY.txt
FIXED_5.9.3-alpha_US30_Q1_fstar_report.json
FIXED_5.9.3-alpha_US30_Q1_fstar_SUMMARY.txt
NONENFORCEMENT_5.7.x_local_probe_stdout.txt
PINS_AND_PROVENANCE.txt
STOPOUT_5.9.3-alpha_probe_bootstrap_stdout.txt
STOPOUT_5.9.3-alpha_probe_control_report.json
STOPOUT_5.9.3-alpha_probe_control_RERUN_report.json
STOPOUT_5.9.3-alpha_probe_control_RERUN_stdout.txt
STOPOUT_5.9.3-alpha_probe_control_stdout.txt