PineForge unreleased
Deterministic PineScript v6 backtest runtime — C ABI reference
Loading...
Searching...
No Matches
report_sink.h
Go to the documentation of this file.
1/*
2 * SPDX-License-Identifier: Apache-2.0
3 *
4 * CR-150 P00: report sink C ABI declarations, DESIGN.md revision 6,
5 * section 3.7 lines 782-901 and section 6 lines 1278-1317.
6 * No definitions or runtime feature probe are supplied by this contract.
7 * Include this additive header explicitly, as for execution_observer.h.
8 */
9
10#ifndef PINEFORGE_REPORT_SINK_H
11#define PINEFORGE_REPORT_SINK_H
12
13#include <pineforge/pineforge.h>
14
15#ifdef __cplusplus
16extern "C" {
17#endif
18
19/** Path sink descriptor (DESIGN 6:1283-1289, 1307-1309).
20 * struct_size is sizeof(pf_report_sink_v1), version is 1. The setter copies
21 * the strings. The engine creates or truncates path, writes the version-1
22 * header at run begin, and writes checked, buffered records (3.7:881-888).
23 * NULL run_id means ""; NULL scratch_dir means the directory of path.
24 * Scratch files are engine-private and live until the next run begins or
25 * handle destruction (3.8:902-909). */
26typedef struct pf_report_sink_v1 {
27 uint32_t struct_size;
28 uint32_t version;
29 const char* path;
30 const char* run_id;
31 const char* scratch_dir;
33
34/** Borrowed sequential stream sink descriptor (CX-SINK-B1/B2).
35 * struct_size is sizeof(pf_report_sink_fd_v1), version is 1. fd is the
36 * caller-selected write end of a FIFO or AF_UNIX SOCK_STREAM socket; there
37 * is no library default fd number. NULL run_id means ""; it is copied.
38 * No path or scratch directory is supplied or derived for this sink.
39 *
40 * The caller keeps fd open through the run and report capture. The library
41 * neither duplicates nor closes it, including on replacement, removal,
42 * failure, abort or handle destruction. The caller closes it to deliver EOF
43 * to the trusted finalizer. A new run on the same fd appends a new stream;
44 * use a fresh stream per run when the reader expects one header/trailer.
45 * Each begin checks that fd is open and matches its installed device/inode
46 * identity; a closed or replaced descriptor fails with report_sink_failed.
47 * A host that dup2s a fresh FIFO onto the same descriptor number for the
48 * next run must install the sink again.
49 *
50 * Bytes, canonical JSON, hashes, LF framing and the 1 MiB line limit are
51 * identical to the path sink. Writes are strictly sequential: no open,
52 * creation, seek, pwrite, truncation, unlink or rename on this route.
53 * Partial writes and EINTR are retried; EAGAIN blocks in poll() with bounded
54 * buffering. Only a successful run writes a trailer, as its last line.
55 * Failure or abort leaves no valid trailer; the receipt stays incomplete.
56 *
57 * Socket writes use send(..., MSG_NOSIGNAL). For a FIFO the host MUST ignore
58 * SIGPIPE (Python does by default; C hosts use signal(SIGPIPE, SIG_IGN))
59 * so a closed reader becomes the typed report_sink_failed error. The
60 * library never changes process-wide SIGPIPE disposition. A successful new inherited-arena attach sets SIGXFSZ to SIG_IGN process-wide; detach does not restore it. Hosts relying on their SIGXFSZ handler or default termination must account for this before attaching an arena. */
61typedef struct pf_report_sink_fd_v1 {
62 uint32_t struct_size;
63 uint32_t version;
64 int fd;
65 const char* run_id;
67
68/** Trailer receipt (DESIGN 6:1291-1298,1305-1306).
69 * The seven counts correspond to E, T, O, V, K, X, H in that order.
70 * sha256_hex is the trailer's lowercase SHA-256, NUL-terminated, and is ""
71 * until the trailer exists. complete becomes 1 once the trailer is written.
72 * trades_retained is 1 when closedtrades.* history stays script-readable.
73 * The explicit seven-byte reserved field gives a 144-byte layout with no
74 * implicit padding. PF_ABI_VERSION stays 4; no existing report/trade grows.
75 * RESOLVED S1: receipt query initialization and outcomes follow the
76 * CORE-TOP rev 6 amendment (10-18 16:04); see the query declaration below. */
85
88PF_STATIC_ASSERT(offsetof(pf_report_sink_receipt_v1, sha256_hex) == 64);
89PF_STATIC_ASSERT(offsetof(pf_report_sink_receipt_v1, reserved) == 129);
90PF_STATIC_ASSERT(offsetof(pf_report_sink_receipt_v1, complete) == 136);
91PF_STATIC_ASSERT(offsetof(pf_report_sink_receipt_v1, trades_retained) == 140);
92
93/** Report-only T-row summary (CX-SINK-B5a), independent of the 144-byte
94 * trailer receipt above. Size 64, alignment 8, with no implicit padding:
95 * struct_size 0, version 4, count_t 8, wins 16, losses 24, best_trade 32,
96 * worst_trade 40, max_drawdown 48, nonfinite_t_field 56, complete 60.
97 *
98 * Rows enter in global T order, using the original report doubles. Wins
99 * and losses count strictly positive/negative P&L; either zero counts as
100 * neither. Best/worst start with the first P&L and retain the previous
101 * operand on ties (including signed zero). Empty extrema are +0.0.
102 * Drawdown is the nonpositive minimum of cumulative trade P&L minus its
103 * running peak, both starting at +0.0; it is not equity-curve drawdown.
104 *
105 * nonfinite_t_field is 0 if all eight canonical doubles are finite.
106 * Otherwise it names the first field of the first offending T row:
107 * 5 entry_price, 6 exit_price, 7 qty, 8 pnl, 9 pnl_pct, 10 max_runup,
108 * 11 max_drawdown, 12 commission. These are T wire field indices.
109 * complete mirrors the trailer receipt. Values are published only with
110 * a successful trailer; until then they are zero and complete is 0. */
112#if defined(__cplusplus)
113 alignas(8)
114#elif defined(_MSC_VER)
115 __declspec(align(8))
116#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
117 _Alignas(8)
118#elif defined(__GNUC__)
119 __attribute__((aligned(8)))
120#endif
121 uint32_t struct_size;
122 uint32_t version;
123 int64_t count_t;
124 int64_t wins;
125 int64_t losses;
130 uint32_t complete;
132
134PF_STATIC_ASSERT(offsetof(pf_report_sink_summary_v1, struct_size) == 0);
139PF_STATIC_ASSERT(offsetof(pf_report_sink_summary_v1, best_trade) == 32);
140PF_STATIC_ASSERT(offsetof(pf_report_sink_summary_v1, worst_trade) == 40);
141PF_STATIC_ASSERT(offsetof(pf_report_sink_summary_v1, max_drawdown) == 48);
142PF_STATIC_ASSERT(offsetof(pf_report_sink_summary_v1, nonfinite_t_field) == 56);
144#if defined(__cplusplus)
146#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
148#endif
149
150/** Fixed-size annualization provenance from the emitted v1 records.
151 * Independent of both existing receipts; no wire or legacy layout changes.
152 * first/last are the first/last presented E rows, not input-feed endpoints.
153 * trade_at_start is 1 iff any emitted T row has entry_bar_index <= 0.
154 * Published only after a successful trailer. No sink, failure, abort or
155 * an unstarted generation yields all-zero values and complete 0.
156 * Read through strategy_report_sink_diagnostics_v1 (pineforge.h). */
166
171PF_STATIC_ASSERT(offsetof(pf_report_sink_diagnostics_v1, trade_at_start) == 64);
173
174/** Returns report sink ABI version 1 (DESIGN 6:1300). */
176
177/** Install, replace or remove (NULL descriptor) the sink (6:1301, 1307-1309).
178 * Returns 0 accepted; -1 bad struct_size, version, path or handle, or invalid environment arena;
179 * -2 stream running/beginning; -3 run in progress or removal while an
180 * ordinary run's scratch curve is live. Strings are copied.
181 * A refused removal keeps the sink and its summary report unchanged;
182 * it never restores a full curve array from scratch.
183 * Sealed trade rows (ledger base > 0) also refuse removal with -3 and
184 * set_trade_history_streamable(false) with coded logic_error engine_invariant,
185 * preserving the sink, receipt and report without rehydrating rows until a
186 * new run begin clears size/base to 0 (ordinary scratch ownership still
187 * independently refuses removal).
188 * Completed fd generations allow NULL through either setter to detach the
189 * next-run sink while preserving their receipt, summary and borrowed fd.
190 * The sink composes with the execution observer and selected window. Write errors
191 * fail the run with the sink I/O code planned for docker/run_failure_codes.json
192 * (DESIGN 3.7:881-888; 6:1330-1332). With a sink, pf_report_t contains summary
193 * scalars, total_trades and metrics; trades, equity_curve, broker_state_hash and
194 * trace are NULL with length 0. Thus total_trades need not equal trades_len, and
195 * equity/hash lengths need not equal script_bars_processed (6:1312-1317). Counts and SHA-256 come from the receipt.
196 * Without a sink the existing entry points and report behavior stay unchanged (6:1352-1354).
197 * A non-NULL descriptor attaches the environment arena if configured: PF_SCRATCH_ARENA_FD=5 and positive PF_SCRATCH_ARENA_BYTES, with validation passing.
198 * The first successful attach sets SIGXFSZ to SIG_IGN process-wide; the prior disposition is not restored. Same fd/size reattaches are no-ops.
199 * Hosts relying on their SIGXFSZ handler or default termination must account for this before attaching. */
201
202/** Install, replace or remove (NULL) the single sink using a borrowed fd.
203 * Mutually exclusive with strategy_set_report_sink_v1: either setter
204 * replaces the other kind. NULL through either setter removes next-run
205 * configuration only when the generation guards permit it.
206 * Returns 0 accepted; -1 bad handle, struct_size, version, negative fd or invalid environment arena;
207 * -2 stream running/beginning; -3 run in progress or removal of a
208 * scratch-backed or sealed-base generation, as the path setter.
209 * A successfully completed fd generation permits NULL through either setter
210 * to detach next-run configuration while preserving its receipt, summary
211 * and borrowed fd. Path generations have no completed-generation detach:
212 * their scratch/base guards still apply. A refusal preserves the generation.
213 * Descriptor I/O/type and installed device/inode identity are checked at
214 * each run begin; a closed, replaced or non-stream descriptor fails the run
215 * with report_sink_failed. A host that dup2s a fresh FIFO onto the same
216 * descriptor number for the next run must install the sink again. Summary
217 * reports and strategy_report_sink_receipt_v1 follow the path sink rules.
218 * A non-NULL descriptor attaches the environment arena if configured: PF_SCRATCH_ARENA_FD=5 and positive PF_SCRATCH_ARENA_BYTES, with validation passing.
219 * The first successful attach sets SIGXFSZ to SIG_IGN process-wide; the prior disposition is not restored. Same fd/size reattaches are no-ops.
220 * Hosts relying on their SIGXFSZ handler or default termination must account for this before attaching. */
222
223/** Query the sink receipt (DESIGN 6:1302; RESOLVED S1).
224 * CORE-TOP's rev 6 amendment (10-18 16:04) uses the observation query
225 * strategy_execution_observation_v1 in execution_observer.h as its model.
226 * The caller first sets out->struct_size and out->version.
227 *
228 * Returns 0 with the receipt filled; -1 with *out unchanged for a bad
229 * handle, struct_size or version. With no sink installed, returns 0 with
230 * all counts 0, sha256_hex "", complete 0.
231 *
232 * Failed, refused, aborted or truncated runs have no valid trailer
233 * (3.7:791-793); a receipt without completion is not a valid sink. */
235
236/** Query the T-row summary for either the path or fd sink.
237 * Initialize out->struct_size to sizeof(*out), out->version to 1.
238 * Returns 0 filled; -1 with *out unchanged for NULL out, bad handle,
239 * struct_size or version, exactly as strategy_report_sink_receipt_v1.
240 * No sink, an unstarted run, failure or abort yields zero values and
241 * complete 0. After successful report capture, count_t equals the trailer's
242 * count_t and complete is 1. A nonzero nonfinite_t_field must be rejected
243 * by consumers as report_result_invalid{field:"trades.<name>",
244 * reason:"nonfinite"} before using the summary.
245 * Queries never capture a report, read a sink, or advance the fold; repeated
246 * reads return the same value. Replacement/removal and new runs reset it. */
248
249#ifdef __cplusplus
250} /* extern "C" */
251#endif
252
253#endif /* PINEFORGE_REPORT_SINK_H */
void * pf_strategy_t
Opaque handle to a compiled strategy instance.
Definition pineforge.h:441
#define PF_API
Definition pineforge.h:83
#define PF_STATIC_ASSERT(condition)
Definition pineforge.h:52
int strategy_report_sink_receipt_v1(pf_strategy_t, pf_report_sink_receipt_v1 *)
Query the sink receipt (DESIGN 6:1302; RESOLVED S1).
uint32_t pf_report_sink_version(void)
Returns report sink ABI version 1 (DESIGN 6:1300).
int strategy_report_sink_summary_v1(pf_strategy_t, pf_report_sink_summary_v1 *)
Query the T-row summary for either the path or fd sink.
int strategy_set_report_sink_v1(pf_strategy_t, const pf_report_sink_v1 *)
Install, replace or remove (NULL descriptor) the sink (6:1301, 1307-1309).
int strategy_set_report_sink_fd_v1(pf_strategy_t, const pf_report_sink_fd_v1 *)
Install, replace or remove (NULL) the single sink using a borrowed fd.
Single per-script-bar equity point.
Definition pineforge.h:326
Fixed-size annualization provenance from the emitted v1 records.
Borrowed sequential stream sink descriptor (CX-SINK-B1/B2).
Definition report_sink.h:61
const char * run_id
Definition report_sink.h:65
Trailer receipt (DESIGN 6:1291-1298,1305-1306).
Definition report_sink.h:77
Report-only T-row summary (CX-SINK-B5a), independent of the 144-byte trailer receipt above.
Path sink descriptor (DESIGN 6:1283-1289, 1307-1309).
Definition report_sink.h:26
const char * run_id
Definition report_sink.h:30
const char * scratch_dir
Definition report_sink.h:31
const char * path
Definition report_sink.h:29
uint32_t struct_size
Definition report_sink.h:27