Skip to content

5.9.3-alpha: backtest terminates on stop-out instead of closing the position; no PositionCloseReason.StopOut delivered to the cBot #18

Description

@Bllacky

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.

  1. 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?
  2. 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.
  3. If termination is intended, could the report JSON carry an explicit machine-readable marker for it? The
    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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions