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