Skip to content

Apply syminfo.mincontract as the engine's lot grid in the harness - #21

Closed
luisleo526 wants to merge 1 commit into
mainfrom
syminfo-mincontract-grid
Closed

luisleo526 wants to merge 1 commit into
mainfrom
syminfo-mincontract-grid

Conversation

@luisleo526

@luisleo526 luisleo526 commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

What

docker/run_json.py (--syminfo FILE, entrypoint env PINEFORGE_SYMINFO) now applies the instrument lot-size grid from the catalog's syminfo.mincontract, so the engine floors order quantities to it, as the MCP servers already do.

Without the grid, a percent-of-equity strategy trades sub-lot "dust" quantities that TradingView never shows. Example (BTCUSDT 4h, EMA 20/50 crossover, 100 % of equity, 0.1 % commission, 2,188 bars): 27 trades without the grid, 22 with it; 5 of the 27 are below one lot (about 3e-08 BTC).

Behaviour

  • mincontract is read from the --syminfo JSON (flat object or {"syminfo": {...}}; a catalog object can be passed as is, other keys are ignored).
  • Absent or null: nothing changes, no setter is called.
  • A JSON number that is finite and greater than zero: strategy_set_syminfo_metadata(strat, "qty_step", v) (the engine floors order quantities to this grid), then strategy_set_syminfo_metadata(strat, "mincontract", v) (so syminfo.mincontract reads in a script return the catalog value, as on TradingView), before mintick, pointvalue, timezone and session, after inputs and overrides.
  • Anything else (0, -1, "0.001", true, NaN, Infinity, a list, an object): rejected, never applied. The harness prints one line {"engine":"pineforge","error":"syminfo.mincontract must be a positive finite number, got <value>"} and exits 1 (the entrypoint exits 4); <value> is the value's JSON text cut to 80 characters.
  • A strategy library without strategy_set_syminfo_metadata fails the same way when mincontract is present. The harness never runs without a grid it was given.
  • An applied grid is recorded as applied_runtime.syminfo = {"qty_step": v, "mincontract": v}, so it is in the report and in fingerprint.provenance.runtime: the fingerprint digest of a run with the grid differs from the gridless run's (its trades differ). Without a grid the key is absent and the report, fingerprint included, is unchanged (see Evidence). mintick, pointvalue, timezone and session stay unrecorded, as before.

Evidence

Release image built from this branch (engine 1.0.1, codegen 1.0.1) on a clean Linux x86_64 host; the published 1.0.1 image is the baseline (it is today's main).

mincontract null or absent mincontract 1e-05
rows (report.trades) 27 22
rows below one lot 5 0
first trade quantity 0.081668384426108 0.08166 (the engine reports 8166 lots as 0.08166000000000001)
every quantity a whole number of lots no yes
applied_runtime.syminfo absent {"qty_step": 1e-05, "mincontract": 1e-05}
fingerprint.digest sha256:e4315ec21e0717ad7d1f06f67d671743e162480b4d3b3464b48587a0a2d86f69 sha256:b9e00cb9b60f54352e8a3243eb77bb8257ef53e408ba83483aba9c0a19aaa503
  • The same numbers through the image entrypoint and through an engine service built on the image (POST /backtest, public mode, the catalog syminfo object sent as is), row by row against the rows of an earlier engine 1.0.1 run (8 columns, 1e-9 relative).
  • On the unchanged main image the grid case returns the gridless 27 rows (the key is ignored): the end-to-end run fails there for that reason and passes on this branch.
  • Byte identity: for runs without a grid (no syminfo file; only mintick/pointvalue/timezone/session; the full catalog object with mincontract: null; the same with the key absent) the baseline image and this branch's image give identical stdout bytes, fingerprint.token and fingerprint.digest included, once elapsed_seconds (the run time) is set to 0.
  • Rejections: -1, 0, "0.001", true exit 4 with the one-line error above.
  • Tests: python3 tests/test_fingerprint_canonical.py (31 checks before, 73 with this change; they drive apply_syminfo and main() with a fake strategy library: order and values of the calls, null/absent/invalid values, missing setter, --bench, the recorded grid and digest), and python3 -m unittest discover -s tests -p 'test_release_*.py' (66 tests), the two commands of python-test.yml.
  • The hosted container shim that replaces apply_syminfo is unaffected: its build gate (pf_run_json.py --check) prints hooks OK against this run_json.py, and its 23 unit tests pass against this file.

Please note

  1. docker/run_json.py is vendored from the pinned pineforge-engine tag (scripts/sync-harness.sh, handle-upstream.yml): today it is byte-identical to the engine's v1.0.1 copy, and the next release sync copies the engine's file over this one. This change takes effect in a published image only if the same diff also lands in pineforge-engine's docker/run_json.py (this diff applies to the v1.0.1 file with git apply -p1 and reproduces this file byte for byte). The file and the README say so.
  2. Besides qty_step (the lot grid), the harness also sets the metadata key mincontract, as the hosted shim does, so a script's syminfo.mincontract reads the catalog value instead of na. Drop that second call (and the mincontract entry of the recorded dict) if only the grid is wanted.
  3. An invalid mincontract is an error rather than a warning, so that a bad catalog value is never silently run without its grid. mintick, pointvalue, timezone and session keep their old, unvalidated handling.
  4. The changes to main are: the bench and body runs sit inside one try (re-indented, otherwise unchanged), _make_state keeps what apply_syminfo returned (a replacement apply_syminfo that returns None or any non-dict is tolerated), and one line adds it to applied_runtime. The hooks the hosted shim's build gate asserts are intact.

🤖 Generated with Claude Code

The harness applied only mintick, pointvalue, timezone and session from the
--syminfo file, so a percent-of-equity strategy traded sub-lot quantities that
the instrument does not allow. A positive finite mincontract is now set as the
engine's qty_step metadata (order quantities are floored to it) and as the
mincontract metadata, before the other setters. Absent or null changes
nothing; any other value, or a library without the metadata setter, fails the
run with the structured error line, which shows the value's JSON text. An
applied grid is recorded as applied_runtime.syminfo and so in the fingerprint.

docker/run_json.py is vendored from the pineforge-engine tag at every
release: keep the lot-grid handling identical in both repos.
@luisleo526

Copy link
Copy Markdown
Contributor Author

Superseded: the lot grid ships in the engine harness (engine #322), synced into the release image; verified on pineforge-release:1.1.0 (qty-step case passed).

@luisleo526 luisleo526 closed this Oct 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant