export-runner: classify a completed-but-empty backtest as a data hole, not a failure

The first full production run aborted on Copper 2017-04: a month with no data
*inside* the symbol's range. cTrader does not emit the clean "No historical data"
message for an in-range hole; it runs the backtest to its `}` summary but with a
degenerate empty result and no EXPORT_SUCCESS. run_one misread that as a transient
failure and hard-aborted after retries.

Reaching the `}` summary means the backtest completed cleanly, so the absence of an
export marker is an empty month (rc 3, skipped), not a transient failure. A genuine
transient failure never reaches `}`, which is what distinguishes the two.
This commit is contained in:
2026-06-24 13:49:50 +02:00
parent cc3f0cbd46
commit bf600334e9
2 changed files with 14 additions and 4 deletions
+9 -1
View File
@@ -24,7 +24,15 @@ outcome, instead of trusting a single stdout sentinel:
- `DATA_FOUND` / `EXPORT_SUCCESS`**success** (rc 0)
- `No historical data for the specified period`**clean-empty** (rc 3, skipped)
- anything else — timeout, `ERROR:`, a result block with no verdict, a dead stream → **transient failure** (rc 2, retried then aborted)
- the backtest reaches its `}` summary but exported nothing → **clean-empty** (rc 3, skipped).
This catches a cTrader-side **data hole** (a month with no data *inside* a symbol's
range, e.g. Copper 2017-04), which emits a degenerate empty result block
(`{ "Equity":, … }`) instead of the clean "No historical data" message — surfaced by
the first real production run.
- bot `ERROR:` (an OnStop exception, e.g. a write failure) → **failure** (rc 2)
- no `}` terminator at all — timeout, crash, dead stream → **transient failure** (rc 2,
retried then aborted). A genuine transient failure never reaches the clean `}` summary,
which is what separates it from a data hole.
A produced ZIP is validated (`testzip` + non-empty entry) **before** the `mv`, so a
truncated artifact from a cut-off run is caught instead of being promoted to a final file.