Metadata-Version: 2.4
Name: nullcheck-trace-bt
Version: 0.1.1
Summary: Standalone scalar execution trace producer for backtesting.py 0.6.6
Author: No Hindsight
License-Expression: MIT
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: backtesting==0.6.6
Dynamic: license-file

# nullcheck-trace-bt 0.1.1

A free, standalone scalar execution-trace producer by **No Hindsight**, licensed under MIT. It runs a separately installed [backtesting.py](https://github.com/kernc/backtesting.py) strategy and writes the public `nullcheck.execution-trace/1` JSONL format. It does not compare prefixes, discover projects, sell entitlements or require Pro.

The engine remains a separate AGPL-3.0-or-later program under its own licence. No engine implementation is copied or bundled here. This tool's original observers delegate to the unchanged engine. The protocol specification is in `nullcheck_trace_bt/SCHEMA.md` and is also MIT-licensed.

## Install and run

Python 3.10+ on an environment with **unmodified backtesting.py 0.6.6** and its dependencies is required. Provision dependencies using your normal package process, then install this wheel locally:

```sh
python -m pip install --no-index --no-deps nullcheck_trace_bt-0.1.1-py3-none-any.whl
python -m nullcheck_trace_bt run --manifest examples/capture.json --prefix 8 --out trace.jsonl
```

Output must be a new file in an existing directory. The command does not download packages or make network calls. Your strategy, imports and preparation are trusted arbitrary Python and may access external resources; this is not a sandbox. Run unknown code only in your own appropriate isolated environment. Trace files and diagnostic output may contain private values and paths.

Capture manifest version 1:

```json
{
  "version": 1,
  "strategy": "strategies.py:Fixture",
  "data": "synthetic_ohlc.csv",
  "parameters": {"mode": "clean"},
  "broker": {"cash": 10000, "trade_on_close": false, "finalize_trades": false}
}
```

Paths are relative to the manifest directory and must stay inside it; symlinks and hidden/credential paths are refused. OHLC columns are required. Optional `index_column` is parsed as timestamps and must be strictly increasing and unique. Without it, input row order is used and calendar chronology is not established. Optional `prepare: "relative.py:function"` receives a fresh truncated DataFrame and must return a DataFrame with exactly the same index. Put all preparation inside it. Parameters must be a JSON object. Use `--seed` for the supported Python/numpy seed.

Broker settings are cash, spread, commission (number or fixed/relative pair), margin, trade_on_close, hedging, exclusive_orders and finalize_trades. Defaults are 10000 cash, no spread/commission, margin 1, and false booleans. `finalize_trades=True` is unsupported: artificial endpoint liquidation is excluded. No future/fabricated row is appended to fill a pending order.

The CLI accepts 2–10000 rows and at most 32 MiB raw CSV. A run caps its trace at 100000 records and 16 MiB. An external supervisor must bound wall time, diagnostics and child processes. Exit 0 means a complete trace was written; **it is not a leak-check result**. Exit 3 means unsupported capture or trace budget; exit 4 means execution/configuration failure. Partial traces have no valid completion footer and must never be interpreted as complete empty histories.

## Capture contract and version pin

Support is pinned to engine version **0.6.6** and SHA-256 `30d8b5ed9c69e4891bd41fe6fa157dcdd9915c2aff279056c8975b0918178852` of its `backtesting/backtesting.py` source file. A changed engine or source hash is refused. New engine releases require a new reviewed capture release and parity/timing tests; do not loosen the pin yourself and assume correctness.

The observers wrap order creation/queue changes, cancellation/amendments, trade creation/splits/closes, protection changes, broker phases and Strategy init/next. A narrowly scoped Python trace hook attributes broker operations to the active order in the pinned engine; it copies no broker loop or fill logic. Existing debugger trace hooks and strategies replacing the capture hook are unsupported. Original engine methods are restored after every run.

Every event records its actual processing bar and phase. Economic entry/exit bars are separate fields: a fill processed at bar 6 can legitimately carry entry bar 5. Init events use bar -1. Pending orders and live trades are recorded as of each snapshot, without attaching future exits. Same-bar submit/cancel and partial-close events remain visible. A footer marks early broker termination, including out-of-money stops. Such a trace does not establish coverage through the requested final bar.

The supported contract is the pinned standard single-instrument engine with public strategy operations. Direct mutation of engine internals, custom brokers, debugger/capture interference, nested engines, opaque object-valued tags, detached child processes and unknown external input state are not supported. No scalar event schema can prove arbitrary strategy code has no other hidden dependency.

## Verification

```sh
python -B -m unittest discover -s tests -v
```

Tests compare instrumented and ordinary engine trade/equity tables for eight synthetic modes, including cancellation, partial closes, bracket/protection changes, rejection and warm-up changes. A separate assertion checks next-bar processing with backdated entry labels. Synthetic fixtures test software behavior; they are not market data or trading-performance evidence. Inspect `BUILD_NOTE.md` and `SHA256SUMS` for the exact release verification.

## Indicator warm-up

Version 0.1.1 records the pinned engine's `warmup_bars` on `init_end`. This observer does not change engine scheduling or results. Use a consumer supporting this optional flat field (nullcheck Pro 0.2.3 or later). Causal SMA(20) capture is checked against ordinary engine runs at 2/10/19/20/21/30/59/60 rows; short all-NaN indicator schedules are faithfully recorded. Consumers can label pre-decision windows not comparable while retaining INIT comparisons and detecting length-dependent warm-up on later prefixes. Older strict consumers refuse the extra field.
