PineForge v0.13.1-379-g9b50973
Deterministic PineScript v6 backtest runtime — C ABI reference
Loading...
Searching...
No Matches
pineforge.h
Go to the documentation of this file.
1/*
2 * SPDX-License-Identifier: Apache-2.0
3 *
4 * pineforge.h — public C ABI for the PineForge runtime.
5 *
6 * This header is the single source of truth for the harness ↔ compiled-
7 * strategy boundary. Every PineForge-generated .so exports a fixed set
8 * of C symbols declared below; the Python harness (validate_detailed_
9 * report.py) and any C/C++/FFI consumer of compiled strategies links
10 * against this contract.
11 *
12 * STABILITY GUARANTEE
13 * ───────────────────
14 * Within the same PINEFORGE_VERSION_MAJOR, this header's POD struct
15 * layouts and `extern "C"` symbol signatures are append-only. Fields
16 * are never reordered, removed, or retyped; new fields may only be
17 * appended at the end of structs. New functions may be added; existing
18 * functions are not removed or signature-changed.
19 *
20 * Across major versions all bets are off. Bump
21 * PINEFORGE_VERSION_MAJOR when breaking the ABI.
22 *
23 * SCOPE — WHAT THIS HEADER COVERS
24 * ───────────────────────────────
25 * ✓ Lifecycle of a compiled strategy (create / destroy)
26 * ✓ Running a backtest (auto-detect or fully configured)
27 * ✓ Per-strategy configuration (inputs, overrides, magnifier, trace)
28 * ✓ The shape of the report returned to the harness
29 *
30 * SCOPE — WHAT THIS HEADER DOES NOT COVER (BY DESIGN)
31 * ───────────────────────────────────────────────────
32 * ✗ The contract between codegen-emitted strategy code and the runtime
33 * internals (TA classes, math, series, strategy commands). That
34 * contract stays C++ — codegen and runtime ship together and are
35 * versioned in lockstep within the closed transpiler.
36 * ✗ Source-compiling strategies. Use the closed transpiler binary.
37 *
38 * The C++ headers under `<pineforge/engine.hpp>` etc. are *internal*
39 * implementation surface — not part of this stability guarantee.
40 */
41
42#ifndef PINEFORGE_H
43#define PINEFORGE_H
44
45#include <stdint.h>
46#include <stddef.h>
47
48/* ── Version ─────────────────────────────────────────────────────── */
49
50/* Macros (PINEFORGE_VERSION_MAJOR / _MINOR / _PATCH / _STRING / _FULL,
51 * PINEFORGE_GIT_SHA) live in the generated <pineforge/version.h>. */
52#include <pineforge/version.h>
53
54/* pf_pending_order_v1_t / pf_field_desc_t -- the generated, C-compatible POD
55 * mirror of the engine's resting-order record (ABI v4, task 7). Regenerate
56 * with scripts/gen_pending_order_mirror.py; never edit by hand. */
58
59/* ── Visibility ──────────────────────────────────────────────────── */
60
61#if defined(_WIN32) || defined(__CYGWIN__)
62 #if defined(PINEFORGE_BUILD_SHARED)
63 #define PF_API __declspec(dllexport)
64 #else
65 #define PF_API __declspec(dllimport)
66 #endif
67#elif defined(__GNUC__) || defined(__clang__)
68 #define PF_API __attribute__((visibility("default")))
69#else
70 #define PF_API
71#endif
72
73/** Monotonic ABI version of pf_report_t / pf_trade_t layout. Bumped
74 * whenever a caller-visible struct grows. Consumers MUST verify
75 * pf_abi_version() == PF_ABI_VERSION before calling run_backtest.
76 * pf_report_t is caller-allocated: growth causes silent stack corruption
77 * in old callers that under-size the struct. pf_trade_t is runtime-
78 * allocated: growth causes array-stride misindexing in old readers that
79 * iterate the trades array with the stale sizeof. Value 2 = first
80 * versioned layout (metrics + equity curve); .so files predating this
81 * macro have no pf_abi_version symbol — treat dlsym failure as
82 * version 1. Value 3 appends pf_trade_t::open_at_end (the range-end
83 * close flag); a v2 reader iterating trades with the v2 stride would
84 * misindex every row after the first. Value 4 appends the live-runtime
85 * accessors and the per-bar broker-state hash array to pf_report_t. */
86#define PF_ABI_VERSION 4
87
88/** Feature probe for the opt-in split chart/request.security feed boundary.
89 * When defined, #strategy_set_aux_security_feed is available. */
90#define PINEFORGE_HAS_AUX_SECURITY_FEED_V1 1
91
92/** Feature probe for native higher-timeframe request.security feeds.
93 * When defined, #strategy_set_native_security_feed is available. */
94#define PINEFORGE_HAS_NATIVE_SECURITY_FEED_V1 1
95
96/** Feature probe for immutable native-run account-currency FX curves.
97 * When defined, #strategy_configure_native_fx_curve_v1 is available. */
98#define PINEFORGE_HAS_NATIVE_FX_CURVE_V1 1
99
100#ifdef __cplusplus
101extern "C" {
102#endif
103
104/** @defgroup pf_types Types
105 * @brief POD types and enums passed across the C ABI.
106 * @{
107 */
108
109/** Bar-magnifier sub-bar sampling distribution.
110 *
111 * Selects how intra-bar synthetic ticks are placed when the bar
112 * magnifier is enabled in #run_backtest_full. Layout-compatible with the
113 * internal C++ `pineforge::MagnifierDistribution` enum class — a
114 * `static_assert` in `c_abi.cpp` guarantees the integer values match. */
115typedef enum pf_magnifier_distribution_e {
116 PF_MAGNIFIER_UNIFORM = 0, /**< Uniform spacing across the parent bar. */
117 PF_MAGNIFIER_COSINE = 1, /**< Cosine-tapered density. */
118 PF_MAGNIFIER_TRIANGLE = 2, /**< Triangle-tapered density. */
119 PF_MAGNIFIER_ENDPOINTS = 3, /**< Default — exact O,H,L,C points plus uniform fill between. */
120 PF_MAGNIFIER_FRONT_LOADED = 4, /**< Sample density biased toward bar open. */
121 PF_MAGNIFIER_BACK_LOADED = 5 /**< Sample density biased toward bar close. */
123
124/** Single OHLCV bar pushed into the engine.
125 *
126 * Layout-compatible with the internal C++ `pineforge::Bar` struct. */
127typedef struct pf_bar_s {
128 double open; /**< Open price. */
129 double high; /**< High price. */
130 double low; /**< Low price. */
131 double close; /**< Close price. */
132 double volume; /**< Bar volume. */
133 int64_t timestamp; /**< Bar open time, Unix milliseconds. */
134} pf_bar_t;
135
136/** One provider-neutral realtime executed-trade update.
137 *
138 * `sequence` is optional: pass 0 when the normalized source has no stable
139 * ordering key. Non-zero values must increase strictly within a stream.
140 * `quantity` is expressed in the configured symbol's volume units and is
141 * accumulated into the input bar's volume. The source adapter owns all
142 * provider-specific fields and normalization. */
143typedef struct pf_trade_tick_s {
144 int64_t timestamp; /**< Source event time, Unix milliseconds. */
145 uint64_t sequence; /**< Normalized per-stream sequence, or 0. */
146 double price; /**< Executed trade price (> 0). */
147 double quantity; /**< Traded quantity in symbol volume units (>= 0). */
149
150/** Closed-trade record returned in pf_report_t::trades.
151 *
152 * Layout-compatible with internal `pineforge::TradeC`. */
153typedef struct pf_trade_s {
154 int64_t entry_time; /**< Entry fill time (Unix ms). */
155 int64_t exit_time; /**< Exit fill time (Unix ms). */
156 double entry_price; /**< Entry fill price (incl. slippage). */
157 double exit_price; /**< Exit fill price (incl. slippage). */
158 double pnl; /**< Net realized PnL in account currency (commission-inclusive). */
159 double pnl_pct; /**< Net return-on-cost in percent: pnl (NET of commission) /
160 * entry cost (entry_price * qty * pointvalue) * 100. This is
161 * TradingView's "Net P&L %" convention, arbitrated 2026-06-12
162 * against a real TV export (trade #258 short: 102.44 USD on a
163 * 2276.66 entry => 4.50%). Degenerates to the old gross
164 * (exit/entry-1)*100 form for longs with zero commission;
165 * the previous short form (entry/exit-1)*100 was wrong on
166 * large moves. Sign always matches pnl. */
167 int is_long; /**< 1 if long, 0 if short. */
168 double max_runup; /**< Peak favorable price travel during the trade ($/unit qty). */
169 double max_drawdown; /**< Peak adverse price travel during the trade ($/unit qty). */
170 double qty; /**< Filled quantity. */
171 double commission; /**< Entry+exit commission actually deducted from pnl
172 * (account currency). pnl is already net of this. */
173 int32_t entry_bar_index;/**< Script-bar index of the entry fill (0-based). */
174 int32_t exit_bar_index; /**< Script-bar index of the exit fill (0-based). */
175 int32_t open_at_end; /**< 1 when this row is the RANGE-END close of a position
176 * that was still open after the final bar; 0 for an
177 * exit the script or a bracket produced. TradingView's
178 * deep-backtest report does not leave the last position
179 * open: it reports it as a closed trade whose exit leg is
180 * the range's last bar at that bar's CLOSE, with an
181 * empty exit Signal, and counts it in closedTrades
182 * (orb-lite on NYSE:F 1D: Entry short 2026-03-16 @ 11.82,
183 * Exit 2026-04-30 @ 12.08 = the last close,
184 * closedTrades:1). The engine emulates that row
185 * (operator decision 2026-09-02): exit_time is the last
186 * script bar's label, exit_price its mintick-rounded
187 * close with no slippage, commission per the strategy's
188 * rules, pnl/pnl_pct/excursions as for any close.
189 * Appended in ABI v3. */
190} pf_trade_t;
191
192/** Trade-level statistics block — computed once each for all / long / short.
193 *
194 * Loss-side fields (`gross_loss`, `avg_loss`, `largest_loss`) are
195 * **positive magnitudes** (absolute values of the underlying negative PnL). */
196typedef struct pf_trade_stats_s {
197 int32_t num_trades; /**< Closed trades in this block (all / long-only / short-only). */
198 int32_t num_wins; /**< Trades with pnl > 0. */
199 int32_t num_losses; /**< Trades with pnl < 0. */
200 int32_t num_even; /**< Trades with pnl == 0.0 exactly; breaks both win and loss
201 * streaks; excluded from win/loss averages.
202 * Invariant: num_trades == num_wins + num_losses + num_even. */
203 double percent_profitable; /**< 100 * num_wins / num_trades, in PERCENT (0-100).
204 * NaN when num_trades == 0. */
205 double net_profit; /**< Sum of pnl (account currency, net of commission). */
206 double net_profit_pct; /**< net_profit as a percent of initial capital (0-100 scale).
207 * NaN when initial capital <= 0. */
208 double gross_profit; /**< Sum of winning pnl. */
209 double gross_profit_pct; /**< gross_profit as a percent of initial capital (0-100 scale).
210 * NaN when initial capital <= 0. */
211 double gross_loss; /**< Sum of |losing pnl| — POSITIVE magnitude (TV display convention). */
212 double gross_loss_pct; /**< gross_loss as a percent of initial capital (0-100 scale).
213 * NaN when initial capital <= 0. */
214 double profit_factor; /**< gross_profit / gross_loss. NaN when gross_loss == 0. */
215 double avg_trade; /**< net_profit / num_trades. NaN when num_trades == 0. */
216 double avg_trade_pct; /**< Mean of per-trade pnl_pct over all trades.
217 * NaN when num_trades == 0. */
218 double avg_win; /**< gross_profit / num_wins. NaN when num_wins == 0. */
219 double avg_win_pct; /**< Mean of per-trade pnl_pct over winning trades.
220 * NaN when num_wins == 0. */
221 double avg_loss; /**< gross_loss / num_losses (positive magnitude).
222 * NaN when num_losses == 0. */
223 double avg_loss_pct; /**< Mean of the NEGATED pnl_pct of the losing trades. Since
224 * pnl_pct is net return-on-cost (sign matches pnl), this is
225 * a genuinely POSITIVE magnitude. Basis = pf_trade_t::pnl_pct.
226 * NaN when num_losses == 0. */
227 double ratio_avg_win_avg_loss; /**< avg_win / avg_loss. NaN unless both sides non-empty. */
228 double largest_win; /**< Single largest pnl among winning trades.
229 * NaN when num_wins == 0. */
230 double largest_win_pct; /**< Maximum pnl_pct over winning trades — an INDEPENDENT
231 * maximum, not the pct of the largest-USD win (TV convention,
232 * validated 2026-06-12 vs TV export).
233 * NaN when num_wins == 0. */
234 double largest_loss; /**< Single largest |pnl| among losing trades (positive magnitude).
235 * NaN when num_losses == 0. */
236 double largest_loss_pct; /**< Maximum of -pnl_pct over losing trades (positive magnitude) —
237 * an INDEPENDENT maximum, not the pct of the largest-USD loss
238 * (TV convention, validated 2026-06-12 vs TV export: All
239 * "Largest loss %" came from a different trade than the
240 * largest USD loss). NaN when num_losses == 0. */
241 double commission_paid; /**< Sum of pf_trade_t::commission in the block. */
242 double expectancy; /**< (num_wins/num_trades)*avg_win - (num_losses/num_trades)*avg_loss,
243 * account currency per trade. NaN when num_trades == 0. */
244 int32_t max_consecutive_wins; /**< Longest winning run; even trades reset both streaks. */
245 int32_t max_consecutive_losses; /**< Longest losing run; even trades reset both streaks. */
246 double avg_bars_in_trade; /**< Mean of (exit_bar_index - entry_bar_index + 1) in SCRIPT
247 * bars, over all trades — inclusive of the entry bar (TV
248 * convention, validated 2026-06-12).
249 * NaN when num_trades == 0. */
250 double avg_bars_in_wins; /**< Mean bar duration of winning trades, inclusive of the entry
251 * bar (TV convention, validated 2026-06-12).
252 * NaN when num_wins == 0. */
253 double avg_bars_in_losses; /**< Mean bar duration of losing trades, inclusive of the entry
254 * bar (TV convention, validated 2026-06-12).
255 * NaN when num_losses == 0. */
257
258/** Equity-curve-derived statistics (all-trades only, like TV). */
259typedef struct pf_equity_stats_s {
260 double max_equity_drawdown; /**< Peak-to-trough equity drop, positive currency magnitude. */
261 double max_equity_drawdown_pct; /**< max_equity_drawdown relative to the peak in effect
262 * (PERCENT 0-100). */
263 double max_equity_runup; /**< Trough-to-peak rise where the trough resets on each new
264 * equity peak (mirrors the engine's intra-run extremes). */
265 double max_equity_runup_pct; /**< max_equity_runup relative to that trough (PERCENT 0-100). */
266 double buy_hold_return; /**< initial_capital * (last_close/first_open - 1), currency.
267 * NaN when first chart open is non-finite or <= 0. */
268 double buy_hold_return_pct; /**< buy_hold_return as PERCENT.
269 * NaN when first chart open is non-finite or <= 0. */
270 /** Month-end-resampled equity simple returns (chart timezone, open-time
271 * bucketing), risk-free 2%/yr (2/12 per month), annualized by sqrt(12).
272 * Uses sample (N-1) stddev. NaN with <2 monthly returns or zero deviation.
273 *
274 * `sharpe_tv` is the historical spelling of this same field. Both names
275 * are one `double` at one offset (a C11 anonymous union of two members of
276 * the same type), so the struct's size and every field offset are
277 * unchanged and a caller compiled against either spelling reads the same
278 * storage. The old spelling is DEPRECATED and is removed at the next
279 * #PF_ABI_VERSION; see ADR-0001 "Deprecated public spellings". The
280 * serialized report key stays `sharpe_tv` (report-schema name). */
281 union {
283 double sharpe_tv; /**< Deprecated spelling of
284 * pf_equity_stats_s::sharpe_monthly. */
285 };
286 /** Same resampling as sharpe_monthly; uses population downside deviation
287 * vs the monthly risk-free. NaN with <2 monthly returns or zero deviation.
288 *
289 * `sortino_tv` is the historical spelling of this same field, on the same
290 * terms as sharpe_monthly / sharpe_tv above. */
291 union {
293 double sortino_tv; /**< Deprecated spelling of
294 * pf_equity_stats_s::sortino_monthly. */
295 };
296 double sharpe_bar; /**< Per-script-bar returns, annualized by observed bar density
297 * (bars per year = (len-1)/calendar span), NOT a fixed
298 * calendar formula. Uses sample (N-1) stddev.
299 * NaN with <2 returns or zero deviation. */
300 double sortino_bar; /**< Same construction as sharpe_bar over per-bar returns;
301 * uses population downside deviation.
302 * NaN with <2 returns or zero deviation. */
303 double cagr; /**< PERCENT per year: 100*((final_equity/initial_capital)^(1/years)-1).
304 * NaN when span <= 0 or either side <= 0. */
305 double calmar; /**< cagr / max_equity_drawdown_pct — BOTH IN PERCENT, so the
306 * ratio is dimensionless. NaN when drawdown is 0. */
307 double recovery_factor; /**< net_profit / max_equity_drawdown (currency / currency).
308 * NaN when drawdown is 0. */
309 double time_in_market_pct; /**< PERCENT (0-100) of script bars with an open position
310 * at bar close. */
311 double open_pl; /**< Mark-to-market open profit at the final bar. */
313
314/** Composite metrics container: trade stats (all / long / short) +
315 * equity-curve stats. */
320
321/** Single per-script-bar equity point.
322 *
323 * `time_ms` is the script-bar **open** timestamp (Unix ms).
324 * `equity` = `initial_capital` + `net_profit` + `open_profit` at bar close. */
325typedef struct pf_equity_point_s {
326 int64_t time_ms; /**< Script-bar OPEN timestamp (Unix ms). */
327 double equity; /**< initial_capital + net_profit + open_profit. */
328 double open_profit; /**< Mark-to-market open P&L at bar close. */
330
331/** Per-`request.security()` site diagnostic counters.
332 *
333 * Layout-compatible with internal `pineforge::SecurityDiagC`. */
334typedef struct pf_security_diag_s {
335 int sec_id; /**< Stable id for the request.security site. */
336 int64_t feed_count; /**< Higher-TF feed bars consumed. */
337 int64_t complete_count; /**< Evaluations on completed parent bars. */
338 int64_t partial_count; /**< Evaluations on still-forming parent bars. */
340
341/** Single per-bar trace entry.
342 *
343 * Emitted when the source script contains `// @pf-trace name=expr`
344 * pragmas and tracing is enabled via #strategy_set_trace_enabled.
345 * Layout-compatible with internal `pineforge::TraceEntryC`. */
346typedef struct pf_trace_entry_s {
347 int64_t timestamp; /**< Bar timestamp (Unix ms). */
348 int32_t bar_index; /**< Zero-based bar index. */
349 int32_t name_id; /**< Index into pf_report_t::trace_names. */
350 double value; /**< Traced expression value on this bar. */
352
353/** Backtest report filled by #run_backtest / #run_backtest_full.
354 *
355 * Layout-compatible with internal `pineforge::ReportC`.
356 *
357 * ### Ownership and lifetime
358 * The struct itself is caller-owned (typically stack). The embedded
359 * arrays (`trades`, `security_diag`, `trace`, `trace_names`,
360 * `equity_curve`, `broker_state_hash`) are heap-allocated by the
361 * runtime; the caller must invoke #report_free exactly once on each
362 * filled report. `trace_names` string pointers remain owned by the
363 * strategy handle until #strategy_free. */
364
365typedef struct pf_report_s {
366 /* Trades */
367 int total_trades; /**< Closed-trade count (== trades_len), including
368 * the range-end close of a position still open
369 * after the final bar (pf_trade_t::open_at_end). */
370 pf_trade_t* trades; /**< Heap array of closed trades; script-driven exits
371 * first, then the range-end rows (open_at_end=1). */
372 int trades_len; /**< Length of #trades. */
373 double net_profit; /**< Sum of all closed-trade PnL. */
374
375 /* Bar processing counts */
376 int64_t input_bars_processed; /**< Source-feed bars consumed. */
377 int64_t script_bars_processed; /**< Script-timeframe bars evaluated. */
378
379 /* Security diagnostics */
380 int64_t security_feeds_total; /**< Total higher-TF feed bars across all security sites. */
381 int64_t security_complete_total; /**< Total complete-bar evals across all security sites. */
382 int64_t security_partial_total; /**< Total partial-bar evals across all security sites. */
383
384 /* Bar magnifier diagnostics */
385 int64_t magnifier_sub_bars_total; /**< Sub-bars synthesized by the magnifier. */
386 int64_t magnifier_sample_ticks_total; /**< Sample ticks visited by the magnifier. */
387
388 /* Timeframe metadata */
389 int input_tf_seconds; /**< Detected/configured input timeframe (seconds). */
390 int script_tf_seconds; /**< Script timeframe (seconds). */
391 int script_tf_ratio; /**< script_tf_seconds / input_tf_seconds. */
392 int needs_aggregation; /**< 1 if input → script TF aggregation was performed. */
393 int bar_magnifier_enabled; /**< 1 if magnifier was active for this run. */
394
395 /* Per-security feed/eval counters */
396 pf_security_diag_t* security_diag; /**< One entry per request.security site. */
397 int security_diag_len; /**< Length of #security_diag. */
398
399 /* Per-bar trace records */
400 pf_trace_entry_t* trace; /**< Per-bar trace records (empty unless tracing enabled). */
401 int trace_len; /**< Length of #trace. */
402 const char** trace_names; /**< Names indexed by pf_trace_entry_t::name_id. */
403 int trace_names_len; /**< Length of #trace_names. */
404
405 /* Computed trading metrics. Trade-based blocks reported for all /
406 * long-only / short-only; equity-based stats are all-trades only.
407 * Loss-side fields are positive magnitudes. Undefined values are NaN
408 * (see per-field docs). */
410 /* Per-script-bar equity curve. time_ms is the script-bar OPEN
411 * timestamp; equity = initial_capital + net_profit + open_profit at
412 * bar close. Heap-allocated; freed by report_free. len ==
413 * script_bars_processed, EXCEPT after a mid-run error (check
414 * strategy_get_last_error): an exception can truncate the curve, and
415 * metrics then describe the truncated prefix. NOTE int64_t length
416 * (ctypes: c_int64). */
419 /* Per-script-bar broker-state hash, filled when
420 * #strategy_set_broker_state_hash_recording is on; freed by
421 * #report_free. NULL / 0-length when recording was off (default) or no
422 * script bars were dispatched. When populated, len ==
423 * script_bars_processed and the last element equals
424 * #strategy_broker_state_hash's value at the end of the run.
425 * ABI v4. */
429
430/** @} */ /* end of pf_types */
431
432/** Opaque handle to a compiled strategy instance. */
433typedef void* pf_strategy_t;
434
435/* ───────────────────────────────────────────────────────────────────
436 * STRATEGY .SO EXPORTS — implemented per compiled strategy
437 * ───────────────────────────────────────────────────────────────────
438 *
439 * Each .so emitted by the codegen exports the following symbols. The
440 * runtime library itself does NOT define them — they are per-strategy
441 * implementations generated by the transpiler.
442 *
443 * Note on naming: these are the legacy unprefixed names retained for
444 * backward compatibility with the existing harness. Future major
445 * versions may introduce `pf_`-prefixed equivalents and deprecate the
446 * unprefixed forms.
447 */
448
449/** @defgroup pf_lifecycle Strategy lifecycle
450 * @brief Create, run, and destroy a compiled strategy instance.
451 * @{
452 *
453 * NOTE: Per-strategy symbols (strategy_create, run_backtest, etc.) are
454 * emitted by the codegen with internal C++ types (ReportC, Bar) that are
455 * layout-compatible but type-distinct from the public C PODs below.
456 * Guard with PINEFORGE_NO_STRATEGY_DECLS so engine.hpp can include this
457 * header for its POD types without conflicting with per-strategy TU
458 * definitions.
459 */
460
461/** Native execution contract query. Returns 1 Legacy, 2 NativeMarketV1, -1 invalid.
462 * Absence of this symbol on a known ABI4/stream-v1 library means legacy. */
464
465/** Versioned native run specification. All string pointers are non-null.
466 * session may be empty (all-day literal). session_key, timeframes, timezone
467 * and tickerid are nonempty. optional_mask bits: 0 quantity_grid, 1
468 * max_abs_units, 2 initial_margin_fraction, 3 max_open_lots. */
481
482/** Apply a native v1 specification. Returns 0 on success, -1 on failure.
483 * Legacy handles refuse without native state mutation. */
485
486/** Immutable timestamped account-currency FX curve for a native run.
487 * The two arrays have exactly @c n elements and are copied by the runtime. */
488typedef struct pf_native_fx_curve_v1 {
489 uint32_t struct_size;
490 uint32_t n;
491 const int64_t* effective_from_ms;
492 const double* account_per_quote;
494
495/** Stage an immutable account-currency FX curve on a Ready native handle.
496 *
497 * Timestamps must be strictly increasing and rates finite and positive.
498 * Pass @c n == 0 to clear the staged curve; the scalar @c account_fx remains
499 * the pre-first fallback. Curves are applied when the native run begins.
500 * Legacy handles and non-Ready native handles refuse without mutation.
501 *
502 * @return 0 only when staging is applied; -1 for invalid input, an invalid
503 * handle, a non-native or non-host native handle, or a non-Ready host. */
505 pf_strategy_t s, const pf_native_fx_curve_v1* curve);
506
507#ifndef PINEFORGE_NO_STRATEGY_DECLS
508
509/** Allocate a new strategy instance.
510 *
511 * @param params_json Currently ignored; pass `NULL`.
512 * @return Strategy handle, or `NULL` on allocation failure.
513 *
514 * Caller owns the returned handle and must release it via #strategy_free. */
515PF_API pf_strategy_t strategy_create(const char* params_json);
516
517/** Release a strategy handle previously returned by #strategy_create.
518 *
519 * Safe to call with `NULL`. Invalidates any `pf_report_t::trace_names`
520 * pointers obtained from this strategy. */
522
523/** Run a backtest with auto-detected timeframe and no bar magnifier.
524 *
525 * @param s Strategy handle from #strategy_create.
526 * @param bars Non-NULL pointer to OHLCV bars (length @p n).
527 * @param n Bar count (>= 0).
528 * @param out Non-NULL output report. Fields are populated with heap
529 * allocations the caller must release via #report_free. */
531 pf_bar_t* bars,
532 int n,
533 pf_report_t* out);
534
535/** Run a backtest with explicit timeframe and magnifier configuration.
536 *
537 * @param s Strategy handle.
538 * @param bars Bar feed.
539 * @param n Bar count.
540 * @param input_tf Input timeframe ("1", "5", "15", "60", "1D", ...).
541 * Empty string → auto-detect from bar timestamps.
542 * @param script_tf Script timeframe. Empty string → defaults to @p input_tf.
543 * @param bar_magnifier Boolean (0 / non-zero) — enable bar magnifier.
544 * @param magnifier_samples Sub-bar samples per parent bar (typical: 4).
545 * @param magnifier_dist Sampling distribution (see #pf_magnifier_distribution_t).
546 * @param out Output report. Free with #report_free. */
548 pf_bar_t* bars,
549 int n,
550 const char* input_tf,
551 const char* script_tf,
552 int bar_magnifier,
553 int magnifier_samples,
554 pf_magnifier_distribution_t magnifier_dist,
555 pf_report_t* out);
556
557/** Free heap arrays attached to a filled report.
558 *
559 * Idempotent. Safe to call with `NULL` or an already-freed report.
560 * The `pf_report_t` struct itself is caller-owned. */
562
563/** @} */ /* end of pf_lifecycle */
564
565/** @defgroup pf_config Per-strategy configuration
566 * @brief Override @c input.*() values, `strategy(...)` params, and runtime knobs.
567 * @{
568 */
569
570/** Override a Pine @c input.*() value before the next run.
571 *
572 * @param s Strategy handle.
573 * @param key The input's title (or fallback identifier).
574 * @param value Serialized value — numbers as decimal strings,
575 * booleans as `"true"` / `"false"`.
576 *
577 * Calls after #run_backtest are accepted but only take effect on
578 * subsequent runs. */
580 const char* key,
581 const char* value);
582
583/** Override a `strategy(...)` declaration parameter.
584 *
585 * Recognised @p key values: `initial_capital`, `commission_value`,
586 * `default_qty_value`, `pyramiding`, `slippage`,
587 * `process_orders_on_close`, `close_entries_rule`, `default_qty_type`,
588 * `commission_type`. */
590 const char* key,
591 const char* value);
592
593/** Toggle volume-weighted bar-magnifier sampling.
594 *
595 * Has no effect unless the bar magnifier is enabled in
596 * #run_backtest_full. */
598 int on);
599
600#endif /* PINEFORGE_NO_STRATEGY_DECLS */
601
602/* ───────────────────────────────────────────────────────────────────
603 * RUNTIME LIBRARY EXPORTS — implemented in libpineforge
604 * ─────────────────────────────────────────────────────────────────── */
605
606/** Toggle per-bar trace recording. Default off (zero-cost when off).
607 *
608 * Enables capture for `// @pf-trace name=expr` pragmas already compiled
609 * into the strategy `.so`. Trace records appear in pf_report_t::trace. */
611
612/** Set the earliest Unix-ms timestamp at which strategy order commands
613 * may fire.
614 *
615 * Earlier bars still execute user code and warm TA/series state, but
616 * `strategy.entry/order/exit/close` commands are ignored. */
618
619/** @} */ /* end of pf_config */
620
621/** @addtogroup pf_lifecycle
622 * @{
623 */
624
625/** Return the physical entry incarnation for one closed-trade row.
626 *
627 * Partial-close/FIFO fragments emitted from the same physical entry share
628 * this value. Distinct broker entry objects receive distinct monotonically
629 * increasing values even when Pine reuses the same user-visible entry ID.
630 * The value is scoped to one strategy run and is intended as report
631 * provenance, not as a stable cross-run identifier.
632 *
633 * @param s Strategy handle whose most recent run filled a report.
634 * @param trade_index Zero-based row index into that report's `trades`
635 * array — the script's closed trades followed by the
636 * range-end rows (`open_at_end`, ABI v3), which carry
637 * the incarnation of the lot they mark like any other
638 * close.
639 * @return Non-zero physical-entry identity, or 0 for an invalid index or a
640 * legacy/synthetic trade without request record provenance. */
642 pf_strategy_t s, int trade_index);
643
644/** @} */ /* end of pf_lifecycle */
645
646/** @defgroup pf_streaming Historical to realtime streaming
647 * @brief Warm on confirmed OHLCV and continue the same strategy instance on
648 * normalized ordered trades from any data source.
649 * @{
650 */
651
652/** Warm a strategy with confirmed OHLCV, then switch the same instance to a
653 * realtime trade stream without resetting position, equity, pending orders,
654 * Pine variables, TA state, request.security state, or timeframe aggregation.
655 *
656 * The warmup must contain at least one complete fixed-duration input bar.
657 * Normalized ticks start at or after the next input bar's open. This
658 * lifecycle uses close-only strategy calculation (the Pine strategy default)
659 * while resting broker orders are evaluated on every normalized trade.
660 *
661 * calc_on_order_fills, historical probe/tail overrides, timestamped FX,
662 * auxiliary and native security feeds are rejected. No every-tick strategy
663 * callback is provided; hand-written strategies follow the same lifecycle.
664 * @return 0 on success, -1 on failure. Inspect #strategy_get_last_error. */
666 const pf_bar_t* warmup_bars,
667 int n_warmup,
668 const char* input_tf,
669 const char* script_tf);
670
671/** Native live extension version (1). Additive to ABI v4; callers must probe
672 * this symbol before using the confirmed-bar/action API in older modules. */
674
675/** One physical emulator lot action. is_long describes the position side:
676 * buy = is_entry == is_long. A reversal is exits followed by an entry.
677 * price/time are emulator reference values, not external broker fills.
678 * Strings are borrowed until the next stream mutation or queue clear. */
679typedef struct pf_stream_order_action {
680 uint64_t sequence;
682 int32_t bar_index;
683 int32_t is_entry;
684 int32_t is_long;
685 double quantity;
686 double price;
687 const char* order_id;
688 const char* comment;
691
692/** Consume one confirmed input-timeframe bar. Its script-timeframe aggregate
693 * uses the existing batch OHLC fill kernel when complete. Close-only strategy
694 * calculation; no bar magnifier or synthetic trade ticks. The first tick/bar
695 * locks the feed mode. Off-grid/out-of-order data, invalid OHLCV and missing
696 * in-session bars fail. Calendar-closed intervals may be skipped. No padding.
697 * Returns 0/-1. On ANY input-processing failure discard/recover the instance:
698 * the operation is not transactional and can have advanced native state. */
700
701/** Queued physical fills since the last clear, in execution order. Historical
702 * warmup and report-only range-end rows never enqueue. Sequence starts at 1
703 * after warmup and is not reset by clear. Returns -1 on a null handle. */
705/** Copy one action; return 0 on success, -1 on invalid handle/index/output. */
708/** Clear observed events after the caller durably journals them. */
710/** Versioned deterministic fingerprint of observable broker/stream state.
711 * Excludes the consumable queue and arbitrary private strategy members.
712 * This is a replay check, not a complete state snapshot or cryptographic hash.
713 * Fresh replay must use deterministic strategy code, the same pinned engine
714 * build and configuration. Fingerprint representations may change between builds.
715 * Returns 0 for NULL. */
717
718/** Push one normalized realtime trade. Returns 0 on success, -1 on failure. */
720 const pf_trade_tick_t* tick);
721
722/** Push an ordered batch of realtime trades. Semantically identical to
723 * repeated #strategy_stream_push_tick calls, with lower FFI overhead. */
725 const pf_trade_tick_t* ticks,
726 int n);
727
728/** Advance the stream clock and close every input bar whose end is <= the
729 * supplied time. Quiet in-session intervals become zero-volume carry-forward
730 * bars; intervals outside the configured syminfo session are skipped. */
732
733/** End a realtime stream. When @p finalize_partial_input_bar is non-zero, the
734 * currently forming input bar is dispatched before ending; normally callers
735 * should first advance to a confirmed boundary and pass zero here. */
736PF_API int strategy_stream_end(pf_strategy_t s, int finalize_partial_input_bar);
737
738/** Snapshot the cumulative warmup + realtime report. The embedded arrays are
739 * caller-owned after return and must be released with #report_free. */
741
742/** @} */ /* end of pf_streaming */
743
744/** @defgroup pf_live Live-runtime surface (ABI v4)
745 * Default-off flags and read-only accessors used by pineforge-live. None of
746 * them changes a historical run unless enabled. @{ */
747/** Request cooperative abort of the run in progress (see c_abi.cpp). */
749/** 0 = completed, 1 = NOT_COMPLETED (aborted), -1 = @p s is NULL. */
751/** Live-runtime tail semantics (spec §3.1): the LAST bar of the array fed to
752 * every subsequent run() is a still-forming bar, not the chart's rightmost
753 * historical bar. This is persistent configuration, not a one-shot flag --
754 * it stays in effect until a caller passes @p on == 0, so a handle reused
755 * for a later plain historical replay must be explicitly turned back off.
756 * Effects when @p on is non-zero:
757 * 1. `barstate.islast` is false for that bar.
758 * 2. `session.islastbar` is computed from the bucket calendar (no next
759 * bar to peek at, so it evaluates whether the next bucket -- this
760 * bar's timestamp plus one script-TF step -- falls out of session).
761 * 3. `bar_index` stays put; `last_bar_index` is frozen at the horizon
762 * bar (`horizon_bars - 1`), when @p horizon_bars > 0. `last_bar_time`
763 * is exact when the horizon bar is in the script-bar array, one
764 * script-TF step per missing bar past the array's last bar
765 * otherwise; under aggregation (input_tf < script_tf) it is
766 * extrapolated from the first bar instead, and the aggregation-path
767 * caveat below applies.
768 * 4. The range-end synthetic close row/trade is skipped (no
769 * `open_at_end` row); the final equity point keeps `open_profit`.
770 * 5. Interior bars (every bar before the last) are unaffected.
771 * Dispatch-path scope: effects 1, 3, and 4 above are honoured on every
772 * dispatch path. Effect 2 (`session.islastbar` from the bucket calendar)
773 * is honoured only on `run_simple_bar_loop` (the input_tf == script_tf
774 * simple bar loop); the single-timeframe `run(bars, n)` overload never
775 * evaluates session predicates at all (pre-existing -- `session.ismarket`/
776 * `session.islastbar` stay at their reset-state `false` there regardless
777 * of this flag). On the non-magnifier aggregation path (input_tf <
778 * script_tf) effect 2 is UNDEFINED: the tail bar's `session.islastbar`
779 * reads the ordinary `in_session && barstate.islast` expression instead of
780 * the calendar lookahead (false there, since this flag also forces
781 * `barstate.islast` false). Callers must feed an input_tf == script_tf
782 * array until that gap closes, matching
783 * #strategy_set_probe_suppress_tail_logic's dispatch-path-scope caveat.
784 * Default off (@p on == 0): every historical run stays byte-identical to
785 * before this flag existed. The mode belongs to the Pine source host; on
786 * a host that models no still-forming tail bar (a bare kernel host) the
787 * call is accepted and inert, as it always was there. */
788PF_API void strategy_set_realtime_tail(pf_strategy_t s, int on, int horizon_bars);
789/** Live probe tail suppression (spec §3.2): the LAST bar of the array fed to
790 * every subsequent run() runs only the broker's pre-`on_bar` steps and
791 * returns, in this order: intraday-cap deferred close, advancing native
792 * source-series history (`_push_source_series`), settling resting
793 * stop/limit orders against the bar (native request matching), the
794 * max-intraday-loss path check (`evaluate_max_intraday_loss_over_path`),
795 * and updating per-trade extremes (`update_per_trade_extremes`).
796 * `on_bar` is never invoked for that bar, and nothing that ordinarily runs
797 * after it runs either -- no `invoke_chart_on_bar`, no
798 * `flush_same_bar_close`, no POOC second pass, no `process_margin_call`
799 * (and, under process_orders_on_close, the pre-script carried-position
800 * margin helpers), no `settle_dormant_bracket_reissues`, no
801 * post-liquidation sizing refresh. A margin call or intraday-cap close that
802 * would ordinarily fire against the forming bar therefore surfaces only at
803 * settlement (the next non-suppressed run), never against the
804 * still-forming probe bar itself.
805 * The run's last-bar fills are exactly the settled book's fills against
806 * the forming bar, and the post-run pending-order book is the book in
807 * force during that bar. This is persistent configuration, like
808 * #strategy_set_realtime_tail, and independent of it -- do not assume the
809 * two flags are coupled; set each explicitly.
810 * Dispatch-path scope (ABI v4 / live v1): this flag is honoured only on the
811 * standard `dispatch_bar` path -- the single-timeframe run loop and the
812 * input_tf == script_tf simple bar loop. It is a silent no-op under
813 * `calc_on_order_fills` (the COOF scheduler dispatches the last bar in full,
814 * `on_bar` included) and under the bar magnifier (`run_magnified_bar` never
815 * reaches `dispatch_bar`); both are gated features in v1 and a probe must
816 * not enable them. On the non-magnifier aggregation path
817 * (input_tf < script_tf) the semantics are UNDEFINED until the partial-
818 * bucket forming-bar flag lands: today the bar suppressed is whichever
819 * script bar is dispatched while walking the array's last input bar (a
820 * completed bucket, when that input bar opens a new one), and a trailing
821 * partial bucket is never dispatched at all. Callers must feed an
822 * input_tf == script_tf array until that flag exists.
823 * Clear this flag (on = 0) before `strategy_stream_begin`; the warmup
824 * replay is a run().
825 * Default off (@p on == 0): every historical run stays byte-identical to
826 * before this flag existed. Like #strategy_set_realtime_tail the mode
827 * belongs to the Pine source host; on a host that models no still-forming
828 * tail bar the call is accepted and inert, as it always was there. */
830/** Force this run's intrabar path order (ABI v4 live-runtime surface): the
831 * leg order every OHLC-path helper (`bar_path_uses_high_first` and
832 * everything built on it -- stop/limit fill priority, exit trail walking,
833 * dual-entry-stop arbitration, and bar-magnifier sub-bar sampling) uses for
834 * the CURRENT and every subsequent run(), until a caller sets a different
835 * mode. Values:
836 * - `0` AUTO (default): the unchanged TV-emulator rule -- the leg nearer
837 * `open` (by `|high-open|` vs `|open-low|`) goes first.
838 * - `1` HIGH_FIRST: force `O -> H -> L -> C` regardless of the bar's own
839 * shape.
840 * - `2` LOW_FIRST: force `O -> L -> H -> C` regardless of the bar's own
841 * shape.
842 * Any other @p mode is clamped to AUTO.
843 * A live probe runs the SAME forming bar under BOTH forced orders and emits
844 * only the fills that agree between the two -- a fill that depends on which
845 * leg TradingView's own (unobservable, still-forming) bar will resolve to
846 * is path-dependent and must be suppressed rather than guessed.
847 * This is persistent configuration, like #strategy_set_realtime_tail -- it
848 * stays in effect until a caller passes @p mode == 0, so a handle reused
849 * for a later plain historical replay must be explicitly set back to AUTO.
850 * Applies to run() only: a stream continued via #strategy_stream_begin
851 * dispatches its realtime ticks outside any run() and always sees AUTO,
852 * regardless of this setting.
853 * Default AUTO (@p mode == 0): every historical run stays byte-identical to
854 * before this flag existed. */
856/** The dual-entry-stop arbitration decided on the LAST bar the most recent
857 * run() dispatched: a flat position resting exactly one long stop-only
858 * ENTRY and one short stop-only ENTRY, both touched on that bar
859 * (`dual_entry_stop_path_winner`, internal). Values mirror
860 * `internal::DualEntryStopPathWinner`'s enumerator order:
861 * - `0` None -- no such pair was arbitrated on that bar (not flat, no
862 * matching pair, or neither/only one side touched).
863 * - `1` LongFirst -- the long stop's first-touch position on the intrabar
864 * path came first (or the two tied, which the engine always resolves
865 * in the long leg's favour).
866 * - `2` ShortFirst -- the short stop's first-touch position came first.
867 * - `-1` -- @p s is NULL.
868 * This is a per-BAR snapshot, not a live read of the engine's per-pass
869 * working state: it is written once, at the arbitration itself, and then
870 * holds for the rest of that bar even though the working state goes back
871 * to None the moment the winning side fills (position no longer flat) or
872 * its stop-entry admission is declined -- neither of which undoes the fact
873 * that TradingView's broker emulator arbitrated a real pair that bar. A
874 * caller therefore gets the right answer whether it reads this after a
875 * `strategy_set_probe_suppress_tail_logic` forming-bar probe (a single
876 * native request-matching pass) or after an ordinary
877 * `process_orders_on_close` run with no tail suppression (two passes, the
878 * winner already filled by the second).
879 * A live probe reads this after a forming-bar run to see which side the
880 * engine's own broker-emulator tie-break picked, without having to re-run
881 * and infer it from which of the two possible fills came back.
882 * Only the standard (non-`calc_on_order_fills`) dispatch path updates this
883 * value; it is a silent no-op under the COOF scheduler, mirroring
884 * #strategy_set_probe_suppress_tail_logic's dispatch-path-scope caveat. */
886/** Toggle per-script-bar broker-state hash recording (spec §3.4, ABI v4).
887 *
888 * When @p on is non-zero, every subsequent run() appends
889 * #strategy_broker_state_hash's value to pf_report_t::broker_state_hash
890 * immediately after each script bar is dispatched, so the array's length
891 * matches pf_report_t::script_bars_processed. Cleared (recorded array
892 * emptied, not the flag itself) at the start of every run(); the flag is
893 * persistent configuration, like #strategy_set_realtime_tail, and stays
894 * set until a caller passes @p on == 0.
895 * Also covers #strategy_stream_begin's warmup run() and every script bar
896 * dispatched afterward by the realtime tick stream, so
897 * #strategy_stream_fill_report's cumulative report satisfies the same
898 * len == script_bars_processed invariant. Set this BEFORE
899 * #strategy_stream_begin to also record the warmup leg -- reset_run_state()
900 * (which stream_begin's internal run() invokes) empties the recorded
901 * array, not the flag, but a flag flipped on only after stream_begin
902 * returns misses the warmup bars already dispatched.
903 * Default off (@p on == 0): pf_report_t::broker_state_hash is NULL /
904 * 0-length and every historical run stays byte-identical to before this
905 * flag existed.
906 *
907 * What a row is, and what it is for. A row is the run's CONTINUATION
908 * IDENTITY at that bar, not its trade outcome: it folds the broker state and,
909 * ahead of it, the state a resume would continue from -- the driving mode
910 * (batch run, stream warmup, stream realtime) included, on purpose. So the
911 * array is a replay check WITHIN one driving mode and deliberately not across
912 * modes: two runs driven the same way over the same bars record the same
913 * rows, and a run driven the same way that ended at bar k recorded, as its
914 * last row, the row the longer run recorded after bar k; but a run(), a
915 * #strategy_stream_begin with one warmup bar and one with every bar as warmup
916 * record DIFFERENT rows from index 0 over the same bars booking the same
917 * trades. Only the len == script_bars_processed identity above holds across
918 * drivings. What pins that a stream books what a batch books is the outcome
919 * itself -- the trades, the position and the equity -- and not this array. */
921/** Return the broker-state hash of the FINAL state after the most recent
922 * run() (see #strategy_set_broker_state_hash_recording's doc and
923 * pf_report_t::broker_state_hash for the per-bar recording; this accessor
924 * works whether or not recording was enabled). Compare only within the same
925 * pinned engine build and configuration; this is not a serialized checkpoint.
926 * Returns 0 when @p s is
927 * NULL. */
929/** Number of orders resting in the engine's pending-order book after the
930 * most recent run() (ABI v4 live-runtime surface, task 7, spec 3.6): the
931 * book in force for the NEXT bar. 0 when @p s is NULL. Read-only; a
932 * historical run is byte-identical whether or not a caller reads it. */
934/** Copy the @p index-th resting order (0-based, the engine's own book
935 * order -- insertion order; broker fill priority is decided at fill time
936 * from `created_seq`, not from this index) into @p out as a
937 * pf_pending_order_v1_t value snapshot. Copies
938 * min(@p size_in, sizeof(pf_pending_order_v1_t)) bytes: an older reader
939 * with a smaller struct receives a prefix (struct_version and size first),
940 * a newer reader with a larger one receives the whole v1 and must not
941 * read past pf_pending_order_v1_t::size. Strings are NUL-terminated
942 * char[64] copies with a `_truncated` flag and a `_hash64` (FNV-1a 64 of
943 * the full string); enums are int32 values; NaN sentinels are copied
944 * verbatim. Returns 0 on success, -1 -- with nothing written -- when
945 * @p s or @p out is NULL, @p index is out of range, or @p size_in < 8
946 * (too small to hold even the `struct_version` + `size` header; every
947 * larger @p size_in is honoured as a prefix copy). The layout is
948 * self-described by #strategy_pending_order_layout. */
949PF_API int strategy_pending_order_get(pf_strategy_t s, int index, void* out, size_t size_in);
950/** The field table of pf_pending_order_v1_t as THIS runtime compiled it --
951 * one pf_field_desc_t {name, type, offset, size} per field, in struct
952 * order, starting with `struct_version` and `size`. Static storage: the
953 * pointer stays valid for the life of the process and needs no handle.
954 * @p count (may be NULL) receives the row count. An FFI consumer builds
955 * its struct from this table rather than from a hand-typed copy, so the
956 * mirror can grow (append-only) without breaking it. */
958/** Engine-computed fill quantity of the @p index-th resting order (ABI v4
959 * live-runtime surface, task 8, spec 3.6): the contracts the entry kernel
960 * would OPEN if that order filled at @p fill_price, sized by the engine's
961 * own rules so a live runtime never re-implements them. @p fill_price is
962 * slipped the way the kernel slips it (`native_matching::apply_slippage`
963 * then the directional grid snap; an entry with a
964 * limit leg takes the unslipped limit-or-better route). @p partition
965 * receives which sizing rule produced the value:
966 * - `0` EXPLICIT -- a script-supplied qty: for `strategy.entry` the
967 * lot-floored contracts (`apply_qty_step`) of a fixed qty, or the
968 * explicit percent/cash budget sized at the fill for a per-call
969 * qty_type override; a `strategy.order` explicit qty is dispatched
970 * verbatim (no lot step).
971 * - `1` FROZEN_PLACEMENT -- a quantity fixed before the fill, never
972 * re-derived from the fill price: the default percent_of_equity /
973 * cash MARKET (or strategy.order) size frozen at the signal close
974 * (`frozen_default_qty`); a MARKET's frozen broker transaction (a
975 * finalized flat pair's `paired_flat_market_transaction_qty`; a
976 * same-bar-market member's `sbmt_tx_qty` from flat or as a kept
977 * over-cap add); or what one of the two MARKET reversal kernels
978 * opens -- a same-bar-market member against an opposite live position
979 * opens the remainder `sbmt_tx_qty - min(sbmt_tx_qty, live qty)`
980 * (`apply_same_bar_market_tx_reversal`), and the exact SHORT-seed
981 * collision's final short re-opens the residual
982 * `pyramid_entries[0].qty - pyramid_entries[1].qty` after closing both
983 * lots (`short_seed_collision_final_short_is_live`). Both kernels are
984 * modelled; each reports `close_only` 1 when it opens nothing.
985 * - `2` DEFAULT_STOP_PLACEMENT -- the DEFAULT percent_of_equity <= 100
986 * pure STOP entry's placement size (`default_stop_placement_qty`,
987 * round-7 family K), when `use_default_stop_placement_qty` says the
988 * fill consumes it: created flat, filling from flat, positive fill.
989 * - `3` AT_FILL -- default sizing at the slipped fill (`calc_qty`).
990 * @p close_only receives 1 when the kernel's close-only predicate fires
991 * -- the fill closes against the live opposite position and that
992 * predicate opens no leg of its own: the order's
993 * `affordability_close_only` (entry leg declined at placement), the
994 * priced-entry `prior_cycle_close_only` rule (opposite live position whose
995 * cycle the order was not placed in -- `created_position_side !=` the
996 * live side -- and not a KI-65 `reverses_same_bar_market_from_flat`), the
997 * same-cycle frozen explicit-FIXED transaction the close consumes exactly,
998 * a finalized flat MARKET pair against an opposite position, or one of the
999 * two reversal kernels above opening nothing. Where the order was created
1000 * FLAT the engine's close-only branch is `close_opposite_then_enter`: a
1001 * transaction larger than the live position still opens the remainder, so
1002 * a consumer compares @p qty with the live position. A replaced
1003 * default-percent short (`replaced_percent_short_market_is_live`) is
1004 * dispatched `close_opposite_then_enter` with its `frozen_default_qty`:
1005 * @p qty is that transaction, @p close_only 0. The probe answers for the
1006 * order filling against the CURRENT book and position; fills that an
1007 * earlier order in the same pass would make first are not simulated.
1008 * NOT folded into @p qty: the deferred-flip
1009 * carry (`tv_carry_qty`, added by `enter_market_from_flat` for a priced
1010 * entry firing from FLAT whose placement side is the opposite of the
1011 * requested side) -- read `tv_carry_qty` / `created_position_side` from
1012 * the mirror. Returns 0 on success; 1 -- with @p qty NaN, @p close_only 0,
1013 * @p partition -1 -- when the order is an EXIT (its fill quantity is
1014 * decided against the live position at the fill, not by a partition); -1
1015 * with nothing written when @p s is NULL, @p index is out of range, or any
1016 * out-pointer is NULL. Read-only: no historical run changes because a
1017 * caller probed it. */
1018PF_API int strategy_pending_order_fill_qty(pf_strategy_t s, int index, double fill_price,
1019 double* qty, int* close_only, int* partition);
1020/** 1 when the @p index-th resting order's entry-relative offsets
1021 * (`profit_ticks` / `loss_ticks` / `trail_points`) resolve now (ABI v4,
1022 * task 8): entries, plain orders and exits with an empty `from_entry`
1023 * always; an exit bound to a `from_entry` only once that id has filled in
1024 * the CURRENT position cycle -- the gate the engine's own
1025 * `materialize_relative_exit_prices_for_live_position` and eligibility
1026 * pass share. 0 otherwise; -1 when @p s is NULL or @p index is out of
1027 * range. */
1029/** The price levels the @p index-th resting order would fire at, as the
1030 * engine's fill path resolves them (ABI v4, task 8). A leg the order
1031 * carries as a price (`stop_price`, `limit_price`, `trail_price`) is
1032 * reported verbatim -- it is already on the price grid. A leg carried as
1033 * a tick offset is resolved against the live position's average entry
1034 * price only when #strategy_pending_order_level_resolved is 1 AND a
1035 * position is live, with the POSITION side's sign exactly as the fill
1036 * path: `limit = entry + dir * profit_ticks * mintick`, `stop = entry -
1037 * dir * loss_ticks * mintick` (dir = +1 long, -1 short; both
1038 * `level_on_price_grid`), and `trail_activation = entry +/- ceil(
1039 * trail_points - 5e-5) * mintick` snapped to the tick grid
1040 * (`trail_points` wins over `trail_price` when both are set, as in
1041 * the trail activation rule). NaN for a leg that is unset or not yet
1042 * resolvable. Returns 0; -1 with nothing written when @p s is NULL,
1043 * @p index is out of range, or any out-pointer is NULL. */
1045 double* limit, double* trail_activation);
1046/** The trail extreme the exit trail legs ride (`trail_best_price_`: the
1047 * running high of a long / low of a short since the position filled,
1048 * bar extremes folded in as the fill path folds them). NaN when @p s is
1049 * NULL and NaN until a position has filled. */
1051/** The live position's volume-weighted average entry price
1052 * (`position_entry_price_`). NaN when @p s is NULL and NaN when the
1053 * position is flat (the engine keeps 0 there; a live reader must not
1054 * mistake it for a price). */
1056/** The live position's cycle id (`position_cycle_seq_`): 0 when flat, a
1057 * fresh nonzero id per open or reversal, unchanged across same-direction
1058 * adds -- the id `created_position_cycle_seq` on a mirrored order refers
1059 * to. -1 when @p s is NULL. */
1061/** Task 9: closed-trade id / exit-comment string accessors, indexing the
1062 * same REPORT row space as #strategy_closed_trade_entry_incarnation
1063 * (`trades_` then the range-end rows, `open_at_end`). The returned pointer
1064 * is valid until the next run() (or stream call) on this handle, like
1065 * #strategy_get_last_error. NULL on a NULL @p s or an out-of-range
1066 * @p trade_index. */
1068/** See #strategy_closed_trade_entry_id for the row-space/lifetime/NULL
1069 * contract. The returned string is the engine's own INTERNAL exit id, not
1070 * always the script's `strategy.exit`/`strategy.close` id verbatim:
1071 * - a real `strategy.exit` bracket leg -- the user's own id, unchanged.
1072 * - a `strategy.close(id, ...)` close -- `"__close__" + id`.
1073 * - `strategy.close_all()` / a bare-id `strategy.close()` -- the literal
1074 * `"__close__"` (empty target id appended).
1075 * - a margin-call forced liquidation -- the sentinel `"__margin_call__"`.
1076 * - an intraday-cap close (`risk.max_intraday_loss`, or the
1077 * max-filled-orders cap) -- empty (`""`).
1078 * A caller matching exit ids back to its own `strategy.close` calls should
1079 * strip the `"__close__"` prefix rather than compare verbatim. */
1081/** See #strategy_closed_trade_entry_id. */
1083/** Task 9: why the @p trade_index-th REPORT-row closed trade exited.
1084 * Values:
1085 * - `0` UNKNOWN -- reserved for the documented "no cause" value on a
1086 * VALID trade. Every in-range row currently falls through the
1087 * derivation below to at worst `1` SCRIPT, so no live derivation
1088 * today actually returns `0`; it is not used for a bad index (see
1089 * `-1` below, final review F7).
1090 * - `1` SCRIPT -- a `strategy.close` / `strategy.close_all` market close,
1091 * or a reversal-driven close.
1092 * - `2` BRACKET -- a `strategy.exit` stop/limit/trail/profit/loss leg.
1093 * - `3` MARGIN_CALL -- a forced liquidation slice.
1094 * - `4` INTRADAY_LOSS_CAP -- `risk.max_intraday_loss`.
1095 * - `5` INTRADAY_FILL_CAP -- the max-filled-orders intraday cap.
1096 * - `6` RANGE_END -- the still-open position closed at the end of a
1097 * flag-off run (`open_at_end`, ABI v3) -- this always wins over every
1098 * other cause below it.
1099 * Derivation order (see `BacktestEngine::closed_trade_close_cause`,
1100 * engine_trade_accessors.cpp): `open_at_end` -> 6; then the cause the
1101 * CLOSER recorded on the row (`execution::CloseCause`, whose numbers are
1102 * exactly these values) -- a kernel-originated liquidation or risk flatten
1103 * states it through the settling fill, and a host running its own
1104 * forced-close policy states it on the row it produced, which is where the
1105 * Pine adapter's margin-call (3), max-intraday-loss (4) and filled-order-cap
1106 * (5) rows get their value; then the row's `exit_from_bracket` flag -- true
1107 * only for a REAL `strategy.exit` leg, either an `OrderType::EXIT` fill
1108 * whose id does NOT carry the internal `"__close__"` prefix that a deferred
1109 * `strategy.close`/`close_all` order is also given (that path reuses the
1110 * same `OrderType::EXIT` fill machinery), or a whole-position bracket
1111 * revived and fired at the margin-call event price
1112 * (`revive_position_brackets_after_margin_call_partial`) -- -> 2;
1113 * otherwise 1.
1114 * A Pine/source run's values are unchanged. A bare native host that declares
1115 * a kernel margin model now reads `3` for its own liquidation rows (and `4`
1116 * for a kernel risk flatten) where the retired string derivation, which only
1117 * recognised the adapter's sentinels, answered `1`.
1118 * `-1` when @p s is NULL, or when @p trade_index is out of range (final
1119 * review F7: matches every sibling indexed live accessor's -1-on-bad-index
1120 * convention -- #strategy_pending_order_fill_qty,
1121 * #strategy_pending_order_level_resolved,
1122 * #strategy_pending_order_effective_levels). `0` therefore never
1123 * ambiguously means "bad index"; a caller can tell "row exists but reads
1124 * UNKNOWN" apart from "bad index" without a separate
1125 * #strategy_closed_trade_entry_incarnation `report_trade_count` bounds
1126 * check first, though doing that check is still good practice. */
1128/** Task 9: the script-facing signed position size (`strategy.position_size`;
1129 * KI-64 freeze-aware -- while a same-bar `process_orders_on_close` close is
1130 * frozen for the current bar, this reads the PRE-close position, matching
1131 * what the script itself observes). NaN when @p s is NULL. */
1133/** Task 9: initial capital plus realized net profit
1134 * (`strategy.initial_capital + strategy.netprofit`). NOT Pine's
1135 * `strategy.equity`, which adds open profit on top of this (unlike the
1136 * last point of `pf_report_t::equity_curve`, which does). NaN when @p s is
1137 * NULL. */
1139/** Task 9: total SCRIPT bars dispatched by the most recent run() (mirrors
1140 * `pf_report_t::script_bars_processed`, engine_report.cpp) -- includes a
1141 * stream's warmup leg and every realtime tick-driven bar dispatched
1142 * afterward by #strategy_stream_push_tick / #strategy_stream_push_ticks.
1143 * `-1` when @p s is NULL. */
1145/** @} */
1146
1147/** @addtogroup pf_config
1148 * @{
1149 */
1150
1151/** Set the strategy's chart timezone (IANA / POSIX TZ string).
1152 *
1153 * Pine builtins ``hour``, ``minute``, ``second``, ``dayofmonth``,
1154 * ``dayofweek``, ``month``, ``year`` and ``weekofyear`` return the
1155 * wall-clock for the chart's timezone — TV exports trade rows in chart
1156 * TZ too. Engine bars are stored as Unix-ms (UTC), so without this
1157 * override these builtins return UTC and silently diverge from TV by N
1158 * hours when the chart is on a non-UTC zone (Asia/Taipei = UTC+8 is the
1159 * validator default).
1160 *
1161 * Pass `NULL`, `""`, `"UTC"` or `"Etc/UTC"` for the legacy UTC
1162 * behaviour (cheap, mutex-free). Any other value names a TZ resolved by
1163 * the system tzdata; the per-bar decomposition then runs under a
1164 * process-global mutex so multi-threaded harnesses don't corrupt each
1165 * other's wall time.
1166 *
1167 * Should be called before #run_backtest / #run_backtest_full. Persists
1168 * across runs on the same strategy handle until overridden. */
1170
1171/** Plumb the symbol's exchange timezone (IANA string) into syminfo. Feeds
1172 * ``session.ismarket`` / ``time(session)`` predicates. Defaults to "UTC"
1173 * (crypto). Distinct from #strategy_set_chart_timezone — the chart TZ
1174 * drives wall-clock builtins and intraday-cap day rollover; this drives
1175 * session membership. `NULL` is ignored. Call before #run_backtest / #run_backtest_full. */
1177
1178/** Set the symbol's session string (e.g. "0930-1600:23456", default
1179 * "24x7"). Feeds ``session.ismarket`` / ``time(session)``. `NULL`
1180 * ignored. Call before #run_backtest / #run_backtest_full. */
1182
1183/** Set the instrument class (``syminfo.type``: "forex", "stock", "crypto",
1184 * "futures", "index", "fund", "cfd", ...; default "crypto"). Scripts branch
1185 * on it for instrument conventions (e.g. the forex pip size). `NULL` /
1186 * empty ignored. Call before #run_backtest / #run_backtest_full. */
1188
1189/** Set one of the remaining string members of ``syminfo`` by Pine member
1190 * name: "ticker", "tickerid", "currency", "basecurrency", "description",
1191 * "volumetype" (and "type"). Returns 0 when set, -1 for an unknown key,
1192 * empty value or NULL. Call before #run_backtest / #run_backtest_full. */
1194 const char* value);
1195
1196/** Set the instrument tick size (``syminfo.mintick``, default 0.01). Drives the
1197 * directional stop-entry snap and ``slippage = N*mintick`` economics. Set
1198 * per-instrument (e.g. 0.25 for ES, 0.00001 for FX). Non-positive ignored.
1199 * Call before #run_backtest / #run_backtest_full. */
1201
1202/** Set the instrument point value (``syminfo.pointvalue``, default 1.0) — the
1203 * $-per-point-per-contract multiplier applied to every money path: realized
1204 * PnL and MFE/MAE, open profit / mark-to-market equity (and the drawdown /
1205 * runup extremes), percent-of-equity and cash position sizing, percent
1206 * commission notionals, and the margin admission check. Set per-instrument
1207 * (e.g. 50 for ES). Non-positive ignored. Call before #run_backtest / #run_backtest_full. */
1209
1210/** Inject a fundamental/exchange metadata value by Pine member name
1211 * (e.g. "shares_outstanding_total", "target_price_average"). These have
1212 * no OHLCV source; reads of un-injected members return na. Call before
1213 * #run_backtest / #run_backtest_full. */
1215 double value);
1216
1217/** Install a timestamped quote-to-account currency conversion curve.
1218 *
1219 * Each value is account-currency units per one unit of the symbol's quote
1220 * currency and becomes active, inclusively, at the corresponding Unix-ms
1221 * timestamp. The latest active value carries forward; broker events before
1222 * the first point use the scalar `account_currency_fx` metadata fallback.
1223 * Installing a curve also selects the converted account-currency broker
1224 * ledger, including during that pre-first fallback interval; it is not
1225 * equivalent to a same-currency run merely because a rate happens to be 1.
1226 * Arrays are copied. Timestamps must be strictly increasing and rates
1227 * positive and finite. Pass `n == 0` to clear the curve and restore scalar
1228 * behavior. Timestamped curves currently support ordinary historical runs.
1229 * Broker-open rate changes: carried 1× longs and 1× shorts are supported;
1230 * leveraged positions fail closed at the crossing. Streaming,
1231 * calc-on-order-fills, and bar-magnifier runs
1232 * also fail closed.
1233 *
1234 * @return 0 on success, -1 for a null strategy or invalid arrays. */
1236 pf_strategy_t s, const int64_t* effective_from_ms,
1237 const double* account_per_quote, int n);
1238
1239#ifdef PINEFORGE_HAS_AUX_SECURITY_FEED_V1
1240/** Copy a finer feed used exclusively by same-symbol request.security calls.
1241 *
1242 * The next ordinary #run_backtest_full call must receive native chart bars
1243 * with @c input_tf equal to @c script_tf and bar magnifier disabled. Chart
1244 * OHLCV, broker fills, and @c bar_index continue to advance only from that
1245 * native chart feed; @p bars advance only request.security evaluators.
1246 * Every auxiliary bar must map to exactly one native chart bar and every
1247 * native chart bar must have at least one auxiliary bar, otherwise the run
1248 * fails closed via #strategy_get_last_error. Arrays are copied. Pass
1249 * @p n == 0 to clear the auxiliary feed.
1250 *
1251 * @return 0 on success, -1 for a null strategy or invalid input. */
1253 const pf_bar_t* bars,
1254 int n,
1255 const char* input_tf);
1256#endif
1257
1258#ifdef PINEFORGE_HAS_NATIVE_SECURITY_FEED_V1
1259/** Copy the exchange's OWN bars of one higher timeframe for same-symbol
1260 * request.security calls that request exactly that timeframe.
1261 *
1262 * On intraday charts of CME futures and US/Indian equities TradingView's
1263 * request.security(syminfo.tickerid, "D", close) returns the exchange's
1264 * daily bar -- the 15:00 CT settlement on ES1!/NQ1!, the official closing
1265 * print on NASDAQ/NYSE/NSE -- which no aggregation of the intraday feed can
1266 * reproduce. With a native feed installed for @p timeframe ("D", "1D", "W",
1267 * ...), every completed request.security bucket of that timeframe carries
1268 * the native bar's OHLCV (matched by the bucket's own label: the session-day
1269 * stamp for D/W/M, the grid open for intraday) instead of the aggregate;
1270 * bucket TIMING -- when the bar completes relative to the chart -- and every
1271 * other timeframe's request are unchanged, and so are chart OHLCV, broker
1272 * fills and bar_index. A "W" / "M" request with no feed of its own reads
1273 * the daily feed's bars aggregated per week / month (TradingView builds its
1274 * W/M bars from the native daily bars, never from intraday prints), keyed by
1275 * the period's first session-day. A completed bucket with no native bar
1276 * keeps its aggregate (counted, not fatal). Bars must be strictly
1277 * increasing; arrays are copied; pass @p n == 0 to clear the feed for
1278 * @p timeframe. Historical runs only: stream_begin() fails closed while a
1279 * native feed is installed.
1280 *
1281 * @return 0 on success, -1 for a null strategy or invalid input. */
1283 const char* timeframe,
1284 const pf_bar_t* bars,
1285 int n);
1286#endif
1287
1288/** Returns the error message captured by the most recent #run_backtest /
1289 * #run_backtest_full call on this strategy.
1290 *
1291 * Returns an empty string when the run completed normally, or `NULL`
1292 * only when `s` itself is `NULL`. The pointer is owned by the engine
1293 * and remains valid until the next #run_backtest / #run_backtest_full call (which clears
1294 * the captured error before it begins).
1295 *
1296 * The runtime catches every `std::exception` derivative inside the
1297 * engine's run loop so the C ABI never unwinds a C++ exception across
1298 * the `extern "C"` boundary. Consumers must check this after every
1299 * run to surface engine-rejected configurations such as a script
1300 * timeframe finer than the input timeframe, a `request.security`
1301 * timeframe below the chart timeframe without a supported lower-TF
1302 * emulation, or a missing input timeframe when securities are
1303 * registered. */
1305
1306/** @} */ /* end of pf_config */
1307
1308/** @defgroup pf_version Version query
1309 * @brief Runtime version metadata.
1310 * @{
1311 */
1312
1313/** Runtime version descriptor returned by #pf_version_get. */
1314typedef struct pf_version_s {
1315 int major; /**< Major version. */
1316 int minor; /**< Minor version. */
1317 int patch; /**< Patch version. */
1318 const char* commit_sha; /**< Short git commit SHA, or `""` if unknown. */
1319} pf_version_t;
1320
1321/** @return Linked runtime version. */
1323
1324/** @return Monotonic ABI version (see #PF_ABI_VERSION). */
1326
1327/** Full git-derived version descriptor.
1328 *
1329 * Returns `"MAJOR.MINOR.PATCH[-N-gSHA[-dirty]]"` for git checkouts, or
1330 * plain `"MAJOR.MINOR.PATCH"` for tarball builds. The pointer is to a
1331 * static string with program lifetime; do not free. */
1332PF_API const char* pf_version_string(void);
1333
1334/** @} */ /* end of pf_version */
1335
1336#ifdef __cplusplus
1337} /* extern "C" */
1338#endif
1339
1340/* The C-level native host API (R5 lane L13). Included last, after the PODs
1341 * it builds on (pf_bar_t, pf_report_t, pf_strategy_t) and outside this
1342 * header's `extern "C"` block, which it opens for itself. Both headers guard
1343 * their own include, so either include order resolves. Its PF_API symbols are
1344 * pinned separately by scripts/check_c_abi_runtime.py, which is why they do
1345 * not move this header's own declaration count. */
1346#include <pineforge/native_c_api.h>
1347
1348#endif /* PINEFORGE_H */
void strategy_set_syminfo_timezone(pf_strategy_t s, const char *tz)
Plumb the symbol's exchange timezone (IANA string) into syminfo.
void strategy_set_syminfo_mintick(pf_strategy_t s, double mintick)
Set the instrument tick size (syminfo.mintick, default 0.01).
void strategy_set_override(pf_strategy_t s, const char *key, const char *value)
Override a strategy(...) declaration parameter.
void strategy_set_input(pf_strategy_t s, const char *key, const char *value)
Override a Pine input.
const char * strategy_get_last_error(pf_strategy_t s)
Returns the error message captured by the most recent run_backtest / run_backtest_full call on this s...
int strategy_set_native_security_feed(pf_strategy_t s, const char *timeframe, const pf_bar_t *bars, int n)
Copy the exchange's OWN bars of one higher timeframe for same-symbol request.security calls that requ...
void strategy_set_syminfo_session(pf_strategy_t s, const char *session)
Set the symbol's session string (e.g.
void strategy_set_syminfo_pointvalue(pf_strategy_t s, double pointvalue)
Set the instrument point value (syminfo.pointvalue, default 1.0) — the $-per-point-per-contract multi...
void strategy_set_chart_timezone(pf_strategy_t s, const char *tz)
Set the strategy's chart timezone (IANA / POSIX TZ string).
int strategy_set_aux_security_feed(pf_strategy_t s, const pf_bar_t *bars, int n, const char *input_tf)
Copy a finer feed used exclusively by same-symbol request.security calls.
void strategy_set_magnifier_volume_weighted(pf_strategy_t s, int on)
Toggle volume-weighted bar-magnifier sampling.
void strategy_set_trade_start_time(pf_strategy_t s, int64_t timestamp_ms)
Set the earliest Unix-ms timestamp at which strategy order commands may fire.
void strategy_set_syminfo_metadata(pf_strategy_t s, const char *key, double value)
Inject a fundamental/exchange metadata value by Pine member name (e.g.
void strategy_set_trace_enabled(pf_strategy_t s, int on)
Toggle per-bar trace recording.
int strategy_set_syminfo_string(pf_strategy_t s, const char *key, const char *value)
Set one of the remaining string members of syminfo by Pine member name: "ticker", "tickerid",...
int strategy_set_account_currency_fx_series(pf_strategy_t s, const int64_t *effective_from_ms, const double *account_per_quote, int n)
Install a timestamped quote-to-account currency conversion curve.
void strategy_set_syminfo_type(pf_strategy_t s, const char *type)
Set the instrument class (syminfo.type: "forex", "stock", "crypto", "futures", "index",...
int strategy_execution_contract(pf_strategy_t s)
Native execution contract query.
uint64_t strategy_closed_trade_entry_incarnation(pf_strategy_t s, int trade_index)
Return the physical entry incarnation for one closed-trade row.
int strategy_configure_native_fx_curve_v1(pf_strategy_t s, const pf_native_fx_curve_v1 *curve)
Stage an immutable account-currency FX curve on a Ready native handle.
pf_strategy_t strategy_create(const char *params_json)
Allocate a new strategy instance.
void run_backtest(pf_strategy_t s, pf_bar_t *bars, int n, pf_report_t *out)
Run a backtest with auto-detected timeframe and no bar magnifier.
void strategy_free(pf_strategy_t s)
Release a strategy handle previously returned by strategy_create.
void report_free(pf_report_t *report)
Free heap arrays attached to a filled report.
int strategy_configure_native_v1(pf_strategy_t s, const pf_native_run_spec_v1 *spec)
Apply a native v1 specification.
void run_backtest_full(pf_strategy_t s, pf_bar_t *bars, int n, const char *input_tf, const char *script_tf, int bar_magnifier, int magnifier_samples, pf_magnifier_distribution_t magnifier_dist, pf_report_t *out)
Run a backtest with explicit timeframe and magnifier configuration.
double strategy_current_equity(pf_strategy_t s)
Task 9: initial capital plus realized net profit (strategy.initial_capital + strategy....
void strategy_set_probe_suppress_tail_logic(pf_strategy_t s, int on)
Live probe tail suppression (spec §3.2): the LAST bar of the array fed to every subsequent run() runs...
const char * strategy_closed_trade_exit_id(pf_strategy_t s, int trade_index)
See strategy_closed_trade_entry_id for the row-space/lifetime/NULL contract.
void strategy_set_path_order(pf_strategy_t s, int mode)
Force this run's intrabar path order (ABI v4 live-runtime surface): the leg order every OHLC-path hel...
int64_t strategy_script_bars_processed(pf_strategy_t s)
Task 9: total SCRIPT bars dispatched by the most recent run() (mirrors pf_report_t::script_bars_proce...
void strategy_request_abort(pf_strategy_t s)
Request cooperative abort of the run in progress (see c_abi.cpp).
int strategy_closed_trade_close_cause(pf_strategy_t s, int trade_index)
Task 9: why the trade_index-th REPORT-row closed trade exited.
double strategy_position_avg_price(pf_strategy_t s)
The live position's volume-weighted average entry price (position_entry_price_).
const char * strategy_closed_trade_entry_id(pf_strategy_t s, int trade_index)
Task 9: closed-trade id / exit-comment string accessors, indexing the same REPORT row space as strate...
const char * strategy_closed_trade_exit_comment(pf_strategy_t s, int trade_index)
See strategy_closed_trade_entry_id.
int strategy_pending_orders_len(pf_strategy_t s)
Number of orders resting in the engine's pending-order book after the most recent run() (ABI v4 live-...
int64_t strategy_position_cycle_seq(pf_strategy_t s)
The live position's cycle id (position_cycle_seq_): 0 when flat, a fresh nonzero id per open or rever...
int strategy_pending_order_effective_levels(pf_strategy_t s, int index, double *stop, double *limit, double *trail_activation)
The price levels the index-th resting order would fire at, as the engine's fill path resolves them (A...
int strategy_pending_order_level_resolved(pf_strategy_t s, int index)
1 when the index-th resting order's entry-relative offsets (profit_ticks / loss_ticks / trail_points)...
int strategy_pending_order_fill_qty(pf_strategy_t s, int index, double fill_price, double *qty, int *close_only, int *partition)
Engine-computed fill quantity of the index-th resting order (ABI v4 live-runtime surface,...
int strategy_last_bar_dual_entry_path(pf_strategy_t s)
The dual-entry-stop arbitration decided on the LAST bar the most recent run() dispatched: a flat posi...
double strategy_position_size(pf_strategy_t s)
Task 9: the script-facing signed position size (strategy.position_size; KI-64 freeze-aware – while a ...
int strategy_pending_order_get(pf_strategy_t s, int index, void *out, size_t size_in)
Copy the index-th resting order (0-based, the engine's own book order – insertion order; broker fill ...
const pf_field_desc_t * strategy_pending_order_layout(int *count)
The field table of pf_pending_order_v1_t as THIS runtime compiled it – one pf_field_desc_t {name,...
int strategy_last_run_status(pf_strategy_t s)
0 = completed, 1 = NOT_COMPLETED (aborted), -1 = s is NULL.
double strategy_trail_best_price(pf_strategy_t s)
The trail extreme the exit trail legs ride (trail_best_price_: the running high of a long / low of a ...
void strategy_set_broker_state_hash_recording(pf_strategy_t s, int on)
Toggle per-script-bar broker-state hash recording (spec §3.4, ABI v4).
uint64_t strategy_broker_state_hash(pf_strategy_t s)
Return the broker-state hash of the FINAL state after the most recent run() (see strategy_set_broker_...
void strategy_set_realtime_tail(pf_strategy_t s, int on, int horizon_bars)
Live-runtime tail semantics (spec §3.1): the LAST bar of the array fed to every subsequent run() is a...
int strategy_stream_begin(pf_strategy_t s, const pf_bar_t *warmup_bars, int n_warmup, const char *input_tf, const char *script_tf)
Warm a strategy with confirmed OHLCV, then switch the same instance to a realtime trade stream withou...
int strategy_stream_push_tick(pf_strategy_t s, const pf_trade_tick_t *tick)
Push one normalized realtime trade.
int strategy_stream_order_actions_len(pf_strategy_t s)
Queued physical fills since the last clear, in execution order.
int strategy_stream_push_ticks(pf_strategy_t s, const pf_trade_tick_t *ticks, int n)
Push an ordered batch of realtime trades.
uint64_t strategy_stream_state_hash(pf_strategy_t s)
Versioned deterministic fingerprint of observable broker/stream state.
int strategy_stream_api_version(void)
Native live extension version (1).
int strategy_stream_advance_time(pf_strategy_t s, int64_t timestamp_ms)
Advance the stream clock and close every input bar whose end is <= the supplied time.
int strategy_stream_push_bar(pf_strategy_t s, const pf_bar_t *bar)
Consume one confirmed input-timeframe bar.
int strategy_stream_end(pf_strategy_t s, int finalize_partial_input_bar)
End a realtime stream.
int strategy_stream_fill_report(pf_strategy_t s, pf_report_t *out)
Snapshot the cumulative warmup + realtime report.
int strategy_stream_order_action_get(pf_strategy_t s, int index, pf_stream_order_action_t *out)
Copy one action; return 0 on success, -1 on invalid handle/index/output.
void strategy_stream_order_actions_clear(pf_strategy_t s)
Clear observed events after the caller durably journals them.
pf_magnifier_distribution_t
Bar-magnifier sub-bar sampling distribution.
Definition pineforge.h:115
@ PF_MAGNIFIER_FRONT_LOADED
Sample density biased toward bar open.
Definition pineforge.h:120
@ PF_MAGNIFIER_COSINE
Cosine-tapered density.
Definition pineforge.h:117
@ PF_MAGNIFIER_ENDPOINTS
Default — exact O,H,L,C points plus uniform fill between.
Definition pineforge.h:119
@ PF_MAGNIFIER_BACK_LOADED
Sample density biased toward bar close.
Definition pineforge.h:121
@ PF_MAGNIFIER_TRIANGLE
Triangle-tapered density.
Definition pineforge.h:118
@ PF_MAGNIFIER_UNIFORM
Uniform spacing across the parent bar.
Definition pineforge.h:116
int pf_abi_version(void)
pf_version_t pf_version_get(void)
const char * pf_version_string(void)
Full git-derived version descriptor.
void * pf_strategy_t
Opaque handle to a compiled strategy instance.
Definition pineforge.h:433
#define PF_API
Definition pineforge.h:70
Single OHLCV bar pushed into the engine.
Definition pineforge.h:127
double volume
Bar volume.
Definition pineforge.h:132
double high
High price.
Definition pineforge.h:129
double low
Low price.
Definition pineforge.h:130
double close
Close price.
Definition pineforge.h:131
double open
Open price.
Definition pineforge.h:128
int64_t timestamp
Bar open time, Unix milliseconds.
Definition pineforge.h:133
Single per-script-bar equity point.
Definition pineforge.h:325
double open_profit
Mark-to-market open P&L at bar close.
Definition pineforge.h:328
double equity
initial_capital + net_profit + open_profit.
Definition pineforge.h:327
int64_t time_ms
Script-bar OPEN timestamp (Unix ms).
Definition pineforge.h:326
Equity-curve-derived statistics (all-trades only, like TV).
Definition pineforge.h:259
double sharpe_tv
Deprecated spelling of pf_equity_stats_s::sharpe_monthly.
Definition pineforge.h:283
double sharpe_monthly
Definition pineforge.h:282
double time_in_market_pct
PERCENT (0-100) of script bars with an open position at bar close.
Definition pineforge.h:309
double max_equity_drawdown_pct
max_equity_drawdown relative to the peak in effect (PERCENT 0-100).
Definition pineforge.h:261
double max_equity_drawdown
Peak-to-trough equity drop, positive currency magnitude.
Definition pineforge.h:260
double max_equity_runup_pct
max_equity_runup relative to that trough (PERCENT 0-100).
Definition pineforge.h:265
double buy_hold_return
initial_capital * (last_close/first_open - 1), currency.
Definition pineforge.h:266
double open_pl
Mark-to-market open profit at the final bar.
Definition pineforge.h:311
double max_equity_runup
Trough-to-peak rise where the trough resets on each new equity peak (mirrors the engine's intra-run e...
Definition pineforge.h:263
double sortino_tv
Deprecated spelling of pf_equity_stats_s::sortino_monthly.
Definition pineforge.h:293
double cagr
PERCENT per year: 100*((final_equity/initial_capital)^(1/years)-1).
Definition pineforge.h:303
double recovery_factor
net_profit / max_equity_drawdown (currency / currency).
Definition pineforge.h:307
double calmar
cagr / max_equity_drawdown_pct — BOTH IN PERCENT, so the ratio is dimensionless.
Definition pineforge.h:305
double buy_hold_return_pct
buy_hold_return as PERCENT.
Definition pineforge.h:268
double sharpe_bar
Per-script-bar returns, annualized by observed bar density (bars per year = (len-1)/calendar span),...
Definition pineforge.h:296
double sortino_monthly
Definition pineforge.h:292
double sortino_bar
Same construction as sharpe_bar over per-bar returns; uses population downside deviation.
Definition pineforge.h:300
Composite metrics container: trade stats (all / long / short) + equity-curve stats.
Definition pineforge.h:316
pf_trade_stats_t all
Definition pineforge.h:317
pf_equity_stats_t equity
Definition pineforge.h:318
pf_trade_stats_t longs
Definition pineforge.h:317
pf_trade_stats_t shorts
Definition pineforge.h:317
Immutable timestamped account-currency FX curve for a native run.
Definition pineforge.h:488
const int64_t * effective_from_ms
Definition pineforge.h:491
const double * account_per_quote
Definition pineforge.h:492
Versioned native run specification.
Definition pineforge.h:469
const char * tickerid
Definition pineforge.h:473
uint32_t allowed_open_directions
Definition pineforge.h:477
const char * volumetype
Definition pineforge.h:473
const char * basecurrency
Definition pineforge.h:473
const char * input_tf
Definition pineforge.h:472
const char * session_key
Definition pineforge.h:471
double initial_margin_fraction
Definition pineforge.h:478
const char * type
Definition pineforge.h:473
const char * script_tf
Definition pineforge.h:472
const char * chart_timezone
Definition pineforge.h:474
const char * timezone
Definition pineforge.h:474
const char * ticker
Definition pineforge.h:473
const char * session
Definition pineforge.h:474
const char * description
Definition pineforge.h:473
const char * currency
Definition pineforge.h:473
Backtest report filled by run_backtest / run_backtest_full.
Definition pineforge.h:365
int input_tf_seconds
Detected/configured input timeframe (seconds).
Definition pineforge.h:389
int security_diag_len
Length of security_diag.
Definition pineforge.h:397
int64_t security_feeds_total
Total higher-TF feed bars across all security sites.
Definition pineforge.h:380
int bar_magnifier_enabled
1 if magnifier was active for this run.
Definition pineforge.h:393
pf_trace_entry_t * trace
Per-bar trace records (empty unless tracing enabled).
Definition pineforge.h:400
int trace_names_len
Length of trace_names.
Definition pineforge.h:403
int64_t input_bars_processed
Source-feed bars consumed.
Definition pineforge.h:376
int script_tf_seconds
Script timeframe (seconds).
Definition pineforge.h:390
double net_profit
Sum of all closed-trade PnL.
Definition pineforge.h:373
int64_t equity_curve_len
Definition pineforge.h:418
int trades_len
Length of trades.
Definition pineforge.h:372
pf_metrics_t metrics
Definition pineforge.h:409
int trace_len
Length of trace.
Definition pineforge.h:401
int64_t script_bars_processed
Script-timeframe bars evaluated.
Definition pineforge.h:377
int64_t magnifier_sample_ticks_total
Sample ticks visited by the magnifier.
Definition pineforge.h:386
int total_trades
Closed-trade count (== trades_len), including the range-end close of a position still open after the ...
Definition pineforge.h:367
int64_t security_partial_total
Total partial-bar evals across all security sites.
Definition pineforge.h:382
pf_security_diag_t * security_diag
One entry per request.security site.
Definition pineforge.h:396
int64_t magnifier_sub_bars_total
Sub-bars synthesized by the magnifier.
Definition pineforge.h:385
int64_t broker_state_hash_len
Definition pineforge.h:427
uint64_t * broker_state_hash
Definition pineforge.h:426
const char ** trace_names
Names indexed by pf_trace_entry_t::name_id.
Definition pineforge.h:402
pf_equity_point_t * equity_curve
Definition pineforge.h:417
int64_t security_complete_total
Total complete-bar evals across all security sites.
Definition pineforge.h:381
int script_tf_ratio
script_tf_seconds / input_tf_seconds.
Definition pineforge.h:391
int needs_aggregation
1 if input → script TF aggregation was performed.
Definition pineforge.h:392
pf_trade_t * trades
Heap array of closed trades; script-driven exits first, then the range-end rows (open_at_end=1).
Definition pineforge.h:370
Per-request.security() site diagnostic counters.
Definition pineforge.h:334
int sec_id
Stable id for the request.security site.
Definition pineforge.h:335
int64_t feed_count
Higher-TF feed bars consumed.
Definition pineforge.h:336
int64_t complete_count
Evaluations on completed parent bars.
Definition pineforge.h:337
int64_t partial_count
Evaluations on still-forming parent bars.
Definition pineforge.h:338
One physical emulator lot action.
Definition pineforge.h:679
Single per-bar trace entry.
Definition pineforge.h:346
double value
Traced expression value on this bar.
Definition pineforge.h:350
int64_t timestamp
Bar timestamp (Unix ms).
Definition pineforge.h:347
int32_t name_id
Index into pf_report_t::trace_names.
Definition pineforge.h:349
int32_t bar_index
Zero-based bar index.
Definition pineforge.h:348
Trade-level statistics block — computed once each for all / long / short.
Definition pineforge.h:196
double avg_win_pct
Mean of per-trade pnl_pct over winning trades.
Definition pineforge.h:219
double avg_win
gross_profit / num_wins.
Definition pineforge.h:218
double net_profit_pct
net_profit as a percent of initial capital (0-100 scale).
Definition pineforge.h:206
double largest_loss_pct
Maximum of -pnl_pct over losing trades (positive magnitude) — an INDEPENDENT maximum,...
Definition pineforge.h:236
int32_t num_losses
Trades with pnl < 0.
Definition pineforge.h:199
double gross_profit
Sum of winning pnl.
Definition pineforge.h:208
int32_t max_consecutive_losses
Longest losing run; even trades reset both streaks.
Definition pineforge.h:245
double avg_loss_pct
Mean of the NEGATED pnl_pct of the losing trades.
Definition pineforge.h:223
double commission_paid
Sum of pf_trade_t::commission in the block.
Definition pineforge.h:241
double percent_profitable
100 * num_wins / num_trades, in PERCENT (0-100).
Definition pineforge.h:203
double largest_win_pct
Maximum pnl_pct over winning trades — an INDEPENDENT maximum, not the pct of the largest-USD win (TV ...
Definition pineforge.h:230
double gross_loss
Sum of |losing pnl| — POSITIVE magnitude (TV display convention).
Definition pineforge.h:211
double gross_profit_pct
gross_profit as a percent of initial capital (0-100 scale).
Definition pineforge.h:209
double largest_loss
Single largest |pnl| among losing trades (positive magnitude).
Definition pineforge.h:234
double avg_bars_in_wins
Mean bar duration of winning trades, inclusive of the entry bar (TV convention, validated 2026-06-12)...
Definition pineforge.h:250
double avg_bars_in_losses
Mean bar duration of losing trades, inclusive of the entry bar (TV convention, validated 2026-06-12).
Definition pineforge.h:253
double profit_factor
gross_profit / gross_loss.
Definition pineforge.h:214
double avg_loss
gross_loss / num_losses (positive magnitude).
Definition pineforge.h:221
double largest_win
Single largest pnl among winning trades.
Definition pineforge.h:228
double avg_bars_in_trade
Mean of (exit_bar_index - entry_bar_index + 1) in SCRIPT bars, over all trades — inclusive of the ent...
Definition pineforge.h:246
double gross_loss_pct
gross_loss as a percent of initial capital (0-100 scale).
Definition pineforge.h:212
int32_t max_consecutive_wins
Longest winning run; even trades reset both streaks.
Definition pineforge.h:244
double expectancy
(num_wins/num_trades)*avg_win - (num_losses/num_trades)*avg_loss, account currency per trade.
Definition pineforge.h:242
double net_profit
Sum of pnl (account currency, net of commission).
Definition pineforge.h:205
int32_t num_trades
Closed trades in this block (all / long-only / short-only).
Definition pineforge.h:197
double avg_trade_pct
Mean of per-trade pnl_pct over all trades.
Definition pineforge.h:216
double ratio_avg_win_avg_loss
avg_win / avg_loss.
Definition pineforge.h:227
double avg_trade
net_profit / num_trades.
Definition pineforge.h:215
int32_t num_even
Trades with pnl == 0.0 exactly; breaks both win and loss streaks; excluded from win/loss averages.
Definition pineforge.h:200
int32_t num_wins
Trades with pnl > 0.
Definition pineforge.h:198
Closed-trade record returned in pf_report_t::trades.
Definition pineforge.h:153
int32_t exit_bar_index
Script-bar index of the exit fill (0-based).
Definition pineforge.h:174
double pnl_pct
Net return-on-cost in percent: pnl (NET of commission) / entry cost (entry_price * qty * pointvalue) ...
Definition pineforge.h:159
double exit_price
Exit fill price (incl.
Definition pineforge.h:157
int32_t open_at_end
1 when this row is the RANGE-END close of a position that was still open after the final bar; 0 for a...
Definition pineforge.h:175
int32_t entry_bar_index
Script-bar index of the entry fill (0-based).
Definition pineforge.h:173
double commission
Entry+exit commission actually deducted from pnl (account currency).
Definition pineforge.h:171
double pnl
Net realized PnL in account currency (commission-inclusive).
Definition pineforge.h:158
int is_long
1 if long, 0 if short.
Definition pineforge.h:167
double max_drawdown
Peak adverse price travel during the trade ($/unit qty).
Definition pineforge.h:169
double qty
Filled quantity.
Definition pineforge.h:170
double max_runup
Peak favorable price travel during the trade ($/unit qty).
Definition pineforge.h:168
int64_t entry_time
Entry fill time (Unix ms).
Definition pineforge.h:154
double entry_price
Entry fill price (incl.
Definition pineforge.h:156
int64_t exit_time
Exit fill time (Unix ms).
Definition pineforge.h:155
One provider-neutral realtime executed-trade update.
Definition pineforge.h:143
double quantity
Traded quantity in symbol volume units (>= 0).
Definition pineforge.h:147
uint64_t sequence
Normalized per-stream sequence, or 0.
Definition pineforge.h:145
int64_t timestamp
Source event time, Unix milliseconds.
Definition pineforge.h:144
double price
Executed trade price (> 0).
Definition pineforge.h:146
Runtime version descriptor returned by pf_version_get.
Definition pineforge.h:1314
int patch
Patch version.
Definition pineforge.h:1317
int minor
Minor version.
Definition pineforge.h:1316
int major
Major version.
Definition pineforge.h:1315
const char * commit_sha
Short git commit SHA, or "" if unknown.
Definition pineforge.h:1318