PineForge unreleased
Deterministic PineScript v6 backtest runtime — C ABI reference
Loading...
Searching...
No Matches
pine_strategy_host.hpp
Go to the documentation of this file.
1#pragma once
2
9
10#include <cstdint>
11#include <limits>
12#include <map>
13#include <optional>
14#include <string>
15#include <vector>
16
17// Generated strategies require this switched source-host surface. There is
18// intentionally no compatibility execution path behind the capability gate.
19#define PINEFORGE_HAS_EXPLICIT_PINE_CAP_V1 1
20#define PINEFORGE_HAS_EXPLICIT_PINE_EXECUTION_ADAPTER_V1 1
21#define PINEFORGE_HAS_NATIVE_LOWERING_V1 1
22// The run-time symbol key overload of register_security_eval (lane XSYM-D):
23// a request site of another symbol reads the feed installed for that symbol.
24#define PINEFORGE_HAS_SYMBOL_SECURITY_EVAL_V1 1
25#define PINEFORGE_HAS_TRADE_SCRATCH_V1 1
26
27namespace pineforge::source {
28
29// Hash domain of the per-site request.security semantics below. Folded into
30// the source extension only when a site is registered, so a run without
31// request.security hashes exactly as before; bumped whenever the folded
32// field set changes.
33inline constexpr char kSourceSecurityDomain[] = "pineforge-source-security/v6";
34
35// Hash domain of the request data of other symbols (lane XSYM-D): the sites
36// that read another symbol's feed, and the facts and recorded series the run
37// was handed. Folded only when one is present, so every other run hashes as
38// before.
39inline constexpr char kSourceSymbolDataDomain[] = "pineforge-source-symbol-data/v1";
40
41// One symbol's facts as a request site's expression reads them through
42// syminfo.* (strategy_set_symbol_facts). Empty strings and a NaN mintick are
43// facts nobody supplied.
45 std::string canonical;
46 std::optional<bool> valid;
47 std::string type;
48 std::string timezone;
49 std::string session;
50 std::string currency;
51 double mintick = std::numeric_limits<double>::quiet_NaN();
52};
53
54// A recorded request series (strategy_set_recorded_series): exact values in
55// increasing chart-open order, read by logical bar index.
57 std::int64_t open_ms = 0;
58 double value = 0.0;
59};
60
63 // Physical setup only: an omitted capacity follows the admitted run policy.
64 // Explicit zero and an explicit internal-default value are both overrides.
65 std::optional<std::size_t> cache_capacity_override;
66};
67
68// One projected higher-timeframe bucket of the opt-in historical
69// request.security lookahead projection (PineSecurityEvalState below).
72 // The instant the projection is dispatched on: the first retained
73 // chart child's timestamp on the single-feed path, that child's first
74 // auxiliary bar on the split-feed path (the requested-context
75 // evaluator is fed the finer slice there). Keyed by instant, not by
76 // feed-call index, so both paths consume one projection per bucket.
77 int64_t first_child_ms = 0;
78 bool is_complete = false;
79};
80
81// One auxiliary bar a first-bucket-latched site holds back until the chart
82// body has run (PineSecurityEvalState::deferred_aux), with the cursor values
83// it would have been fed under.
86 int64_t next_input_ms = 0;
88};
89
90// Pine's publication semantics for ONE request.security site, kept beside the
91// kernel's generic evaluator state of the same sec_id
92// (BacktestEngine::SecurityEvalState: aggregator, current bar, feed counts,
93// native feed routing). The kernel evaluator knows none of these rules; this
94// host composes them around the kernel's aggregation, substitution and
95// dispatch primitives (src/source/pine_security_eval.cpp).
97 // barmerge.lookahead_on: the site reads the in-progress requested bucket.
98 // Every input peeks the running aggregate (a partial evaluation), a
99 // completion rewrites the slot its first peek opened instead of opening a
100 // second one, and generated TA sites take their recompute path on every
101 // sub-bar after a bucket's first (security_series_slot_is_new).
102 bool lookahead_on = false;
103 // barmerge.gaps_on: the site reads na on every input that completes no
104 // requested bucket (clear_security on the generated series).
105 bool gaps_on = false;
106 // Plain ``request.security`` (not ``_lower_tf``) with a requested TF
107 // STRICTLY FINER than script_tf (e.g. so2TF="5" read from a 15m
108 // chart) under ``lookahead=barmerge.lookahead_ON``: the security's
109 // own aggregator completes multiple times
110 // (script_seconds / requested_seconds) per calling/script bar. A
111 // history-offset read (``expr[1]`` inside the security call, see
112 // the ``*_hist`` push/read machinery in codegen) is meant to expose
113 // "the value already confirmed as of the close of the PREVIOUS
114 // calling bar" — TV's lookahead_on merge takes the FIRST intrabar
115 // of each calling bar, so the publish granularity is the CALLING
116 // bar, not the security's own (finer) period. Without this, the
117 // read-before-push ``hist[0]`` gets refreshed on every one of the
118 // R completions inside the current calling bar, so by the time
119 // on_bar() reads it the value has silently drifted to "one
120 // security-period behind the LAST completion of THIS SAME calling
121 // bar" (e.g. the middle of 3 sub-periods) instead of "the last
122 // completion of the PREVIOUS calling bar" — an aliasing bug
123 // confirmed against TradingView-exported trades on a triple-RSI
124 // DCA strategy using so2Rsi = request.security(sym, "5",
125 // ta.rsi(close,7)[1], lookahead=barmerge.lookahead_on) on a 15m
126 // chart (finer target under lookahead + offset).
127 //
128 // ``lookahead_OFF`` is deliberately NOT gated (field stays 0): TV's
129 // lookahead_off merge takes the LAST intrabar of the calling bar,
130 // so the exposed value — and any ``[k]`` history offset off it —
131 // advances at the security's own finer cadence (one hist.push per
132 // completed security period), which is exactly the ungated
133 // behavior. Gating lookahead_off regressed
134 // masayanfx-multi-time-score-strategy
135 // (request.security(sym, "5", ta.highest(high, 20)[1],
136 // barmerge.gaps_off, barmerge.lookahead_off) on a 15m chart) from
137 // 100.0% to 93.7% trade parity vs TradingView.
138 //
139 // When nonzero, this holds the requested TF's duration in seconds
140 // (script_seconds % this == 0 verified at validate time) and gates
141 // ``pine_feed_security_eval_state``'s aggregator branch: only the
142 // completion whose bucket END aligns to a script_tf boundary is
143 // passed through to ``evaluate_security`` as ``is_complete = true``
144 // (letting codegen's ``hist.push()`` fire); all other completions
145 // within the same calling bar are still evaluated (so the
146 // underlying TA state keeps advancing at native/security
147 // resolution) but are passed ``is_complete = false`` so they do not
148 // advance the exposed history buffer. Zero (the default) means "not
149 // applicable" (target TF coarser than or equal to script_tf, or
150 // lookahead_off — the already-correct cases) and leaves behavior
151 // unchanged.
153 // Plain ``request.security`` with a requested TF strictly finer than
154 // script_tf, served by the auxiliary finer feed (the split-feed
155 // path), under ``lookahead_off``: TradingView surfaces the LAST
156 // intrabar of the calling chart bar at that bar's close whatever
157 // the bucket's sub-bar count -- on the OANDA:XAUUSD 1D chart the
158 // Thanksgiving 2025-11-26 bar's last 3m bucket (21:57Z, holding
159 // the 21:59Z minute alone before the 22:00Z session close) is the
160 // value ``request.security(tickerid, "3", ta.rsi(close, 14))``
161 // reads at the daily close (72.64, lab tv dca-ltf-last-intrabar,
162 // 2026-09-05), where the aggregator's count / real-end /
163 // session-close rules leave that bucket partial until the next
164 // chart bar's first sub-bar and the close read the 21:54Z bucket
165 // (38.87). When set, pine_feed_security_eval_state
166 // finalizes and publishes the pending partial bucket on the
167 // calling bar's last auxiliary bar (TimeframeAggregator::
168 // complete_pending_partial), once: the next chart bar's first
169 // sub-bar resets the bucket without re-emitting it. Dense feeds
170 // whose final bucket completes on its count are untouched (no
171 // partial is pending), and so are lanes without the auxiliary
172 // slice, lookahead_on (its gated publication is untouched: on this
173 // shape it stays one bucket behind, as before -- the tape pins
174 // lookahead_off only), lower-TF arrays and calendar / same-TF
175 // requests. False (the default) means "not applicable".
177 // Plain ``request.security`` with a requested TF strictly finer than the
178 // finest feed the host supplied (the auxiliary slice, or an input feed
179 // already finer than the chart), e.g. "5S" or "15S" on a lane whose
180 // finest bars are 1m: no bar of the requested timeframe is loaded, so
181 // the site is never fed and reads na on every chart bar. That is
182 // TradingView's own reading of a chart bar that holds no loaded bar of
183 // the requested lower timeframe: it keeps the last 2,000,000 bars of
184 // it, and on the BINANCE:ETHUSDT.P 15m and 1D charts over 2025-04-01..
185 // 2026-05-01 ``request.security(tickerid, "5S", close)`` is na up to
186 // 2026-01-04 22:45 UTC and ``"15S"`` up to 2025-05-18 03:15 UTC (lab tv
187 // pf-krunerr-ltf-horizon / -ltf-count2, 2026-09-26), gaps_on reads na on
188 // the chart bars holding no 5S bar. A chart fed without any intrabar
189 // bars (input_tf == script_tf) still refuses a finer request, so its
190 // host can supply finer bars. False (the default) means "loaded".
191 bool no_loaded_bars = false;
192 // Heikin-Ashi same-symbol read: request.security(ticker.heikinashi(
193 // syminfo.tickerid), ...). When set, the completed (aggregated) bar's
194 // OHLC is replaced by its Heikin-Ashi candle before the security
195 // expression is evaluated, so close/open/high/low inside the call see
196 // HA values. HA is stateful (ha_open depends on the prior HA bar), so
197 // the running state lives here per sec_id.
198 bool heikinashi = false;
199 double ha_prev_open = 0.0;
200 double ha_prev_close = 0.0;
201 bool ha_seeded = false;
202 // One entry per projected HTF bucket, populated only for an explicitly
203 // opted-in finite historical batch. Empty for every default/streaming
204 // run and for sites outside the narrow HTF lookahead_on+gaps_off
205 // contract. The feed index advances once per retained input bar (bars
206 // before an opt-in security range start are dropped by both producer
207 // and consumer); the projection cursor advances only at the next
208 // bucket's first child.
209 std::vector<HistoricalSecurityProjection> historical_projections;
211 // Which projection (cursor) has already been dispatched: every later
212 // input of the same bucket is a no-op for the evaluator.
214 // request.security_lower_tf emulation: a requested TF finer than the
215 // evaluator input is synthesized from each input bar's OHLC path
216 // (``lower_tf_emulation``: ``lower_tf_ratio`` sub-bars of
217 // ``lower_tf_seconds``); ``lower_tf_requested`` marks a site on either
218 // lower-timeframe path.
219 bool lower_tf_requested = false;
220 bool lower_tf_emulation = false;
223 // ``request.security_lower_tf`` returns one element per
224 // synthesised sub-bar of the current chart bar, so the codegen
225 // needs to know which sub-bar inside the current chart bar is
226 // currently being processed by the per-sec_id evaluator method.
227 // ``lower_tf_array_requested`` is set by
228 // ``register_security_lower_tf_eval`` and forces an extra
229 // lower-TF-emulation validity check in
230 // ``validate_security_timeframes``. ``lower_tf_sub_bar_index``
231 // is reset to 0 at the start of every
232 // ``pine_feed_security_eval_state`` invocation in lower-TF
233 // emulation mode and incremented after each per-sub-bar
234 // dispatch so the codegen can clear its accumulator on index
235 // 0 and then push for every subsequent sub-bar.
238 // ``lower_tf_use_input`` selects the input-passthrough LTF path:
239 // when the requested TF is >= input_tf and < script_tf we hand
240 // the per-script-bar window of real input bars to the codegen
241 // (optionally roll-up aggregated when req > input). Mutually
242 // exclusive with ``lower_tf_emulation`` (synthesis) — only one
243 // is set per state. ``lower_tf_input_aggregation_ratio`` is
244 // ``req_seconds / input_seconds`` (>=1; 1 means raw passthrough,
245 // N means N raw input bars roll up into one returned LTF bar).
246 // ``lower_tf_input_buffer`` accumulates raw input bars within
247 // the current script-TF chunk and is flushed at chunk
248 // completion (or at end of feed for trailing partial chunks).
249 bool lower_tf_use_input = false;
251 std::vector<Bar> lower_tf_input_buffer;
252 // Plain ``request.security`` with a requested TF strictly finer than
253 // script_tf, served by the auxiliary finer feed (the split-feed
254 // path), under ``lookahead_on``: TradingView merges the requested bar
255 // opening at or before the calling bar's time and holds it -- on
256 // the BINANCE:BTCUSDT 1D chart ``request.security(tickerid, "15",
257 // ta.rsi(close, 14)[1], lookahead_on)`` reads, on every daily bar,
258 // the 15m RSI of the previous day's LAST bucket, i.e. ``rsi[1]``
259 // evaluated on the day's first 15m bucket, na on the range's first
260 // bar (lab tv notrade-ltf-sample-btc1d, 2025-04-01..20, 18/18,
261 // 2026-09-05), so a plain ``expr`` reads the day's first bucket and
262 // ``expr[k]`` the k-th bucket before it, at the requested cadence.
263 // The legacy gate above (publish_gate_tf_seconds) publishes one
264 // bucket per calling bar -- the LAST one -- which reads right for
265 // ``expr[1]`` alone and one bucket late for ``expr``. When set, the
266 // evaluator publishes EVERY completed requested bucket (the exposed
267 // history advances per bucket, as under lookahead_off), and
268 // feed_aux_security_for_chart_bar feeds the calling bar's auxiliary
269 // bars only up to the one completing its FIRST bucket before the
270 // chart body runs; the rest of the slice is held in ``deferred_aux``
271 // and fed by feed_deferred_aux_security_for_chart_bar right after the
272 // chart body (PineScheduler::bar calls scheduler_feed_deferred_aux_security
273 // once the bar is published), so the body reads the first-bucket
274 // evaluation while the TA state still sees every sub-bar, in order,
275 // before the next chart bar. publish_gate_tf_seconds stays 0 on this
276 // path; lanes without the auxiliary slice keep the gate. False (the
277 // default) means "not applicable".
279 // Per calling chart bar: whether this state's first bucket of the
280 // slice has been published (the deferral point), and the auxiliary
281 // bars held back until after the chart body, each with the
282 // security_next_input_ms_ / calling_bar_complete it was fed with.
284 // The label (bucket open) of the slice's first requested bucket:
285 // a completion published by this slice's first auxiliary bars that
286 // carries an OLDER label is the boundary emission of the previous
287 // slice's still-pending bucket (a tail the count / real-end /
288 // session-close rules left partial), not this bar's first bucket.
289 int64_t slice_open_label = 0;
290 // The label of the latest completed bucket this evaluator published
291 // through its aggregator (pine_feed_security_eval_state), whatever the
292 // state's current bucket is afterwards.
294 std::vector<DeferredAuxBar> deferred_aux;
295};
296
298public:
299 explicit PineStrategyHost(
301
302 std::uint64_t broker_state_hash_projection() const override;
303
304 void prepare_native_begin(const NativeBeginArgs&) final;
305 void on_native_run_begin() final;
306 void on_native_input(const Bar&, const NativeInputContext&) final;
307 void on_native_tick(const Bar&, const NativeTickContext&) final;
308 void on_native_bar_open(const Bar&, const NativeDecisionContext&) final;
309 void on_native_bar(const Bar&, const NativeDecisionContext&) final;
310 // A magnified run's intrabar is done: its open-point fills are spent
311 // (PineScheduler::sub_bar_complete).
312 void on_native_sub_bar(const Bar&, const NativeDecisionContext&) final;
313 // R6: every calculation of the run arrives here. BarClose forwards to
314 // on_native_bar exactly as the kernel's default does; OrderFill is
315 // calc_on_order_fills, which the consumer now schedules (spec
316 // NativeCalculationTrigger::BarCloseAndFills) and the source layer only
317 // executes — language-state rollback, publication, first-open chain.
322 const NativeDecisionContext&) final;
323 // Lane XSYM-D: a bar of another symbol's feed, handed to a foreign site's
324 // series; the site's payload runs on it in the requested context. A
325 // same-symbol series the kernel steps needs nothing here.
326 void on_native_timeframe_bar(const Bar&, const NativeTimeframeBarContext&) final;
328 const NativeExecutionTermsFacts&) const final;
330 const NativePrecommitView&) const final;
331 // R5: the kernel owns the margin mechanism; these three answer with the
332 // TradingView policy the adapter holds.
333 bool margin_check_allowed(const NativeMarginCheckPoint&) const final;
334 std::optional<NativeMarginDecision> resolve_margin_requirement(
335 const NativeMarginRequirementView&) const final;
336 std::optional<double> resolve_margin_call_units(
337 const NativeMarginCallView&) const final;
338 // R5 lane R4d: the kernel arms a relative strategy.exit leg at its
339 // parent's fill; TradingView's projection of that level is the adapter's.
340 std::optional<double> resolve_anchored_level(
341 const NativeAnchoredLevelView&) const final;
342 // The R5 R2 sizing classification of the adapter, for tests and for hosts
343 // that need to know whether the core or the source owns a default
344 // quantity at this point.
345 bool adapter_core_sizes_default_opening(bool is_long) const;
346
347 virtual void on_source_bar(const Bar&) = 0;
349 // Run-owner provisioned, exclusive scratch; use trade_scratch_order().
350 // Borrowed through the run, empty outside a recut or same-bar sort.
354 // A stream's historical warmup remains a configuration window until the
355 // first realtime input. The legacy stream path permits the feed to
356 // finalize its session template in that window (the sparse-boundary
357 // request.security probe relies on it); retain that source-host surface
358 // while native staged ingress remains refused once realtime starts.
359 void set_syminfo_session(const std::string&);
360 void set_pine_risk_direction(int);
362 void set_pine_risk_max_drawdown(double, bool);
363 void set_pine_risk_max_intraday_loss(double, bool);
366
367 void strategy_entry(const std::string& id, bool is_long,
368 double limit_price = std::numeric_limits<double>::quiet_NaN(),
369 double stop_price = std::numeric_limits<double>::quiet_NaN(),
370 double qty = std::numeric_limits<double>::quiet_NaN(),
371 const std::string& comment = {},
372 const std::string& oca_name = {}, int oca_type = 0,
373 int qty_type = -1);
374 void strategy_close(const std::string& id, const std::string& comment = {},
375 double qty = std::numeric_limits<double>::quiet_NaN(),
376 double qty_percent = std::numeric_limits<double>::quiet_NaN(),
377 bool immediately = false);
378 void strategy_close(const std::string& id, const std::string& comment,
379 double qty, double qty_percent, bool immediately,
380 std::uint64_t callsite_token);
381 void strategy_close_all();
382 void strategy_exit(const std::string& id, const std::string& from_entry,
383 double limit_price, double stop_price,
384 double trail_points = std::numeric_limits<double>::quiet_NaN(),
385 double trail_offset = std::numeric_limits<double>::quiet_NaN(),
386 double trail_price = std::numeric_limits<double>::quiet_NaN(),
387 double qty_percent = 100.0, const std::string& comment = {},
388 double qty = std::numeric_limits<double>::quiet_NaN(),
389 const std::string& oca_name = {},
390 double profit_ticks = std::numeric_limits<double>::quiet_NaN(),
391 double loss_ticks = std::numeric_limits<double>::quiet_NaN());
392 void strategy_exit_cancel_bracket(const std::string& exit_id,
393 const std::string& from_entry,
394 const std::string& comment = {});
395 void strategy_cancel(const std::string& id);
396 void strategy_cancel_all();
397 void strategy_order(const std::string& id, bool is_long, double qty,
398 double limit_price = std::numeric_limits<double>::quiet_NaN(),
399 double stop_price = std::numeric_limits<double>::quiet_NaN(),
400 const std::string& oca_name = {}, int oca_type = 0);
401
402 int pine_bar_index() const;
403 int pine_last_bar_index() const;
404 double prev_chart_close() const;
405 bool is_first_tick() const noexcept;
406 bool is_last_tick() const noexcept;
407 bool history_advances_new_bar() const noexcept;
408 bool security_series_slot_is_new(int) const noexcept;
409 int last_bar_dual_entry_path() const;
410 double live_position_size() const override;
411 int pending_order_count() const;
414 std::vector<admission::Field> market_admission_fields() const;
415 int probe_fill_qty(int index, double fill_price, double* qty,
416 int* close_only, int* partition) const;
417 int pending_order_level_resolved(int index) const;
418 int pending_order_effective_levels(int index, double* stop, double* limit,
419 double* trail_activation) const;
420 const PendingIntentView& pending_intent_view() const noexcept;
421 int short_seed_collision_role_v1(native_order::RequestHandle) const noexcept;
424 // The report-only terminal quote (docs/report-terminal-quote.md) has three
425 // writers, and none stores once a selected-window run has sealed
426 // (selected_window.h; the fence of query_refusal.hpp): set_syminfo_metadata,
427 // for the two reserved keys report_terminal_quote_time_ms and
428 // report_terminal_quote_close, and clear_report_terminal_quote throw
429 // pineforge::SelectedWindowQueryAfterSeal before any store, and
430 // set_report_terminal_quote returns false. The C export
431 // strategy_set_syminfo_metadata is void and swallows the throw, so its
432 // refusal is read through strategy_state_query_status_v1. The refusal latches
433 // no run failure and leaves the error, the status, the first cause and the
434 // captured report alone. A preset before the first admitted begin, and every
435 // handle without selected-window provenance, keep their original behaviour.
436 void set_syminfo_metadata(const std::string&, double) override;
437 bool set_report_terminal_quote(std::int64_t time_ms, double close);
439 bool report_terminal_quote_applied(const ReportC&) const;
440 // TradingView's session calendar for the chart's symbol (lane XAU-CAL):
441 // per session day, its open and its close, ascending, each day opening
442 // before it closes and no later than the next opens. Where it holds a
443 // chart bar, the bar after it is the calendar's -- the next slot of an
444 // intraday chart's timeframe in the day, else the next day's open; a 1D
445 // chart's next day -- for session.islastbar / islastbar_regular and for
446 // time() / time_close() reading a later bar, as TradingView reads them;
447 // everywhere else (a W, M or multi-day chart's own bars among them), and
448 // with no calendar (the default), the feed's own bars answer. False, and
449 // nothing installed, for sessions out of order; an empty list removes the
450 // calendar. The C ABI carries it as syminfo metadata ("symbol_calendar_*",
451 // set_syminfo_metadata), installed when the run begins.
452 bool set_symbol_calendar(std::vector<std::pair<std::int64_t, std::int64_t>> sessions);
453 const std::vector<std::pair<std::int64_t, std::int64_t>>& symbol_calendar() const noexcept {
454 return symbol_calendar_;
455 }
456 bool set_aux_security_feed(const Bar* bars, int n,
457 const std::string& input_tf) override;
458 // The request-data doors (pineforge.h strategy_set_symbol_feed and its
459 // siblings); see BacktestEngine for the contract (lane XSYM-D).
460 bool set_symbol_feed(const std::string& key, const std::string& timeframe,
461 const Bar* bars, const int64_t* close_ms, int n) override; bool set_symbol_feed_borrowed(const std::string& key, const std::string& timeframe, const Bar* bars, const int64_t* close_ms, int n) override;
462 bool set_symbol_feed_column(const std::string& key, const std::string& timeframe,
463 const std::string& name, const double* values,
464 int n) override;
465 bool set_symbol_facts(const std::string& key, const std::string& field,
466 const std::string& value) override;
467 bool set_recorded_series(const std::string& key, const int64_t* chart_open_ms,
468 const double* values, int n) override;
469 // The raw-array setter still copies before returning. This overload
470 // selects optional resident bytes for that tape, including page tags,
471 // in preference to the run policy. The four-argument overload follows
472 // the run policy.
473 bool set_recorded_series(const std::string& key, const int64_t* chart_open_ms,
474 const double* values, int n, std::size_t cache_capacity_bytes);
475 // The provider is a retained immutable snapshot with copy-out random
476 // reads. A miss reads the provider even with zero resident bytes.
477 // Omitting capacity follows the run policy; an explicit value overrides it.
479 const std::string& key, std::shared_ptr<const FeedReader<RecordedSeriesRow>> reader);
481 const std::string& key, std::shared_ptr<const FeedReader<RecordedSeriesRow>> reader,
482 std::size_t cache_capacity_bytes);
483#ifdef PINEFORGE_HAS_AUX_SECURITY_FEED_V1
484 bool source_aux_security_feed_enabled() const override;
485 void source_aux_security_input_view(const Bar*&, int&) const override;
486#endif
487 int observe_last_bar_dual_entry_path_v1() const override;
488 int observe_pending_count_v1() const override;
489 int observe_pending_copy_v1(int, pf_pending_order_v1_t*) const override;
490 int observe_probe_fill_qty(int, double, double*, int*, int*) const override;
491 int observe_pending_level_resolved(int) const override;
492 int observe_pending_effective_levels(int, double*, double*, double*) const override;
493 double observe_trail_best_price_v1() const override;
494
495 // Fixture-only adapter projections retained for the L2 literal twins.
496 // They are read-only and never participate in execution or matching.
499 std::string id;
500 FixtureIntentKind type = FixtureIntentKind::MARKET;
501 double default_stop_placement_qty = std::numeric_limits<double>::quiet_NaN();
502 double default_stop_sizing_price = std::numeric_limits<double>::quiet_NaN();
503 double frozen_market_own_units = std::numeric_limits<double>::quiet_NaN();
504 double frozen_market_transaction_units = std::numeric_limits<double>::quiet_NaN();
505 // These are immutable adapter placement facts, exposed only to test
506 // facades which formerly read the retired source PendingOrder owner.
507 std::string from_entry;
508 bool is_long = true;
509 double qty = std::numeric_limits<double>::quiet_NaN();
510 double qty_percent = std::numeric_limits<double>::quiet_NaN();
511 std::int64_t created_bar = -1;
512 std::int64_t created_seq = 0;
513 std::uint64_t incarnation = 0;
516 double paired_flat_market_transaction_qty = std::numeric_limits<double>::quiet_NaN();
517 double frozen_default_qty = std::numeric_limits<double>::quiet_NaN();
518 double default_stop_placement_equity = std::numeric_limits<double>::quiet_NaN();
519 double default_stop_placement_signal_close = std::numeric_limits<double>::quiet_NaN();
520 double affordability_placement_equity = std::numeric_limits<double>::quiet_NaN();
521 // The adapter currently has no pair-review receipt for this request;
522 // an empty Draft truthfully represents that absence to a fixture.
524 };
525
526public:
527 // Toggle TradingView's forced-liquidation (margin call) emulation.
528 // Defaults ON to match TV; set false for the hold-the-position behaviour.
529 // TV runs the broker margin-call emulator by default. It is a no-op for
530 // the validation corpus (long-only positions at the default 100% margin
531 // can never be liquidated — the formula denominator ``margin/100 -
532 // direction`` is 0 — and no corpus short is sized at full equity).
533 //
534 // R5 lane L12 (2.ii k): this used to be BacktestEngine state. The kernel
535 // toggle is the presence of `spec.margin` since L4/L4b, so the TV emulator
536 // switch belongs to the adapter that implements that emulator. The value
537 // reaches `PineExecutionAdapter::source_margin_call_enabled_` at the
538 // native begin boundary, exactly as before.
539 void set_margin_call_enabled(bool enabled) {
540 guard_native_mutation("set_margin_call_enabled");
541 margin_call_enabled_ = enabled;
542 }
544
545 // Live-runtime tail semantics (spec §3.1, ABI v4): the caller's fed array
546 // ends with a still-forming bar rather than the chart's rightmost
547 // historical bar. When `on`, the LAST bar of every subsequent run() (this
548 // is persistent configuration, not a one-shot flag -- it stays set until
549 // a caller passes on=false, and reset_run_state() does not touch it)
550 // gets barstate.islast == false, session.islastbar computed from the
551 // bucket calendar (no i+1 bar to peek at), pine_last_bar_index() /
552 // last_bar_time_ frozen at the horizon bar (`horizon_bars - 1`), and no
553 // range-end close row/trade. Default off: every historical run is
554 // byte-identical to before this flag existed.
555 //
556 // R5 lane N14: this and probe tail suppression used to be BacktestEngine
557 // state. Both are the live runner's probe / settle protocol expressed in
558 // Pine language state (barstate, session flags, the range-end row), so
559 // they belong to the host that implements that protocol; the frozen C
560 // setters reach them through the kernel's virtual seams.
561 bool set_realtime_tail(bool on, int horizon_bars) override {
562 guard_native_mutation("set_realtime_tail");
563 realtime_tail_ = on;
564 realtime_tail_horizon_bars_ = horizon_bars;
565 return true;
566 }
567 bool realtime_tail() const { return realtime_tail_; }
568
569 // Live probe tail suppression (spec §3.2, ABI v4): when `on`, the LAST
570 // script bar of every subsequent run() is matched but not calculated:
571 // the kernel settles the resting book against it, and PineScheduler::bar
572 // publishes it through scheduler_publish_suppressed_tail instead of
573 // on_source_bar (source history and bar_index advance, the already-
574 // matched receipts settle, the report point is marked; no range-end
575 // row). on_native_bar still runs adapter_.on_bar_close after the
576 // scheduler, so the risk-state update and the TradingView margin-call
577 // checkpoint see the forming bar, and a margin call due against it is
578 // booked there. This is persistent configuration, like
579 // set_realtime_tail, and independent of it — do not couple the two
580 // flags. Honoured on every batch path (single-TF, timeframe-aware,
581 // magnifier; under aggregation, the last completed script bar). Under
582 // calc_on_order_fills a fill on the forming bar still triggers the fill
583 // recalculation, which runs the script there; COOF and the magnifier
584 // stay gated in live v1. A stream begin refuses the flag
585 // (prepare_native_begin); see pineforge.h. Default off (@p on == 0):
586 // every historical run stays byte-identical to before this flag existed.
587 bool set_probe_suppress_tail_logic(bool on) override {
588 guard_native_mutation("set_probe_suppress_tail_logic");
590 return true;
591 }
593
594 // Keep every event of the following runs readable after they end
595 // (NativeEventRetention::Full as a readback, R5 lane V19-B). The adapter
596 // declares Window and acknowledges what it reads, so a fixture that reads
597 // a whole run's native_events() asks for this before the run. The spec,
598 // its digest and every decision stay the adapter's.
600
601protected:
602 // The observer seam: on_native_applied is final, so a host that has to
603 // observe an applied event after the host itself has settled its rows
604 // overrides THIS instead. Called exactly once per applied event, at the
605 // end of on_native_applied and after finish_applied_admission -- never
606 // per bar. Empty by default, so a host that does not override it behaves
607 // byte for byte as before and folds the same hash. It observes; it does
608 // not admit, price or seal anything.
611 // Narrow test-facade configuration slots keep the frozen L0 oracle bodies
612 // unchanged while routing their setup through the source configuration
613 // projected at the native begin boundary.
615 const PineStrategyConfig& fixture_configuration() const noexcept { return config_; }
616#ifdef PINEFORGE_VALUE_INVALID_SIZING_MARGIN_FIXTURES
617 // Stateless test injection at the derived sizing tuple, inside a real
618 // source callback. Absent from normal builds; no layout or kernel seam.
619 double fixture_default_sizing_units(const PineSizingSnapshot& sizing) const {
620 return adapter_.default_sizing_units(sizing);
621 }
622#endif
626 BarTime fixture_chart_time(std::int64_t timestamp_ms) const;
627 std::int64_t fixture_chart_day_key(std::int64_t timestamp_ms) const noexcept {
628 return adapter_.chart_day_key(timestamp_ms);
629 }
630 std::uint64_t fixture_applied_receipt_count() const;
631#ifdef PINEFORGE_VALUE_INVALID_FIXTURES
632 void fixture_value_invalid_drawdown_limit(double value, bool percent) noexcept {
633 adapter_.fixture_value_invalid_drawdown_limit(value, percent);
634 }
635#endif
636 bool fixture_cap_due_pending() const noexcept {
637 return adapter_.cap.due_cause().has_value();
638 }
640 public:
641 explicit FixtureQtyTypeSlot(PineStrategyHost& host) noexcept : host_(host) {}
643 host_.config_.default_qty_type = static_cast<int>(value);
644 return *this;
645 }
646 operator QtyType() const noexcept {
647 return static_cast<QtyType>(host_.config_.default_qty_type);
648 }
649 private:
650 PineStrategyHost& host_;
651 };
656 public:
657 explicit FixtureCommissionTypeSlot(PineStrategyHost& host) noexcept : host_(host) {}
659 host_.config_.commission_type = static_cast<int>(value);
660 return *this;
661 }
662 operator CommissionType() const noexcept {
663 return static_cast<CommissionType>(host_.config_.commission_type);
664 }
665 private:
666 PineStrategyHost& host_;
667 };
672 public:
673 explicit FixtureRiskDirectionSlot(PineStrategyHost& host) noexcept : host_(host) {}
674 FixtureRiskDirectionSlot& operator=(int value) noexcept {
675 host_.adapter_.set_risk_direction(value);
676 return *this;
677 }
678 private:
679 PineStrategyHost& host_;
680 };
684 class SourceIdLedgerView {
685 public:
686 struct value_type { double second = 0.0; };
688 public:
689 const value_type* operator->() const noexcept { return &value_; }
690 bool operator==(const const_iterator& other) const noexcept {
691 return present_ == other.present_;
692 }
693 bool operator!=(const const_iterator& other) const noexcept {
694 return !(*this == other);
695 }
696 private:
697 friend class SourceIdLedgerView;
698 bool present_ = false;
699 value_type value_{};
700 };
701 const_iterator find(const std::string& id) const noexcept {
702 const double units = host_ ? host_->adapter_.source_unclosed_qty_for(id) : 0.0;
703 const_iterator result;
704 result.present_ = units > 0.0;
705 result.value_.second = units;
706 return result;
707 }
708 const_iterator end() const noexcept { return {}; }
709 private:
710 friend class PineStrategyHost;
711 explicit SourceIdLedgerView(const PineStrategyHost* host) noexcept : host_(host) {}
712 const PineStrategyHost* host_ = nullptr;
713 };
715 return SourceIdLedgerView(this);
716 }
717 // strategy.netprofit and, through current_equity(), strategy.equity and
718 // the netprofit/openprofit percentages. TradingView charges an entry's
719 // commission when the entry fills, so the entry fees the open lots have
720 // paid (the share still on each lot) are already out of net profit;
721 // strategy.openprofit (open_profit) stays gross of them
722 // (tests/fixtures/open_entry_fee_charged). The engine's own accessors
723 // keep the closed trades' figures.
724 double net_profit() const { return BacktestEngine::net_profit() - open_entry_fees_paid(); }
725 double current_equity() const {
726 return BacktestEngine::current_equity() - open_entry_fees_paid();
727 }
728 // strategy.openprofit and the open part of strategy.equity, as generated
729 // code reads them (open_profit(current_bar_.close)). TradingView marks the
730 // open position at the tick-built close floor(close / mintick + 0.5) *
731 // mintick, not at an off-grid close: NYSE:F 15m closes on half cents
732 // (10.155 marks 10.15, 10.265 marks 10.27;
733 // tests/fixtures/explicit_qty_floor). An on-grid close is read as it is.
734 // The kernel's own marks (equity extremes, reports, sizing) keep
735 // BacktestEngine::open_profit. The tick is syminfo_mintick_: a host that
736 // sets no mintick marks on the engine's 0.01 default, the tick its fills
737 // already round to, and a close within 1e-9 of a tick (relative to the
738 // tick count) counts as on the grid.
739 double open_profit(double current_price) const {
740 return BacktestEngine::open_profit(script_mark_price(current_price));
741 }
742 // The price open_profit marks the position at: `price` itself, or its
743 // tick when it lies off the mintick grid (ScriptRuleSwitches).
744 double script_mark_price(double price) const;
745 // Initial capital plus the closed trades' net profit: current_equity()
746 // before the open lots' paid entry fees are charged.
747 double closed_trade_equity() const { return BacktestEngine::current_equity(); }
748 double open_entry_fees_paid() const;
749 double signed_position_size() const;
752 // After a fill at the call flattened the book under a frozen position
753 // view (the current-close exit, PoocCloseFillViewSwitches), the generated
754 // strategy.position_avg_price read -- position_entry_price_ behind a
755 // non-zero signed_position_size() -- keeps the pre-fill `average` until
756 // the pass ends; release_script_average_price() then restores the flat
757 // book's 0. Transient: never held across a pass boundary.
758 void hold_script_average_price(double average);
759 void release_script_average_price() noexcept;
760 const Series<double>& source_series(const std::string&) const;
761 const Series<double>& source_input_series(const std::string& key,
762 const Series<double>& fallback) const;
763 // Generated input.source() calls retain this established surface spelling.
764 const Series<double>& get_input_source(const std::string& key,
765 const Series<double>& fallback) const {
766 return source_input_series(key, fallback);
767 }
768 // Generated strategy.margin_liquidation_price reads this: the kernel's
769 // native_liquidation_price() in TradingView's tick spelling.
770 double margin_liquidation_price() const;
771 void fixture_publish_source_series(const Bar& bar, bool new_history_slot) {
772 scheduler_.fixture_publish_source_series(bar, new_history_slot);
773 }
774 int64_t time_close() const {
775 // Inside a foreign site's payload the requested context's bar closes
776 // when its feed says it does: no calendar is guessed for the symbol.
777 if (foreign_context_ != nullptr) return foreign_context_->close_ms;
778 return chart_time_close();
779 }
780 int64_t chart_time_close() const; // the chart bar's own (pine_strategy_host.cpp)
781 // The close of the chart bar stamped `stamp`, as chart_time_close reads it.
782 int64_t chart_bar_close_ms(int64_t stamp) const;
783 // Pine v6 time() / time_close() reading another bar: bars_back and
784 // timeframe_bars_back (pine_strategy_host.cpp). From the chart bar opening
785 // at `bar_open_ms` (the current one), `bars_back` chart bars back -- na
786 // before the first -- or, when negative, forward; then the `tf` bar (on
787 // `session` / `tz`, as time() reads them) holding that chart bar, stepped
788 // `timeframe_bars_back` of its own bars: back when positive, forward when
789 // negative. Returns its open, or its close with `close`.
790 int64_t pine_time_offset(int64_t bar_open_ms, int bars_back, const std::string& tf,
791 const std::string& session, const std::string& tz,
792 int timeframe_bars_back, bool close) const;
793 // The symbol calendar's session day holding `ms` (open <= ms < close), or
794 // null: no calendar, or an instant outside its days.
795 const std::pair<std::int64_t, std::int64_t>* symbol_calendar_day(std::int64_t ms) const;
796 // The first chart bar the symbol calendar opens at or after `at` -- a
797 // slot of the chart's timeframe from its day's open, or the day's open on
798 // a D/W/M chart -- or na outside the calendar's span.
799 std::int64_t symbol_calendar_open_from(std::int64_t at) const;
800 // The last chart bar the symbol calendar opens before `before`, or na
801 // outside the calendar's span.
802 std::int64_t symbol_calendar_open_before(std::int64_t before) const;
803 // ab9714be pine_strategy_host.hpp:348-358: generated three-argument
804 // session predicates are class-scope calls whose chart timeframe changes
805 // the D/W/M meaning. Keep that Pine policy in the source host; the
806 // namespace-level overload remains the raw intraday time-of-day query.
807 bool pine_session_ismarket(const std::string& session,
808 const std::string& timezone,
809 std::int64_t bar_ms) const {
810 return pineforge::pine_session_ismarket(session, timezone, bar_ms, script_tf_);
811 }
812 bool pine_session_ispremarket(const std::string& session,
813 const std::string& timezone,
814 std::int64_t bar_ms) const {
815 return pineforge::pine_session_ispremarket(session, timezone, bar_ms, script_tf_);
816 }
817 bool pine_session_ispostmarket(const std::string& session,
818 const std::string& timezone,
819 std::int64_t bar_ms) const {
820 return pineforge::pine_session_ispostmarket(session, timezone, bar_ms, script_tf_);
821 }
822 const std::vector<FixtureIntentRow>& source_pending_view() const;
823 // request.security registration, as generated configure_security_evaluators()
824 // spells it. The kernel registers the aggregating evaluator; Pine's
825 // publication semantics for the site (barmerge.lookahead, barmerge.gaps,
826 // ticker.heikinashi, the lower-timeframe emulation a finer request
827 // selects) are this host's, recorded for the sec_id it just registered.
828 void register_security_eval(int sec_id, const std::string& requested_tf,
829 const std::string& input_tf, bool lookahead_on,
830 bool gaps_on = false, bool heikinashi = false);
831 // request.security of ANOTHER symbol (lane XSYM-D): `symbol` is the
832 // run-time string the script passes. The site reads the feed installed
833 // for (symbol, requested_tf) through a kernel InstrumentFeed series: its
834 // expression runs on every bar of that feed, in order -- history offsets
835 // and TA state over the context's own bars, bar_index the context's,
836 // time_close the bar's own close, syminfo.* the symbol's facts -- and the
837 // chart bar reads the last bar the kernel's merge rule makes visible
838 // (lookahead_on: open by the chart bar's open; otherwise closed by its
839 // close). gaps_on reads na on a chart bar that received no new bar. A
840 // symbol whose facts say it is invalid reads na with
841 // ignore_invalid_symbol, and fails the run closed without it; so does a
842 // site whose feed was not installed, naming the symbol and timeframe.
843 void register_security_eval(int sec_id, const std::string& symbol,
844 const std::string& requested_tf, const std::string& input_tf,
845 bool lookahead_on, bool gaps_on = false,
846 bool ignore_invalid_symbol = false);
847 // A string literal where input_tf stands, in five or six arguments, would
848 // bind the same-symbol overload above (a pointer converts to bool ahead of
849 // std::string) and read the symbol as its timeframe; it does not compile.
850 void register_security_eval(int sec_id, const std::string& symbol,
851 const std::string& requested_tf, const char* input_tf,
852 bool lookahead_on, bool gaps_on = false) = delete;
853 // The named column (strategy_set_symbol_feed_column) of the context bar a
854 // foreign site is evaluating, or last evaluated; NaN when the site, the
855 // column or the bar is unknown. Generated payload code reads it.
856 double security_column_value(int sec_id, const std::string& name) const;
857 // The recorded request series `key` on the current chart bar: the value
858 // recorded for the bar that opened at current_bar_.timestamp, NaN where
859 // the tape has no row. A key nobody installed fails the read closed,
860 // naming it.
861 double recorded_series_value(const std::string& key) const;
862 // ``request.security_lower_tf`` registers the same per-sec_id eval
863 // state but with the additional contract that the requested TF must
864 // resolve to a finer-than-input TF emulation. This wrapper sets the
865 // ``lower_tf_array_requested`` flag so ``validate_security_timeframes``
866 // can throw a precise error if the chart's input TF turns out to be
867 // <= the requested TF (mirroring TradingView's "lower timeframe
868 // required" error for ``request.security_lower_tf``).
869 void register_security_lower_tf_eval(int sec_id, const std::string& requested_tf,
870 const std::string& input_tf);
871 // Sub-bar index (0-based) of the current ``request.security_lower_tf``
872 // synthesis within the current chart bar. Returns 0 outside the
873 // synthesis loop. Used by codegen to clear its per-call vector at
874 // sub-bar 0 and push one element per sub-bar after.
875 int security_lower_tf_sub_bar_index(int sec_id) const;
876 // TradingView's request.security / request.security_lower_tf timeframe
877 // rules for the registered sites against the run's evaluator input
878 // timeframe: the diagnostics a script author reads, the lower-timeframe
879 // emulation each array site selects, and the publication gates a
880 // finer-than-chart site runs under.
881 void validate_security_timeframes(const std::string& input_tf);
882 void source_stream_entry_comment(const PyramidEntry&, std::string&) const override;
883 // The adapter's durable state rides the kernel's generic host seam.
884 void hash_host_extension(BrokerStateHashSink&) const override;
885 // Deprecated spelling, kept forwarding so a caller written against it
886 // still reads this host's fold. final: the kernel folds
887 // hash_host_extension, so an override of this name would never be folded.
890 }
891
892private:
893 friend class PineScheduler;
896 // One request.security site of ANOTHER symbol (register_security_eval with a
897 // symbol key). Its payload runs in the requested context: on every bar of the
898 // symbol's feed, in order, as the kernel hands the bars over through the
899 // site's NativeSeriesSource::InstrumentFeed series -- or, on a calendar
900 // chart, as soon as TradingView's merge rule makes a bar visible
901 // (read_ahead_foreign_security_sites). The registration fields are the
902 // site's; the rest is per run.
903 struct ForeignSecuritySite {
904 std::string symbol; // the run-time key, exactly as the script passes it
905 std::string requested_tf; // the literal as registered ("" = the chart's)
906 bool lookahead = false; // barmerge.lookahead_on
907 bool gaps = false; // barmerge.gaps_on
908 bool ignore_invalid = false; // ignore_invalid_symbol
909 // Per run: the feed key's timeframe (canonical spelling), whether the
910 // symbol is invalid (the site then reads na throughout), the kernel
911 // series the site reads (-1 when none), the feed the kernel serves it
912 // from (an index into the installed feeds), the context bars the
913 // payload has run on so far (the next one's bar_index and feed index),
914 // and the close of the bar it ran on last. `eval` carries the
915 // requested-context bar index into the payload's TA members; `syminfo`
916 // is the context's SymInfo.
917 std::string tf;
918 bool invalid = false;
919 std::int64_t subscription = -1;
920 std::size_t feed = 0;
921 std::int64_t delivered = 0;
922 std::int64_t close_ms = 0;
923 // The input the payload last ran on (-1: none yet): a gaps_on site
924 // that received nothing on the current input reads na.
925 std::int64_t last_input = -1;
926 // The bars the kernel's series has handed over so far, the next one's
927 // feed index. A bar the payload already ran on, because the merge
928 // rule made it visible on an earlier chart bar, is handed over below
929 // `delivered` and is not run again.
930 std::int64_t handed = 0;
931 SecurityEvalState eval{};
932 SymInfo syminfo{};
933 };
934
935 StagedConfiguration staged_configuration() const;
936 static PineStrategyConfig apply_overrides(PineStrategyConfig,
937 const StrategyOverrides&);
938 std::uint64_t adapter_broker_fill_event_sequence() const noexcept {
939 return broker_fill_event_seq_;
940 }
941 void scheduler_prepare_script_run(PineScheduler::InputView,
942 bool static_eligible, int expected_script_bars,
943 bool script_bar_geometry);
944 void scheduler_configure_security_evaluators();
945 bool scheduler_uses_aux_security_feed() const noexcept;
946 void scheduler_prepare_security_sequence(PineScheduler::InputView);
947 // R5 lane R3b: the plain request.security sites of a batch run whose
948 // input and script timeframes coincide are declared to the kernel as
949 // NativeTimeframeSubscription series instances at the L6c begin-time hook
950 // -- one per site, sec_id by index, barmerge.gaps_on as the kernel's
951 // `gaps` -- and the kernel's own pump steps them; this host's evaluator
952 // registration, per-run preparation and pump stand down for that run.
953 // False, keeping this host's own drive, when the run or any site needs a
954 // Pine-only rule the kernel step does not have: a stream (the kernel
955 // takes confirmed bars only), the bar magnifier or an aggregated chart
956 // (the calling-bar deferrals), the auxiliary feed, the range-start
957 // cut and its OTC-daily pins, the historical lookahead projection,
958 // barmerge.lookahead_on, ticker.heikinashi, request.security_lower_tf,
959 // sec_ids that are not the registration order, or a declaration the
960 // kernel refuses.
961 bool declare_security_sites_to_kernel();
962 // The same-symbol half of that predicate: true, with `declared` holding
963 // one Input series per site, when every same-symbol site can be stepped
964 // by the kernel; false (and `declared` untouched) otherwise.
965 bool same_symbol_sites_routable(std::vector<NativeTimeframeSubscription>& declared);
966 // Lane XSYM-D: the foreign sites' per-run resolution. Each site's feed and
967 // facts are looked up, an InstrumentFeed series is appended to `declared`
968 // for every site that reads one, and a site that can read nothing fails
969 // the run closed (thrown, naming the symbol and timeframe).
970 void prepare_foreign_security_sites(std::vector<NativeTimeframeSubscription>& declared);
971 // The site's payload on the next bar of its feed, in the requested
972 // context: `bar` and `close_ms` are that bar's own.
973 void run_foreign_site_bar(ForeignSecuritySite& site, const Bar& bar, std::int64_t close_ms);
974 // On a calendar chart, every foreign bar TradingView's merge rule makes
975 // visible at `chart_bar` that the kernel has not handed over yet, run in
976 // feed order after the input's deliveries (pine_security_eval.cpp).
977 void read_ahead_foreign_security_sites(const Bar& chart_bar);
978 // gaps_on foreign sites read na on an input that ran their payload on no
979 // new bar: cleared at every chart bar, after the input's deliveries.
980 void clear_gapped_foreign_security_sites();
981 std::int64_t find_symbol_feed(const std::string& key, const std::string& tf) const;
982 bool set_recorded_series_impl(const std::string& key, const int64_t* chart_open_ms,
983 const double* values, int n,
984 std::optional<std::size_t> cache_capacity_bytes);
985 bool set_recorded_series_reader_impl(
986 const std::string& key, std::shared_ptr<const FeedReader<RecordedSeriesRow>> reader,
987 std::optional<std::size_t> cache_capacity_bytes);
988 void refresh_symbol_data_digest();
989 std::uint64_t calculate_symbol_data_digest(
990 const std::string* replacement_key = nullptr,
991 const RecordedSeries* replacement = nullptr) const;
992 // AR-R4: feed i's bars, closes and column values, wherever they are held:
993 // this host's store, or the kernel's run spec while they are lent to it.
994 const NativeInstrumentFeed& installed_symbol_feed(std::size_t i) const;
995 // Copies lent bytes back from the kernel's spec, which keeps its own.
996 void restore_symbol_feeds(); void stage_symbol_feed_views();
997 // The same for a request-data door: the reason it failed, or empty.
998 std::string restore_symbol_feeds_reason();
999 // True while the running spec names this host's sites: the kernel steps
1000 // them and the scheduler feeds nothing.
1001 bool security_sites_kernel_routed() const noexcept;
1002 void init_security_eval_states_for_run(const std::string& effective_input_tf);
1003 void prepare_historical_security_lookahead_projections(
1004 const Bar* input_bars, int n_input, const std::string& effective_input_tf);
1005 void clear_historical_security_lookahead_projections();
1006 bool scheduler_feed_security_input(const Bar&, std::int64_t next_input_ms,
1007 bool calling_bar_complete,
1008 bool defer_boundary_gate);
1009 void scheduler_publish_security_boundary();
1010 void scheduler_feed_deferred_security_input(const Bar&, std::int64_t next_input_ms);
1011 void scheduler_feed_aux_security(int chart_index);
1012 void scheduler_feed_deferred_aux_security(int chart_index);
1013 void scheduler_finish_security_sequence();
1014 // Pine's evaluator step for one request.security site and one evaluator
1015 // input bar: the kernel's aggregation, native-bar substitution and
1016 // dispatch primitives, composed under TradingView's publication rules
1017 // (src/source/pine_security_eval.cpp).
1018 void pine_feed_security_eval_state(SecurityEvalState& state, const Bar& input_bar,
1019 bool calling_bar_complete = false);
1020 // The site's Pine semantics; a sec_id registered outside this host's
1021 // register_security_eval (none today) reads as a plain site.
1022 PineSecurityEvalState& pine_security_state(int sec_id) {
1023 return pine_security_states_[sec_id];
1024 }
1025 const PineSecurityEvalState& pine_security_state(int sec_id) const {
1026 static const PineSecurityEvalState plain{};
1027 const auto found = pine_security_states_.find(sec_id);
1028 return found == pine_security_states_.end() ? plain : found->second;
1029 }
1030 // Drops the entries of sec_ids the evaluator registry no longer holds.
1031 void prune_pine_security_states();
1032 // Range-start gate for one evaluator: true when the input bar at
1033 // `input_ts` belongs to an HTF bucket that opened before the cut --
1034 // security_range_start_ms_ under the flag, else the run's first chart
1035 // bar for a coarser-than-chart / chart-timeframe evaluator
1036 // (security_first_chart_bar_ms_, split-feed runs with the auxiliary feed
1037 // proving prior trading, or single-feed historical intraday forex/cfd D
1038 // requests keyed to their session's actual open; false for lower TFs). The
1039 // progressive feed and the historical lookahead projection builder must
1040 // agree on this predicate so projected child indexes line up with the
1041 // per-state feed cursor.
1042 bool security_input_precedes_range_start(const SecurityEvalState& state,
1043 int64_t input_ts) const;
1044#ifdef PINEFORGE_HAS_AUX_SECURITY_FEED_V1
1045 // True when the auxiliary request.security feed holds a bar in
1046 // [from_ms, to_ms): the evidence that an HTF bucket whose nominal open
1047 // precedes the run's first chart bar was in progress at the range start.
1048 bool aux_security_traded_between(int64_t from_ms, int64_t to_ms) const;
1049#endif
1050 // The calling chart bar's publication boundary for a gated site
1051 // (publish_gate_tf_seconds): re-publish the completed caller's final
1052 // requested value before the retained boundary input is fed.
1053 void publish_security_eval_state_at_calling_boundary(SecurityEvalState& state);
1054 void scheduler_record_range_end(const Bar&);
1055 void present_report(ReportC*) const override;
1056 // Pine presentation re-marks owning trade/equity arrays, including an
1057 // unbound host's future terminal quote. No bounded-state adapter exists.
1058 bool report_presenter_supports_non_full() const noexcept override { return false; }
1059 bool report_terminal_quote_eligible() const;
1060 std::int64_t report_quote_time_ms_ = -1;
1061 double report_quote_close_ = std::numeric_limits<double>::quiet_NaN();
1062 // One report point per published source slot. The kernel records it
1063 // (NativeReportPolicy::KernelRecordedAtHostMarks); this host owns only
1064 // the Pine cadence that says where the points fall.
1065 void scheduler_mark_report_point(std::int64_t script_bar_ts);
1066 void scheduler_record_broker_hash();
1067 void capture_script_continuation_hash();
1068 // The one writer of the session flags below: it selects the kernel's
1069 // session-day facts of the script bar being published, and widens the
1070 // chart's day by the pre- and post-market bars an extended chart holds.
1071 void scheduler_update_session_state();
1072 void update_extended_session_day(const NativeDecisionContext& facts);
1073 void adapter_label_bracket_trades(
1074 const native_order::ExecutionAppliedEvent&, bool from_bracket);
1075 bool adapter_has_open_entry_id(const std::string&) const;
1076 void scheduler_publish_source_bar(const Bar&, bool first_tick,
1077 bool advance_source_index = true);
1078 void scheduler_publish_suppressed_tail(const Bar&);
1079 // The selected window's pre-roll capture hooks (src/source/pine_intent_state.hpp):
1080 // nonvirtual, and no member of this class; the state is the consumer's host
1081 // state. Begin resets the buffer to the evaluation labelled `label_ms` and
1082 // opens its capture bracket; it answers false, doing nothing, unless the run is
1083 // selected and the label precedes the window. Finish records the evaluation's
1084 // end configuration and closes the bracket. A suppressed pre-roll tail runs no
1085 // body and clears the buffer to an empty evaluation.
1086 bool selected_begin_evaluation(std::int64_t label_ms);
1087 void selected_finish_evaluation() noexcept;
1088 void selected_clear_suppressed_evaluation(std::int64_t label_ms);
1089 // The selected window's replay of that buffer, nonvirtual like the hooks above.
1090 // At the first Open labelled inside the window, ahead of adapter_.on_bar_open,
1091 // the last pre-roll evaluation's rows go to the adapter's own doors once, in
1092 // their original order, each under its own configuration and retained point,
1093 // inside the consumer's open activation scope; then the same drains a source
1094 // evaluation ends with and the terminal explicit-market policy, submit-only.
1095 // It does nothing, and allocates nothing, for a run that is not selected and
1096 // for a label before the window. A broken precondition or a native failure
1097 // throws engine_invariant: the replay is never skipped quietly.
1098 void selected_replay_at_open(const Bar&, const NativeDecisionContext&);
1099 // The drains that close a source evaluation once its body has run: pending
1100 // closes, pending entries, bracket legs, relative exits and the same-point
1101 // entry order. The one list scheduler_publish_source_bar and the replay share.
1102 void finish_adapter_evaluation();
1103 void project_short_seed_report_rows(const native_order::ExecutionAppliedEvent&);
1104 void order_global_exit_children(const native_order::ExecutionAppliedEvent&);
1105 detail::CommandScratchStore* trade_scratch_ = nullptr;
1106 detail::CommandScratchStore& trade_scratch();
1107 std::optional<std::size_t> sort_same_bar_exit_trades(
1108 TradeLedger&, PineExecutionAdapter&);
1109 bool scheduler_coof_enabled() const noexcept { return config_.calc_on_order_fills; }
1110 // The range-end row an applied execution may complete. It stays ordered
1111 // after the fill's calc_on_order_fills recalculation, which can advance
1112 // the source bar counter, so it runs in whichever of the two callbacks is
1113 // last for that event.
1114 void record_applied_range_end();
1115#ifdef PINEFORGE_HAS_AUX_SECURITY_FEED_V1
1116 void clear_aux_security_chart_ranges();
1117 void prepare_aux_security_chart_ranges(const Bar*, int, const std::string&);
1118 std::int64_t aux_security_calling_close_ms() const;
1119 void feed_aux_security_for_chart_bar(int);
1120 void feed_deferred_aux_security_for_chart_bar(int);
1121#endif
1122
1123protected:
1124 // @source-state begin
1127 public:
1128 explicit SourceCloseObligationView(const PineExecutionAdapter& adapter) noexcept
1129 : adapter_(&adapter) {}
1130 bool pending() const noexcept {
1131 return adapter_ && adapter_->cap.due_cause().has_value();
1132 }
1133 private:
1134 const PineExecutionAdapter* adapter_ = nullptr;
1135 };
1136 // Retained protected spelling for source fixtures/generated code. The
1137 // authoritative due request lives in IntradayCap; this read-only facade
1138 // prevents a second mutable close-obligation owner.
1143 // Generated strategies still use this source-series spelling directly.
1144 // The state remains scheduler-owned and is hashed by PineScheduler.
1155 const bool& is_last_tick_;
1158 std::uint64_t source_callback_count_ = 0;
1161#ifdef PINEFORGE_HAS_AUX_SECURITY_FEED_V1
1162 std::vector<Bar> aux_security_bars_;
1164 std::vector<std::size_t> aux_security_chart_begin_;
1165 std::vector<std::size_t> aux_security_chart_end_;
1166 // R5 lane V19-E: the four above as the v4 extension folds them, digested
1167 // whenever they are set (never during a run): the fold reads one word
1168 // instead of every auxiliary bar.
1169 std::uint64_t aux_security_digest_ = 1469598103934665603ULL;
1170 void refresh_aux_security_digest() noexcept;
1171#endif
1172 // Per-sec_id request.security semantics (PineSecurityEvalState), keyed and
1173 // folded in sec_id order. An entry lives exactly as long as the kernel
1174 // evaluator state registered under the same sec_id: generated
1175 // configure_security_evaluators() opens with security_eval_states_.clear(),
1176 // so the first registration into an empty registry starts this table over.
1177 std::map<int, PineSecurityEvalState> pine_security_states_;
1178 // Lane XSYM-D: other symbols' data, installed before a run
1179 // (strategy_set_symbol_feed / _column / _facts, strategy_set_recorded_series)
1180 // and read by the foreign request sites. The feeds become the projected
1181 // spec's instrument feeds (keyed by the run-time symbol string and the
1182 // canonical timeframe spelling), so the kernel digests their bars; the
1183 // facts and tapes are this host's alone and fold through the digest
1184 // below, taken whenever they are set (never during a run).
1185 // AR-R4: one copy of the bytes. A configure the kernel applies takes each
1186 // feed's bars, closes and column values into its run spec (lent, below);
1187 // while lent, an entry here keeps its instrument, tf and column names --
1188 // all generated code reads of it -- and its bytes are read through
1189 // installed_symbol_feed(i). A door or the next begin copies them back.
1190 std::vector<NativeInstrumentFeed> symbol_feeds_;
1192 std::map<std::string, SymbolFacts> symbol_facts_;
1193 std::map<std::string, RecordedSeries> recorded_series_;
1194 std::uint64_t symbol_data_digest_ = 1469598103934665603ULL;
1195 // The foreign sites in sec_id order, and per kernel series index the
1196 // foreign sec_id it serves (-1: a same-symbol series). Generated
1197 // configure_security_evaluators() registers the sites; on_native_run_begin
1198 // starts the table over.
1199 std::map<int, ForeignSecuritySite> foreign_security_sites_;
1201 // The accepted input being processed, as on_native_input last saw it; a
1202 // foreign site's deliveries are stamped with it.
1203 std::int64_t foreign_input_index_ = -1;
1204 // @source-state end
1205 // The foreign site whose payload is being evaluated: its requested
1206 // context answers pine_bar_index(), time_close() and syminfo_ for the
1207 // length of the dispatch, and nothing outside one. Never durable.
1208 struct BorrowedFeedSlot { const Bar* bars = nullptr; const std::int64_t* close_ms = nullptr; std::size_t size = 0; bool present = false; }; std::vector<BorrowedFeedSlot> symbol_feed_views_; const ForeignSecuritySite* foreign_context_ = nullptr;
1209
1210 // Provider configuration, not book state: it is staged before a run and
1211 // projected into the adapter at begin, so it is waived from the durable
1212 // fold like the other configuration slots.
1214
1215 // Live-runtime tail overrides (spec §3.1 / §3.2), provider configuration
1216 // like margin_call_enabled_ above: consumed at begin and by the
1217 // scheduler's publication of the last script bar, never by broker
1218 // settlement. See set_realtime_tail / set_probe_suppress_tail_logic.
1219 bool realtime_tail_ = false;
1222
1223 // The script bar's session flags. Generated code reads session.isfirstbar
1224 // and session.islastbar from the next two -- and, as the transpiler lowers
1225 // them today, their _regular spellings as well, which the last two answer:
1226 // the plain pair is the chart's session day, widened by the pre- and
1227 // post-market bars an extended-hours chart holds, the _regular pair the
1228 // regular session's day, the kernel's facts as they are.
1229 // session_ismarket_ is the kernel's in-session fact. A generated chart
1230 // session.ismarket does not read it: it asks the session calendar at the
1231 // bar's open (the transpiler's _pf_session_market_ helper, e.g.
1232 // corpus/validation/symbol-specified/AAPL/session-ismarket-nyse-rth-01),
1233 // and one inside a request.security payload calls pine_session_ismarket
1234 // above. The flags were BacktestEngine members the kernel only cleared (R5
1235 // lane F5 moved them here). scheduler_update_session_state writes them
1236 // before each source callback and on_native_run_begin clears them;
1237 // derived from the kernel's session-day facts, they fold into no hash, as
1238 // the facts do not.
1239 bool session_ismarket_ = false;
1244 // TradingView's session calendar for the chart's symbol
1245 // (set_symbol_calendar), and the days the C ABI's syminfo metadata has
1246 // named since the last run began (index -> open / close,
1247 // "symbol_calendar_*"), installed and consumed when a run begins. Run
1248 // inputs, as the feed is: they fold into no hash.
1249 std::vector<std::pair<std::int64_t, std::int64_t>> symbol_calendar_;
1250 std::map<long long, double> symbol_calendar_opens_;
1251 std::map<long long, double> symbol_calendar_closes_;
1254
1255 // Live-runtime tail (spec §3.1): once script_tf_seconds_ is known for
1256 // this run, freeze pine_last_bar_index()/last_bar_time_ at the horizon
1257 // bar instead of the fed array's actual last index. No-op unless
1258 // realtime_tail_ is on and realtime_tail_horizon_bars_ > 0.
1259 //
1260 // `script_bar_geometry` says whether `bars` is script-bar geometry:
1261 // true -- every run without aggregation (input_tf == script_tf, the
1262 // single-TF run(bars, n) included: PineScheduler::run_begin
1263 // passes !needs_aggregation), where the horizon
1264 // indexes `bars` directly; last_bar_time_ is the EXACT
1265 // timestamp of bars[horizon_bars - 1] when that bar exists,
1266 // else extrapolated from the array's actual final bar.
1267 // false -- `bars` is the *input* array under aggregation
1268 // (needs_aggregation, input_tf < script_tf): indexing it by
1269 // a script-bar horizon would land on the wrong input bar
1270 // (final-rereview.md N1), so instead extrapolate from the
1271 // first input bar's timestamp, one script-TF step per
1272 // horizon bar (the pre-fix formula, restored for this path
1273 // only).
1274 void apply_realtime_tail_horizon(const Bar* bars, int n, bool script_bar_geometry);
1275
1276 // Opt-in chart warmup parity (see set_syminfo_metadata,
1277 // "chart_ema_na_warmup"). When enabled, chart-timeframe ta.ema instances
1278 // first used by on_bar na-warm per TV built-in semantics. This selector is
1279 // scoped independently from request.security so the two execution contexts
1280 // cannot leak their warmup mode into each other. Default OFF.
1282 // Independent opt-in HTF warmup parity. When enabled,
1283 // request.security series aggregate from security_range_start_ms_ instead
1284 // of the feed start and their embedded ta.ema na-warm per TV built-in
1285 // semantics. The cut is taken per evaluator on HTF-BUCKET opens, not on
1286 // input timestamps: an input bar is dropped when the D/W/M (or intraday
1287 // grid) bucket it belongs to opened before the range start, so the first
1288 // HTF bar every series sees is a whole bucket that opened at/after the
1289 // range start (security_input_precedes_range_start). Default OFF;
1290 // consulted only by pine_feed_security_eval_state, the auxiliary
1291 // lower-timeframe slice and the historical lookahead projection builder.
1294 // The run's first chart bar (0 outside a run). Without the flag above
1295 // this is the default cut for every coarser-than-chart and chart-
1296 // timeframe request.security aggregation: TradingView's deep-backtest
1297 // series of a timeframe hold the bars of that timeframe whose OPEN lies
1298 // at or after the range start, and the bucket in progress at the range
1299 // start is absent -- on every lane (round 8, family P: masayanfx
1300 // multi-time-score; lab tv famp-sense-{es15full,nq15full,f15full,
1301 // nifty15full,nifty1d,xau1d,xau15,eur15,eth15,btc1d}, 2026-09-05: "D"
1302 // on CME_MINI:ES1! 15m first reads on the 05-01 20:45Z bar (bucket 0 is
1303 // the 04-02 trade date; the 04-01 date opened 03-31 22:00Z before the
1304 // 04-01 00:00Z range start), "240" on 04-04 13:45Z (the 22:00Z bucket
1305 // dropped), "W" on the 08-29 / 08-28 bar on every 15m and 1D lane (the
1306 // Mon 03-31 week dropped), while "60" on a 00:00Z start and "D" on the
1307 // NYSE / NSE lanes (the chart's first bar IS the session open) keep
1308 // their first bucket). Whether the bucket was in progress is read from
1309 // the auxiliary 1m feed (did it trade between the bucket's nominal open
1310 // and the first chart bar? the NSE week whose Monday was a holiday opens
1311 // on Tuesday and is kept). Historical intraday single-feed forex/cfd D
1312 // requests also omit their partial first session, using its actual trading
1313 // open rather than its label. Other single-feed series keep their feed-start
1314 // behavior; native feeds retain their own rules. Lower-TF evaluators are
1315 // untouched (their slices begin at the first chart bar anyway), and the
1316 // flag above keeps its explicit epoch plus the EMA na-warmup semantics.
1318
1319 // Nominal close (TradingView's time_close) of the CALLING chart bar the
1320 // input bar being fed belongs to; 0 = the input bar is the chart bar
1321 // (single-feed runs, streams). Set per native chart bar on the
1322 // split-feed path, where a finer auxiliary slice advances
1323 // request.security under a D/W/M chart bar whose close an OTC
1324 // calendar bucket compares against the period's nominal close
1325 // (TimeframeAggregator::feed(bar, next_input_ms, calling_close_ms)).
1327
1328 // Opt-in historical-only request.security lookahead projection. TradingView
1329 // can merge a completed higher-timeframe bar onto the first chart child
1330 // when a finite historical batch is already known. The normal path
1331 // remains progressive, and stream warmup/realtime deliberately ignore this
1332 // selector so future data can never leak into a live continuation.
1335
1336 // Boundary-fallback publication replays the completed caller's already
1337 // evaluated final requested value. Force generated TA sites down their
1338 // recompute path so the replay advances merged history only, never the
1339 // requested-context TA cadence. True only inside
1340 // publish_security_eval_state_at_calling_boundary's own scope.
1342
1343 // Read-only test projection cache; no future execution can observe it.
1344 mutable std::vector<FixtureIntentRow> source_pending_view_cache_;
1345
1346 // Held units the applied request's precommit saw (the owner's pre-fill
1347 // position_qty_); cleared after the applied notification.
1348 mutable double precommit_held_units_ =
1349 std::numeric_limits<double>::quiet_NaN();
1350
1351 // hold_script_average_price() wrote position_entry_price_ on the flat
1352 // book this pass; release_script_average_price() clears it.
1355 const NativeDecisionContext&);
1356#ifdef PINEFORGE_HAS_AUX_SECURITY_FEED_V1
1358#endif
1359};
1360
1364
1365} // namespace pineforge::source
The public native host: an abstract subclass of BacktestEngine with no PineScript on it.
FixtureCommissionTypeSlot & operator=(CommissionType value) noexcept
FixtureQtyTypeSlot & operator=(QtyType value) noexcept
FixtureRiskDirectionSlot & operator=(int value) noexcept
SourceCloseObligationView(const PineExecutionAdapter &adapter) noexcept
const_iterator find(const std::string &id) const noexcept
void validate_security_timeframes(const std::string &input_tf)
compat::pine::Calculation fixture_cap_calculation() const
void register_security_eval(int sec_id, const std::string &requested_tf, const std::string &input_tf, bool lookahead_on, bool gaps_on=false, bool heikinashi=false)
std::map< long long, double > symbol_calendar_opens_
std::map< std::string, RecordedSeries > recorded_series_
bool set_symbol_facts(const std::string &key, const std::string &field, const std::string &value) override
FixtureCommissionTypeSlot fixture_commission_type_slot() noexcept
bool set_realtime_tail(bool on, int horizon_bars) override
void set_syminfo_session(const std::string &)
std::optional< NativeMarginDecision > resolve_margin_requirement(const NativeMarginRequirementView &) const final
const std::pair< std::int64_t, std::int64_t > * symbol_calendar_day(std::int64_t ms) const
std::optional< double > resolve_margin_call_units(const NativeMarginCallView &) const final
const Series< double > & get_input_source(const std::string &key, const Series< double > &fallback) const
double open_profit(double current_price) const
void prepare_native_begin(const NativeBeginArgs &) final
BarTime fixture_chart_time(std::int64_t timestamp_ms) const
compat::pine::CapClock fixture_cap_clock() const
void strategy_entry(const std::string &id, bool is_long, double limit_price=std::numeric_limits< double >::quiet_NaN(), double stop_price=std::numeric_limits< double >::quiet_NaN(), double qty=std::numeric_limits< double >::quiet_NaN(), const std::string &comment={}, const std::string &oca_name={}, int oca_type=0, int qty_type=-1)
const PendingIntentView & pending_intent_view() const noexcept
const std::vector< FixtureIntentRow > & source_pending_view() const
void finish_applied_admission(const native_order::ExecutionAppliedEvent &, const NativeDecisionContext &)
void on_native_tick(const Bar &, const NativeTickContext &) final
SourceCloseObligationView position_close_obligation_
const Series< double > & source_input_series(const std::string &key, const Series< double > &fallback) const
int pending_order_effective_levels(int index, double *stop, double *limit, double *trail_activation) const
int observe_pending_effective_levels(int, double *, double *, double *) const override
bool set_aux_security_feed(const Bar *bars, int n, const std::string &input_tf) override
double observe_trail_best_price_v1() const override
void strategy_order(const std::string &id, bool is_long, double qty, double limit_price=std::numeric_limits< double >::quiet_NaN(), double stop_price=std::numeric_limits< double >::quiet_NaN(), const std::string &oca_name={}, int oca_type=0)
void configure_pine_strategy(const PineStrategyConfig &)
FixtureQtyTypeSlot fixture_default_qty_type_slot() noexcept
bool set_recorded_series_reader(const std::string &key, std::shared_ptr< const FeedReader< RecordedSeriesRow > > reader)
void strategy_exit_cancel_bracket(const std::string &exit_id, const std::string &from_entry, const std::string &comment={})
int observe_pending_level_resolved(int) const override
FixtureRiskDirectionSlot fixture_risk_direction_slot() noexcept
bool set_symbol_feed_borrowed(const std::string &key, const std::string &timeframe, const Bar *bars, const int64_t *close_ms, int n) override
bool set_symbol_calendar(std::vector< std::pair< std::int64_t, std::int64_t > > sessions)
bool pine_session_ispremarket(const std::string &session, const std::string &timezone, std::int64_t bar_ms) const
virtual void on_native_applied_event(const native_order::ExecutionAppliedEvent &, const NativeDecisionContext &)
std::vector< BorrowedFeedSlot > symbol_feed_views_
std::vector< std::size_t > aux_security_chart_begin_
bool pine_session_ismarket(const std::string &session, const std::string &timezone, std::int64_t bar_ms) const
const std::vector< std::pair< std::int64_t, std::int64_t > > & symbol_calendar() const noexcept
const Series< double > & source_series(const std::string &) const
bool set_symbol_feed_column(const std::string &key, const std::string &timeframe, const std::string &name, const double *values, int n) override
void apply_realtime_tail_horizon(const Bar *bars, int n, bool script_bar_geometry)
int64_t chart_bar_close_ms(int64_t stamp) const
std::vector< std::pair< std::int64_t, std::int64_t > > symbol_calendar_
void hash_source_extension(BrokerStateHashSink &sink) const final
void on_native_applied(const native_order::ExecutionAppliedEvent &, const NativeDecisionContext &) final
std::vector< admission::Field > market_admission_fields() const
PineStrategyHost(compat::pine::CapAttachment cap=compat::pine::CapAttachment::None)
int security_lower_tf_sub_bar_index(int sec_id) const
bool set_symbol_feed(const std::string &key, const std::string &timeframe, const Bar *bars, const int64_t *close_ms, int n) override
std::map< std::string, SymbolFacts > symbol_facts_
int observe_last_bar_dual_entry_path_v1() const override
void set_trade_scratch(detail::CommandScratchStore &)
std::int64_t fixture_chart_day_key(std::int64_t timestamp_ms) const noexcept
std::uint64_t broker_state_hash_projection() const override
double security_column_value(int sec_id, const std::string &name) const
std::vector< FixtureIntentRow > source_pending_view_cache_
bool margin_check_allowed(const NativeMarginCheckPoint &) const final
void fixture_publish_source_series(const Bar &bar, bool new_history_slot)
std::optional< double > resolve_anchored_level(const NativeAnchoredLevelView &) const final
void hash_host_extension(BrokerStateHashSink &) const override
void register_security_lower_tf_eval(int sec_id, const std::string &requested_tf, const std::string &input_tf)
std::uint64_t fixture_applied_receipt_count() const
bool security_series_slot_is_new(int) const noexcept
PineStrategyConfig & fixture_configuration() noexcept
void register_security_eval(int sec_id, const std::string &symbol, const std::string &requested_tf, const char *input_tf, bool lookahead_on, bool gaps_on=false)=delete
bool report_terminal_quote_applied(const ReportC &) const
std::vector< NativeInstrumentFeed > symbol_feeds_
std::int64_t symbol_calendar_open_before(std::int64_t before) const
void strategy_exit(const std::string &id, const std::string &from_entry, double limit_price, double stop_price, double trail_points=std::numeric_limits< double >::quiet_NaN(), double trail_offset=std::numeric_limits< double >::quiet_NaN(), double trail_price=std::numeric_limits< double >::quiet_NaN(), double qty_percent=100.0, const std::string &comment={}, double qty=std::numeric_limits< double >::quiet_NaN(), const std::string &oca_name={}, double profit_ticks=std::numeric_limits< double >::quiet_NaN(), double loss_ticks=std::numeric_limits< double >::quiet_NaN())
void on_native_input(const Bar &, const NativeInputContext &) final
std::map< long long, double > symbol_calendar_closes_
bool source_aux_security_feed_enabled() const override
bool set_recorded_series(const std::string &key, const int64_t *chart_open_ms, const double *values, int n) override
int short_seed_collision_role_v1(native_order::RequestHandle) const noexcept
void strategy_close(const std::string &id, const std::string &comment={}, double qty=std::numeric_limits< double >::quiet_NaN(), double qty_percent=std::numeric_limits< double >::quiet_NaN(), bool immediately=false)
std::map< int, PineSecurityEvalState > pine_security_states_
double recorded_series_value(const std::string &key) const
void on_native_bar_open(const Bar &, const NativeDecisionContext &) final
bool set_report_terminal_quote(std::int64_t time_ms, double close)
bool set_probe_suppress_tail_logic(bool on) override
MarketAdmissionJournal & market_admission_journal()
static detail::ScratchKeyOrder trade_scratch_order() noexcept
NativePrecommitVerdict validate_execution_precommit(const NativePrecommitView &) const final
int64_t pine_time_offset(int64_t bar_open_ms, int bars_back, const std::string &tf, const std::string &session, const std::string &tz, int timeframe_bars_back, bool close) const
void on_native_timeframe_bar(const Bar &, const NativeTimeframeBarContext &) final
bool pine_session_ispostmarket(const std::string &session, const std::string &timezone, std::int64_t bar_ms) const
void source_aux_security_input_view(const Bar *&, int &) const override
double script_mark_price(double price) const
void on_native_bar(const Bar &, const NativeDecisionContext &) final
int observe_probe_fill_qty(int, double, double *, int *, int *) const override
const PineStrategyConfig & fixture_configuration() const noexcept
void set_syminfo_metadata(const std::string &, double) override
void set_strategy_override(const StrategyOverrides &)
SourceIdLedgerView source_id_ledger_view() const noexcept
virtual void on_source_bar(const Bar &)=0
int observe_pending_copy_v1(int, pf_pending_order_v1_t *) const override
std::vector< std::size_t > aux_security_chart_end_
bool adapter_core_sizes_default_opening(bool is_long) const
native_order::ExecutionTerms resolve_execution_terms(const NativeExecutionTermsFacts &) const final
void on_native_sub_bar(const Bar &, const NativeDecisionContext &) final
int probe_fill_qty(int index, double fill_price, double *qty, int *close_only, int *partition) const
void on_native_recalculate(const Bar &, const NativeDecisionContext &, NativeCalculationReason, const native_order::ExecutionAppliedEvent *) final
std::int64_t symbol_calendar_open_from(std::int64_t at) const
const ForeignSecuritySite * foreign_context_
std::map< int, ForeignSecuritySite > foreign_security_sites_
void source_stream_entry_comment(const PyramidEntry &, std::string &) const override
NativeCalculationReason
Why the kernel is asking the host to calculate.
NativePrecommitVerdict
The host is consulted before generic opening-margin admission.
constexpr char kSourceSecurityDomain[]
PineStrategyHost::FixtureIntentRow FixtureIntentRow
PineStrategyHost::FixtureIntentKind FixtureIntentKind
constexpr char kSourceSymbolDataDomain[]
PineStrategyHost PineNativeHost
bool pine_session_ispostmarket(const std::string &session, const std::string &tz, int64_t bar_ms)
admission::Draft MarketAdmissionDraft
bool pine_session_ispremarket(const std::string &session, const std::string &tz, int64_t bar_ms)
bool pine_session_ismarket(const std::string &session, const std::string &tz, int64_t bar_ms)
admission::Journal MarketAdmissionJournal
Ephemeral read-only facts of one anchored-leg materialization (L7b), offered to the host exactly once...
Borrowed begin-call facts.
Read-only owning-value facts for one candidate.
Accepted input facts presented before the generic consumer aggregates the bar into its script interva...
Ephemeral factual view of one kernel-issued liquidation before its units are fixed.
Ephemeral factual view of one kernel check point, offered to the host before the check runs.
Ephemeral factual view of the numbers the kernel is about to compare, at one check point,...
Ephemeral factual view of one prepared execution before any physical effect.
One accepted realtime print before native matching at its current decision point.
One completed higher-timeframe bucket of a declared subscription (NativeRunSpec::subscriptions),...
One committed execution: the definition, the cursor, the resolved price and units,...
std::vector< HistoricalSecurityProjection > historical_projections
std::optional< std::size_t > cache_capacity_override
pineforge::detail::FeedProjection< RecordedSeriesRow > rows