piker/docs/manual_backfill_qualificati...

9.9 KiB

Manual Backfill Qualification

Purpose

This runbook separates provider coverage, durable NativeDB state, shared-memory hydration, and chart rendering while reproducing gappy history. Work one FQME and timeframe at a time. Preserve the original Parquet first, then qualify both repair of that evidence and a completely fresh backfill.

The read-only command used throughout is:

piker store audit FQME --period 60
piker store audit FQME --period 60 --json

It reads raw Parquet directly. It does not open a storage client, canonicalize data, publish cache state, attach SHM, or rewrite history. Positive gaps are deliberately unclassified; an observed interval is not automatically corruption or an expected venue closure.

Warning

Do not run qualification against normal user storage. The helper below requires a marked disposable root before it will seed, archive, or clear a Parquet. piker store anal and piker store ldshm are interactive and potentially mutating; they are not audit commands.

Keep the disposable root private and do not rename, replace, or symlink files under it while a helper is running. Stop the chart cleanly before every audit or comparison phase.

Load The Xonsh Helpers

Run from the repository root in the same xonsh which will launch the chart or daemon:

source snippets/nativedb_backfill_audit.xsh

Choose a disposable root and copy only the provider configuration needed for the case:

run_root = p'/tmp/piker-backfill/mnq-20260918-60s'
source_config = p'~/.config/piker'.expanduser()
bfq_init(run_root, source_config)
bfq_record_case(
    run_root,
    fqme='mnq.cme.20260918.ib',
    period_s=60,
    provider='ib',
    symptom='gappy chart during reverse backfill',
    bad_start_utc=None,
    bad_end_utc=None,
)

bfq_init() sets XDG_CONFIG_HOME in the current xonsh, so every subsequent piker and pikerd child inherits the disposable config. The effective storage path is under RUN_ROOT/xdg/piker/nativedb. It also records the exact repository root and piker executable in the qualification marker, prepends that root to child PYTHONPATH, and verifies that the bound executable exposes piker store audit. Audits fail if that executable imports another tree.

The copied brokers.toml can contain credentials. Keep the run root private and remove it manually after evidence is no longer needed.

Repository Preflight

Before every daemon start, verify the code and config inherited by child processes:

git status --short --branch
git rev-parse HEAD
python -c 'import piker; print(piker.__file__)'
python -c 'import os; print(os.environ["XDG_CONFIG_HOME"])'

Stop any daemon from the previous phase cleanly. Use a separate registry address in the disposable conf.toml when a normal Piker stack remains active.

Known-Bad Replay

Copy the original file; never move it out of normal storage:

fqme = 'mnq.cme.20260918.ib'
period_s = 60
source_parquet = (
    p'~/.config/piker/nativedb'.expanduser()
    /f'{fqme}.ohlcv{period_s}s.parquet'
)
bfq_seed(run_root, fqme, period_s, source_parquet)
bfq_audit(run_root, 'baseline', fqme, period_s)

The baseline phase writes:

evidence/baseline.json
evidence/baseline.parquet
evidence/baseline.manual.json

The JSON checksum must match the read-only snapshot, and the manual sidecar binds that report to its phase and case. Comparison rechecks those bindings before reading either phase. The report records raw schema, row and timestamp counts, non-finite values, ordering, duplicate excess, canonical index state, and every retained positive-gap detail. Files with invalid Parquet encoding can not produce a JSON report, but --snapshot still preserves their exact bytes before parse failure.

Launch the chart manually from the same shell and capture logs:

piker -l info chart mnq.cme.20260918.ib \
    2>&1 | tee @(run_root / 'logs' / 'repair-chart.log')

After a clean stop:

bfq_audit(
    run_root,
    'repair',
    fqme,
    period_s,
    require_structural=True,
)
bfq_compare(run_root, 'baseline', 'repair')

The comparison fails if structural validity regresses, any original timestamp disappears, chronological endpoints shrink, timestamps become duplicated or unordered, indexes cease to be canonical, or OHLCV becomes non-finite. Provider replacement of matching seam rows is reported separately as changed_common_timestamps for operator review.

Fresh Backfill

Yes, fresh qualification means saving the known-bad evidence and then starting with no active Parquet in the disposable NativeDB. Archive only the disposable file:

bfq_clear_for_fresh(run_root, fqme, period_s)

Confirm the printed active path is absent, launch the same chart, wait for bounded provider exhaustion or the intended coverage, then stop cleanly:

bfq_audit(
    run_root,
    'fresh',
    fqme,
    period_s,
    require_structural=True,
)

Do not compare baseline to fresh as a timestamp-preservation gate: provider limits may intentionally produce a different initial extent. Treat fresh as the baseline for restart and append qualification.

Restart And Append

Restart the same chart against the fresh persisted file, stop cleanly, and record:

bfq_audit(
    run_root,
    'restart',
    fqme,
    period_s,
    require_structural=True,
)
bfq_compare(run_root, 'fresh', 'restart')

Leave a subsequent run active long enough to append new samples, stop, and record:

bfq_audit(
    run_root,
    'append',
    fqme,
    period_s,
    require_structural=True,
)
bfq_compare(
    run_root,
    'restart',
    'append',
    require_newer=True,
)

The comparison JSON reports gaps added and removed. Expected closures stay visible until a venue-aware classifier proves their session alignment.

Gap Layer Triage

Use the persisted report and chart together:

Observation Suspected layer
Parquet contiguous, chart gappy SHM hydration, sampling, or chart render
Parquet gap, provider has bars

provider-delta or storage merge

Provider and Parquet both gappy provider omission or venue closure
Gap vanishes after restart SHM publication or cache lifecycle
Old timestamps disappear destructive persistence regression
Newest seam duplicates conflict or dedupe policy

Always record exact UTC gap endpoints. The audit's numeric epoch is authoritative; the *_utc fields are deterministic display values.

Expansion Matrix

Expand only after the fixed FQME passes fresh, restart, and append. Use a new disposable root and case record for every row:

Case Period Coverage model
Original IB future 60 seconds CME maintenance and weekend closures
Original IB future 1 second high-frequency provider limits
Second CME future 60 seconds contract-specific history
IB equity 60 seconds regular overnight and weekend closure
IB FX 60 seconds 24x5 session behavior
Crypto spot/perp 60 seconds continuous-session control
Expired future 60 seconds history-only and expiry behavior

A sourceable matrix can remain ordinary Python data in xonsh:

cases = [
    {
        'name': 'mnq-60s',
        'fqme': 'mnq.cme.20260918.ib',
        'period_s': 60,
        'provider': 'ib',
    },
    {
        'name': 'mnq-1s',
        'fqme': 'mnq.cme.20260918.ib',
        'period_s': 1,
        'provider': 'ib',
    },
]
for case in cases:
    case_root = p'/tmp/piker-backfill' / case['name']
    bfq_init(case_root, source_config)
    bfq_record_case(
        case_root,
        fqme=case['fqme'],
        period_s=case['period_s'],
        provider=case['provider'],
        symptom='manual expansion case',
    )

This loop prepares isolated case roots only. Run the complete seed or fresh, chart, audit, restart, append, and compare lifecycle in each root; directory creation alone is not qualification.

The chart path may allocate companion 1-second and 60-second files. Audit the selected case path explicitly and retain companion files as secondary evidence; do not imply that the live request isolated one timeframe.

Interruption Qualification

After normal restart is stable, interrupt one phase with graceful Ctrl-C, restart, and compare against the last clean evidence. Provider disconnect, SIGTERM, and deterministic write-barrier failures come next. Avoid timing a SIGKILL by hand; add an explicit failpoint before qualifying atomic replace boundaries.

Result Interpretation

structural_ok means the raw persisted file satisfies durable schema, finite values, positive unique ordered timestamps, canonical index, and period-aligned gaps. gap_free means no positive gaps were observed when at least two valid timestamps made cadence verifiable; otherwise it is null. qualification_ok combines both and is intentionally strict.

A structurally valid session-market file can have gap_free = false. Those intervals require provider/session evidence before being marked as expected closures. Unknown calendar state must stay unknown.