Skip to content

Another symbol's bars reach a run as a feed: kernel instrument feeds + Pine foreign request sites (XSYM-D) - #292

Merged
luisleo526 merged 6 commits into
mainfrom
r5/xsym-feeds
Sep 27, 2026
Merged

luisleo526 merged 6 commits into
mainfrom
r5/xsym-feeds

Conversation

@luisleo526

Copy link
Copy Markdown
Collaborator

Another symbol's bars reach a run as a feed (XSYM-D)

A strategy that reads request.security on a symbol other than its chart can now run against that symbol's own bars. Before this, only the run's own symbol was addressable, so these scripts were refused.

Kernel

  • NativeRunSpec::instrument_feeds (NativeInstrumentFeed {instrument, tf, bars, close_ms, columns}) and a new series source InstrumentFeed keyed by (instrument, timeframe).
  • Delivery by interval only:
    • With lookahead off, every undelivered bar whose close is at or before the chart bar's last traded close is handed over, in order.
    • With lookahead on, every bar that opened by the chart bar's open is handed over.
    • Feed bars before the first chart bar are history. No calendar is guessed for the other instrument.
  • Opt-in: every hash fold is conditional, so a run without feeds is byte-identical. The ADR-0001 rule-2 ruling row is added: another venue is driven by data only, with no new knob.
  • Refusals: bad feeds are refused with named errors, and stream_begin refuses a spec that has instrument feeds.

Pine adapter

  • Each foreign request site runs its expression on every delivered bar of its feed, in the requested context: history, TA, bar_index, time_close and syminfo.* belong to the other symbol.
  • ignore_invalid_symbol turns an invalid symbol into na.
  • A registered symbol with no installed feed fails the run closed, naming the symbol and timeframe.
  • TradingView's recorded request values are replayed from a store keyed by chart-bar open time, with na where the tape has no row.

C ABI and harness

  • Four setters, each behind its own PINEFORGE_HAS_…_V1 probe: strategy_set_symbol_feed, strategy_set_symbol_feed_column, strategy_set_symbol_facts, strategy_set_recorded_series. They refuse while a run is in progress.
  • run_strategy.py installs a probe's pinned request data from PINEFORGE_REQUESTS_ROOT, sha256-verified, and records it in the run provenance. With the variable unset, nothing changes.
  • Four BacktestEngine virtuals are added inside the current C++ ABI epoch; abi-stability.md records that objects compiled against an earlier v19 header must be rebuilt. PF_ABI_VERSION is unchanged.

Verification

  • New tests on synthetic bars:
    • test_native_instrument_feed: 12,375 checks;
    • test_native_instrument_feed_twin, the kernel/adapter twin: 353 checks;
    • test_symbol_request_data: 119 checks.
    • Each fails before the change.
  • Full verification on this head:
    • preflight 43 stages;
    • release 720 rows, runtime budget ratio 3.557x;
    • debug and sanitizers 701/701;
    • kernel 292 rows with 0 residual findings;
    • docs with 0 Doxygen warnings;
    • corpus parity 312/312 with 0 moved;
    • subset 54/54.
  • Two TradingView witness tapes reproduce bar for bar through the new path: 130/130 and 21/21.
  • Floors: kernel 289 → 291, release 716 → 720, and debug/sanitizers/native registration 697/697/706 → 701/701/710.

The population parity sweep of this exact tree is posted as pineforge/parity.

🤖 Generated with Claude Code

https://claude.ai/code/session_01LifAW9e5J6gFx7gatuxETE

luisleo526 and others added 6 commits September 27, 2026 22:22
… interval, and the Pine adapter's request sites of other symbols

Kernel (ADR-0001 rule 3, opt-in). NativeRunSpec::instrument_feeds holds other
instruments' own bars, keyed by (instrument, timeframe), each bar with its own
close (close_ms) and optional named columns; a NativeSeriesSource::InstrumentFeed
series names one. On each accepted input the series is handed, in feed order,
every bar not yet handed over that has closed by the input's
NativeInterval::last_traded_close_ms (lookahead off) or opened by its open_ms
(lookahead on); gaps is the subscription contract's; bars before the first
input ride on it. No calendar is guessed for the instrument; the chart side
reads the input's calendar interval (resolved from the label on a raw-label
partition, whose inputs carry no duration). With no feed installed every
digest, delivery and run is what it was. Streams refuse a spec that installs
one. Nine named refusals (NativeRunSpecError/Field, with C twins so the
translation stays exhaustive); C has no route (1.0 boundary row).

Adapter. register_security_eval(sec_id, symbol, tf, input_tf, lookahead_on,
gaps_on, ignore_invalid_symbol) registers a site of another symbol; the host
projects its installed feeds as the spec's instrument feeds, declares one
InstrumentFeed series per site after its same-symbol sites (which keep their
route), and runs the payload on every bar handed over in the requested
context (history and TA over the context's bars, bar_index, time_close and
syminfo.* the context's). gaps_on clears at the bar open, before the COOF
checkpoint. An invalid symbol reads na under ignore_invalid_symbol and fails
closed without it; a missing feed fails closed naming symbol and timeframe;
an aggregated chart is refused. Recorded request series: key -> chart-bar
open -> value, na where the tape has no row.

C ABI: strategy_set_symbol_feed / _feed_column / strategy_set_symbol_facts /
strategy_set_recorded_series, each behind its own PINEFORGE_HAS_*_V1 probe,
reached through BacktestEngine virtuals whose kernel base answers false; a
source host refuses stream_begin while any holds data, and
check_c_abi_runtime.py pins that each declaration says so.

Tests on synthetic bars only: test_native_instrument_feed (kernel, the
report's pinned cases), test_native_instrument_feed_twin (kernel/adapter
twin), test_symbol_request_data (C ABI, recorded series, stream refusal).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…INEFORGE_REQUESTS_ROOT

The environment contract of workflow docs/xsym-requests.md: when
PINEFORGE_REQUESTS_ROOT is set and <root>/<basename of the strategy dir>/
requests.json exists, the harness holds the manifest to
pineforge-probe-requests/v1 (the refusals of the workflow's
validateProbeRequests) and to this probe, verifies every file it names under
the probe's own files/ against its sha256 (a feed against its byte count),
parses the feeds (an empty or NaN volume or extra value is NaN), the
recorded tapes and the symbol facts, and installs them through
strategy_set_symbol_facts / _feed / _feed_column / strategy_set_recorded_series
before the run. It reads nothing else: never another slug's directory, never
the case-wide PINEFORGE_PINE_LIBRARIES. The run provenance records each
feed's (key, sha, bars), each tape's (key, sha, rows) and the manifest's sha,
as it does for the aux and native security feeds. A malformed manifest stops
the run by name before any engine call; a library without the setters and the
docker runner are refused. Unset, or no manifest for the probe: the engine is
handed exactly what it always was.

The frozen verifier names the probe's build directory BUILD / slug
(pineforge-lab 3bac0b7b scripts/verify-engine-local.py:1695) and runs this
script on it (:1593-1596, :1614-1617) through _run_timed (:190-193), whose
subprocess.run passes no env=, so the runner's environment reaches it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…nstrument-feed contract

native-engine.md: the field list, the Limits paragraph and the stream-limits
bullet state the new contract; a new section "Instrument feeds: another
symbol's bars" gives the struct, the delivery rule, the validation table, the
stream refusal and the Pine adapter's use (the symbol-key overload, the
payload in the requested context, ignore_invalid_symbol, the fail-closed
refusals, recorded request series, the run_strategy.py environment contract);
the 1.0 C boundary table has the "instrument feeds" row its checker pins.
ADR-0001: the rule-2 ruling row "Wave XSYM-D" (mechanism: another venue is
driven by changing data only; knob: none -- lookahead and gaps are already
the subscription's delivery rules), calibrated by XSYM-DESIGN's 203/203 merge
pins and this lane's two witness reproductions; the HTF paragraph, the run-spec
paragraph and the adapter's section name the new surface. mtf.md, coverage.md
and README: the other-symbol surface, the recorded fundamentals store, the
four C setters; the runtime export counts move 58 -> 62 (66 -> 70 declared,
109 -> 113 in total).

Sentences this commit removes, each superseded by the contract it states:
- ADR-0001 (5691220, lane L14-C): "request.security-style series are
  reachable from a bare host, as declared series of the run's own symbol" --
  another symbol's bars are reachable now too, and the paragraph says so.
- coverage.md (cc4a56e): "Runtime owns the security state machine, ratio /
  calendar aggregation, lookahead / gaps semantics, lower-TF emulation, and
  per-security diagnostics", "The runtime owns same-symbol security
  computation" and "Why not done yet: Most strategy logic in our test corpus
  relies on the chart symbol's bars + indicators" -- the request.security row
  and its section state the other-symbol surface instead.
- coverage.md (9507d8f): "Would need a parallel data-ingestion path so the
  user can supply a CSV / Parquet of fundamentals", "Future story:
  Fundamentals and economic indicators would land as a generic auxiliary
  timeline feature" and "Fundamentals- and macro-driven strategies are a
  meaningful but smaller user segment" -- the recorded request series store
  and the pinned requests manifest are that ingestion path.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…'s four rows

The rebase onto 1e360bd (INT27 with its round-2 fix) shifted cited lines:
reanchor_doc_citations.py --ref 1e360bd plus check_doc_anchors.py --fix and
four hand fixes bring every anchor back (1725/1725; the diff changes digits
only).

Floors, counted with ctest -N on the lane's tree under each ci_verify
profile's cache definitions:
  kernel      292 registered, 291 run (+2: test_native_instrument_feed,
              test_run_strategy_requests; the WebSocket row still skips)
  release     720 (+4: the two above, test_native_instrument_feed_twin,
              test_symbol_request_data)
  debug/sanitizers/native registration 701/701/710 (+4 each)
KERNEL_MIN_TESTS 289 -> 291, RELEASE_MIN_TESTS 716 -> 720,
EXCLUDED_REGISTERED_MIN 697/697/706 -> 701/701/710; docs/ci.md and the
floor citations follow.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…d); setters refuse a run in progress; the literal input_tf trap deleted

An independent review of 1e360bd..80236d5a found, and this commit fixes:

- The workflow keeps a request that was na on every chart bar as a tape with
  a header only (campaign/tv/xsym/decode.mjs keeps non-na rows), and pins it.
  strategy_set_recorded_series(key, ..., 0) erased the key, so the first read
  failed the run as "no recorded series is installed". n == 0 now installs a
  series without rows: every read is na. strategy_set_symbol_feed(..., 0)
  likewise installs a feed without bars, whose sites read na, instead of
  leaving the symbol unfed.
- The four setters answer -1 while a run is in progress (a stream's warmup
  or realtime leg included), naming it: the source host's mutation guard is
  inert, so a mid-stream install changed what the run read and its digest.
- strategy_set_symbol_feed_column judges the column name the kernel's way at
  install (a name that is not UTF-8 was accepted, then refused at begin).
- register_security_eval(id, "SYM", "tf", "literal", bool[, bool]) bound the
  same-symbol overload (const char* -> bool outranks std::string) and read
  the symbol as the timeframe. That spelling is deleted; generated code
  passes input_tf_ (a std::string) and is unaffected.
- run_strategy.py refuses by name a manifest the schema cannot walk (JSON
  nested past the recursion limit, a mintick past a double, an unreadable
  file) and a stamp an int64 cannot hold, which ctypes would wrap silently.
- abi-stability.md records the four BacktestEngine virtuals added inside v19
  (as N5 did inside v18); mtf.md and native-engine.md state the empty
  installs, the in-run refusal and that the generated clear_security() also
  clears a gaps_on payload's own history (a codegen limit).

Tests: test_symbol_request_data 77 -> 119 checks (empty tape, empty feed,
column name, in-run refusal, the deleted spelling as static_asserts);
test_native_instrument_feed 10543 -> 12375 (a raw label outside the session
reads the calendar's zero-traded slot); test_run_strategy_requests 13 -> 15.
Fail-before on 80236d5a's headers and library: the static_asserts fail to
compile; 15 runtime failures; the harness raises OverflowError.

Sentences of 4076d7d this commit rewrites: mtf.md's and native-engine.md's
"gaps_on reads na on a chart bar that received no new bar" now also states
that the generated clear_security() resets the payload's own history, and
mtf.md's "Historical runs only: a stream refuses an installed symbol feed"
now also states the in-run refusal.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…etached-comments census 8 -> 0)

The preflight's detached-comments stage (measure_detached_comments.py
--check-ceiling, ceiling 0) counted the eight-line group comment above the
four PINEFORGE_HAS_…_V1 probes as detached: a second block in the gap above
the first probe's own doc. Its text now opens that doc, so the census of the
kernel compile closure is 0 again; the lines keep their numbers.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
import math
import sys
import tempfile
import unittest
from pathlib import Path
from unittest import mock

import run_strategy as rs
@luisleo526
luisleo526 merged commit 7f807b9 into main Sep 27, 2026
18 checks passed
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