PineForge v0.13.1-379-g9b50973
Deterministic PineScript v6 backtest runtime — C ABI reference
Loading...
Searching...
No Matches
native_run_spec.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <pineforge/bar.hpp>
6
7#include <cstdint>
8#include <optional>
9#include <string>
10#include <type_traits>
11#include <variant>
12#include <vector>
13
14namespace pineforge {
15inline namespace native_run_spec_v3 {
16
17/// Encodings coincide with the versioned native-v1 C transport. These values
18/// describe native execution; they do not configure source strategy policies.
19enum class NativeFeeKind : std::uint32_t {
23};
24
25/// When a request born at a script calculation may first match.
26/// NextEligiblePoint (the default) waits for a later eligible matching point — the
27/// next modeled opening, an observed print, or a carried open — and never fills on
28/// the bar's already presented open/high/low/close. AfterCalculation additionally
29/// offers a modeled close point after that calculation, still obeying the birth
30/// ordinal and floor; it is not a replay of observed prints. The Pine adapter
31/// projects process_orders_on_close onto AfterCalculation. Pinned by
32/// tests/test_native_resting_driver_contract.cpp.
33enum class NativeCloseExecution : std::uint32_t {
36};
37
38/// Abort presentation is a run-level policy rather than an exception-path
39/// convention. Generic hosts retain an error diagnostic by default; a host
40/// that models cooperative cancellation can opt into a quiet status result.
41enum class NativeAbortReporting : std::uint32_t {
42 Error = 0,
43 Quiet = 1,
44};
45
46/// Who records the per-script-bar report series. HostRecorded leaves the
47/// equity curve, its metrics and any range-end row entirely to the host, which
48/// is what every host that drives its own recording already does.
49/// KernelRecorded asks the consumer to mark one equity point per script
50/// calculation, so a bare host gets a truthful curve, finite drawdown/run-up
51/// metrics and a report whose walk is not degenerate. Recording is reporting:
52/// it books no cash and places no order.
53/// KernelRecordedAtHostMarks records the very same series, at the points the
54/// host marks: a host whose report cadence is not one point per calculation —
55/// a source adapter that re-enters its script on a fill, or publishes a bar
56/// its script never calculates — keeps that cadence and still stops owning
57/// what a report point is. The consumer never records on its own initiative
58/// under it, so it leaves the continuation identity exactly where
59/// HostRecorded leaves it (see hash_spec in native_execution_consumer.cpp).
65
66/// Which opening directions the run admits at all. Both is the default and the
67/// whole established surface; a refused opening is
68/// MatchRejectReason::OpeningDirection, and it rejects the ENTIRE transaction,
69/// including a proposed close remainder. Closing-only reductions stay legal under
70/// None. Pinned by tests/test_native_resting_matching_contract.cpp.
71enum class NativeOpenDirections : std::uint32_t {
72 None = 0,
73 Long = 1,
74 Short = 2,
75 Both = 3,
76};
77
78/// When the kernel asks the host to calculate. BarClose is the whole default
79/// surface: exactly one calculation per script bar, at its close, which is
80/// what every host that drives its own cadence already gets. The other two
81/// are a strict superset of the one before them, so a host never loses the
82/// close calculation by opting in.
83///
84/// BarCloseAndFills additionally recalculates once at the cursor of each
85/// applied execution, from the existing applied-notification drain and
86/// bounded by max_recalculations_per_point. EveryModeledPoint additionally
87/// recalculates at every modeled point of the delivered path (each confirmed
88/// OHLC waypoint, each intrabar sample) and at every observed print.
89///
90/// This is a generic cadence, not a source-language policy: TradingView's
91/// waypoint-only COOF refill, its two-fills-at-open rule and its script-state
92/// rollback stay in the source layer, which never sets this field.
93enum class NativeCalculationTrigger : std::uint32_t {
97};
98
99/// What a bar-open callback is handed. Complete keeps the established view:
100/// on_native_bar_open receives the whole script bar, which is what a host that
101/// schedules against the bar's own high/low needs. OpenOnly masks that
102/// lookahead for hosts that must decide at the open with open-only
103/// information: H = L = C = open and volume 0. It changes no matching, no
104/// fill and no other callback; mid-bar callbacks answer current_partial_bar()
105/// for the lookahead-free bar so far.
106enum class NativeOpenBarView : std::uint32_t {
109};
110
111/// A generic instrument price grid. The kernel otherwise treats price_tick as
112/// the slippage multiplier only, so an unset grid leaves every booked price
113/// exactly as the path presented it. QuantizeFills books the fill on the tick
114/// ladder; QuantizeFillsAndTriggers additionally tests a resting trigger
115/// against the tick-quantized path. Source-language tick quirks are not
116/// spelled here: they remain source-layer policy on top of None.
117enum class NativePriceGrid : std::uint32_t {
118 None = 0,
121};
122
123/// HalfUp is the nearest tick with ties away from zero. Directional rounds
124/// toward the price region the resting order needs: a buy limit rounds down
125/// and a sell limit up, a stop the other way, which is also the adverse side
126/// of a market fill.
127enum class NativeGridRounding : std::uint32_t {
130};
131
132/// Which units a kernel-issued liquidation reduces (L4). RestoreMinimum is
133/// the fewest units that restore the marked equity to the maintenance
134/// requirement at the sizing mark; ShortfallMultiple books that same restore
135/// scaled by `shortfall_multiple` (never 4.0 by default: a broker wanting a
136/// multiple declares it); Flatten closes the whole position. A host whose
137/// slice rule is not one of these answers it through
138/// resolve_margin_call_units and declares none of them (the Pine adapter does).
139enum class NativeLiquidationSizing : std::uint32_t {
143};
144
145/// When the kernel tests the maintenance requirement. PathAdverseExtreme
146/// evaluates it against the most adverse price the remaining modeled script
147/// path still reaches and rests the reduction at the liquidation level, so the
148/// fill lands where the account actually runs out of margin. CalculationOnly
149/// tests the mark only at a script calculation point and rests nothing.
150/// PathAdverseExtremeMark is the period-mark broker: it measures the breach at
151/// that same adverse mark and rests the reduction AT THAT MARK, so the fill
152/// lands on the adverse waypoint the breach was measured at instead of on a
153/// solved level. It never solves a level, which is also what makes it the one
154/// mode that still checks where no level exists (a LONG at full maintenance —
155/// see NativeMarginModel). The resting price is the default resolved price of
156/// that fill, so resolve_execution_terms still has the last word on it.
162
163/// Which equity the maintenance requirement is tested against. MarkedEquity is
164/// the account's marked equity exactly as marked_equity() computes it: the open
165/// entries' commissions have already reduced it. MarkedEquityBeforeOpenCommission
166/// is the same mark-to-market equity taken before that reduction (initial
167/// capital + realized net profit + open profit), i.e. a broker whose margin
168/// equity does not charge the still-open entries' commission against the
169/// account. Nothing else about the account model changes: this is one term of
170/// one comparison, never a second accounting truth.
175
176/// Which base the liquidation level is solved from. MarkedEquity is the
177/// intercept of marked_equity(): initial capital + realized net profit minus
178/// the open entries' commissions. RealizedOnly drops that last term, so the
179/// level is solved from initial capital + realized net profit alone. The two
180/// agree whenever no open entry has paid a commission.
181enum class NativeLiquidationLevelBase : std::uint32_t {
184};
185
186/// A generic per-side broker margin model (L4). It is entirely opt-in: a spec
187/// that leaves `NativeRunSpec::margin` unset keeps the one-scalar
188/// `initial_margin_fraction` gate and has no liquidation path at all.
189///
190/// `initial_long` / `initial_short` are the opening-admission fractions (not
191/// percents) applied to the resulting absolute notional of an opening, per
192/// side. Both must be finite and never negative.
193///
194/// ZERO is the MAINTENANCE-ONLY spelling for that side: the kernel enforces no
195/// opening requirement on it, at the candidate gate and at placement alike, and
196/// the HOST owns opening admission there. Opening admission and liquidation are
197/// two different broker functions: a host that runs its own pre-trade check
198/// (and says so at the candidate gate by answering
199/// NativePrecommitVerdict::AdmitWithHostMargin) still wants the kernel's
200/// liquidation mechanism, and this is how it asks for one without the other.
201/// A side may waive either function but not both: `initial_* == 0.0` is legal
202/// only where that side's `maintenance_*` is set (and positive), and a side
203/// with neither is refused as MarginSideUndeclared. A model whose `initial_*`
204/// are positive behaves exactly as it always has.
205///
206/// `maintenance_long` / `maintenance_short` are the liquidation fractions.
207/// Absent means that side never liquidates. Present means the kernel solves
208/// for the liquidation level -- the price at which the marked equity falls
209/// below the maintenance requirement -- and rests a kernel-originated Reduce
210/// with Stop{level} while the requirement is breached on the modeled path.
211/// A maintenance fraction equal to 1.0 has no finite level for a LONG: at
212/// full maintenance a long's equity and requirement move together, so the
213/// breach is a constant and no price solves it. That degenerate slope rests
214/// nothing under PathAdverseExtreme -- there is no price to rest at -- while
215/// PathAdverseExtremeMark, which never solves a level, still measures the
216/// breach at the adverse mark and still consults the host's requirement hook.
217/// Source-language money rules beyond that are source-layer policy, never
218/// spelled here.
219///
220/// `liquidation_min_units` is the broker's minimum liquidation trade: a
221/// computed reduction below it flattens the position instead.
222///
223/// `basis` and `level_base` are the two places brokers legitimately disagree
224/// about money: which equity the requirement is tested against, and which base
225/// the reported level is solved from. Both default to the marked-equity model
226/// the kernel has always used, and both fold into the run spec's digest only
227/// when moved off it.
228///
229/// `liquidation_label` / `liquidation_comment` name the TICKET the kernel's own
230/// liquidation is booked under. A broker's forced liquidation carries the
231/// broker's identifiers, and a reporting layer that classifies a closed row by
232/// its ticket id -- which is the ordinary way to say "this row was a margin
233/// call" -- can only do so if those identifiers are the broker's. Empty keeps
234/// the kernel's own ("__kernel_liquidation__" / "Margin liquidation"); a set
235/// value is used verbatim for the request's label and comment and therefore
236/// for the closed row's exit id and comment. Like the two money bases, each
237/// folds into the model's digest only when set, so a model that does not name
238/// its ticket digests exactly as it did before they existed.
253
254/// One risk threshold (L9). `value` is account currency when `percent` is
255/// false, and a percentage of the limit's own basis equity — the running peak
256/// for a drawdown, the day's opening equity for an intraday loss — when it is
257/// true. Percent is out of 100: 2.5 means two and a half percent.
259 double value = 0.0;
260 bool percent = false;
261};
262
263/// Which day a risk limit's "day" is. SessionDay is the run's own session
264/// calendar: the trading date of the session that contains the instant
265/// (native_calendar::session_day_ordinal), so an overnight session is one day.
266/// CalendarDayInTimezone is the plain civil date in the spec's scheduling
267/// timezone, which is what a host that reports by wall-clock date wants. The
268/// two differ exactly where a session crosses midnight.
269enum class NativeRiskDay : std::uint32_t {
272};
273
274/// What a breach does. BlockOpenings refuses every opening while the block
275/// lasts and leaves the live book alone. FlattenAndBlock first closes the book
276/// with one kernel-originated Flatten and then blocks.
277enum class NativeRiskAction : std::uint32_t {
280};
281
282/// Generic account risk limits (L9), entirely opt-in: a spec that leaves
283/// `NativeRunSpec::risk` unset evaluates nothing, appends no event and folds
284/// nothing into the continuation digest. The existing per-opening caps
285/// (max_abs_units, max_open_lots, allowed_open_directions) are unchanged and
286/// independent of this block.
287///
288/// Every limit is measured at three points of each script bar — its open, its
289/// own close calculation, and after each applied drain — against the marked
290/// equity there:
291/// max_drawdown equity has fallen from its running peak by at
292/// least the limit (percent: of that peak)
293/// max_intraday_loss equity is below the day's opening equity by at
294/// least the limit (percent: of that opening
295/// equity)
296/// max_consecutive_loss_days N consecutive days each closing with a realized
297/// loss; a day that closes with a realized profit
298/// restarts the count, a day with no realized
299/// result leaves it where it was
300/// max_fills_per_day N applied fills within the day, counted as they
301/// settle, so the fills of one matching point all
302/// settle and the limit blocks the next admission
303///
304/// A drawdown or consecutive-loss-days breach blocks openings to the end of
305/// the run; the two intraday limits block to the end of their own day.
306/// TradingView's own strategy.risk.* rules are not this model: its chart-day
307/// key, its cancel-pending behaviour and its epsilon stay in the source layer,
308/// which never sets this block.
310 std::optional<NativeLossLimit> max_drawdown;
311 std::optional<NativeLossLimit> max_intraday_loss;
312 std::optional<std::uint32_t> max_consecutive_loss_days;
313 std::optional<std::uint32_t> max_fills_per_day;
316};
317
318/// Native hosts normally require every confirmed bar to name a canonical input
319/// slot. A host whose feed carries provider labels can retain the caller's
320/// strictly-increasing timestamps as its decision labels instead. This is a
321/// feed-shape policy, not a source-language one, and it remains a run-spec
322/// value so the two modes never share a continuation.
323enum class NativeSlotLabelPolicy : std::uint32_t {
326 /// Deprecated spelling of FeedTolerant; identical value, kept so existing
327 /// hosts and the source adapter compile unchanged.
329};
330
331/// Generic ordering for a modeled OHLC path. Auto retains the open-proximity
332/// rule; the forced modes make the first excursion explicit for replay/live
333/// hosts without relying on process-global or source-language state.
334enum class NativePathOrder : std::uint32_t {
335 Auto = 0,
338};
339
340/// Explicit, opt-in admission exceptions for a tolerated input-feed shape.
341/// They are separate from slot labels because a host may need the tolerant
342/// price / unavailable-volume admission while retaining canonical calendar
343/// labels. This is a feed-shape policy, not a source-language one.
344enum class NativeFeedTolerance : std::uint32_t {
345 None = 0,
346 /// Match the batch structural check: finite OHLC values need not be
347 /// positive, and NaN volume means unavailable activity.
349 /// Source-compatible stream warmups admit finite, non-negative interim
350 /// OHLC values. The final warmup close remains strictly positive.
352};
353
354/// Deprecated spelling of the tolerance type. Same type, same values, same
355/// hash; kept so existing hosts and the source adapter compile unchanged.
357
358/// Whether one NativeFeedTolerance bit is set in a run's mask. Prefer it to
359/// testing the bits by hand; `enabled` is the run's own value and `requested` the
360/// single bit being asked about.
362 NativeFeedTolerance enabled, NativeFeedTolerance requested) noexcept {
363 return (static_cast<std::uint32_t>(enabled)
364 & static_cast<std::uint32_t>(requested)) != 0u;
365}
366
367/// Deprecated spelling of native_feed_tolerance_enabled.
369 NativeFeedTolerance enabled, NativeFeedTolerance requested) noexcept {
370 return native_feed_tolerance_enabled(enabled, requested);
371}
372
373/// An owned lower-timeframe execution path. It is deliberately a run-spec
374/// value rather than a caller borrow: public begin arguments expire when the
375/// begin call returns, whereas native matching may need the lower bars later
376/// while sealing an aggregated script bar.
378 /// No intrabar path: the confirmed OHLC waypoints are the whole modeled walk, and
379 /// on_native_sub_bar never fires. The default.
380 struct none {};
381 enum class SampleEligibility : std::uint32_t {
382 /// Native hosts retain continuous matching between generated samples
383 /// unless they explicitly request point-only sample eligibility.
386 };
387 /// A RETAINED finer feed: the host's own lower-timeframe bars, the literal they
388 /// are at, and the sampling policy over them. It is the only alternative that has
389 /// sub-bars of its own, so it is what makes on_native_sub_bar reachable, and it is
390 /// what gives the margin model a delivered sample to re-evaluate at instead of a
391 /// whole-bar waypoint. C spelling: PF_NATIVE_INTRABAR_LOWER_TF.
402 /// A synthesized path has no retained lower feed. The driver samples each
403 /// script bar's own OHLC path through the generic sampler declared in
404 /// include/pineforge/magnifier.hpp. Its point-only eligibility is inherent
405 /// to this mode, so there is no separate SampleEligibility member.
413 /// The three alternatives as one value. Exhaustive; use the accessors below rather
414 /// than std::get_if at call sites.
415 using value_type = std::variant<none, lower_tf, synthesized>;
416
418
419 /// Whether this run declares no intrabar path at all.
420 bool is_none() const noexcept { return std::holds_alternative<none>(value); }
421 /// The retained finer feed, or nullptr when this is not the lower_tf alternative.
422 const lower_tf* lower() const noexcept { return std::get_if<lower_tf>(&value); }
423 /// The mutable retained finer feed, or nullptr; a staged spec's feed is copied by
424 /// configure_native, so mutating it afterwards changes nothing about the run.
425 lower_tf* lower() noexcept { return std::get_if<lower_tf>(&value); }
426 /// The synthesized sampling policy, or nullptr when this is not that alternative.
427 const synthesized* synthesized_path() const noexcept {
428 return std::get_if<synthesized>(&value);
429 }
430 /// The mutable synthesized sampling policy, or nullptr.
431 synthesized* synthesized_path() noexcept { return std::get_if<synthesized>(&value); }
432};
433
434/// One declared higher-timeframe series of the run's own symbol, the native
435/// equivalent of request.security(syminfo.tickerid, tf, ...). The kernel
436/// aggregates the accepted input into `tf` buckets and delivers each completed
437/// bucket to the host; nothing here configures a source language.
438///
439/// A subscription is a series INSTANCE, not a period: several may declare the
440/// same `tf`, each gets its own evaluator and bucket state, and each is
441/// identified — in NativeTimeframeBarContext::subscription and in
442/// native_series_bar() — by its own index in NativeRunSpec::subscriptions.
443/// The one thing same-period instances cannot each own is a different
444/// `authoritative_bars` feed; see DuplicateSubscriptionTimeframe.
445///
446/// `tf` must pair with NativeRunSpec::input_tf exactly as script_tf does
447/// (native_calendar::compatibility) and may not be strictly finer than the
448/// input: a lower-timeframe array is a different contract.
449///
450/// `authoritative_bars` are the exchange's own bars of that timeframe, at most
451/// one per completed bucket. When present, a completed bucket takes its
452/// OHLCV from the bar keyed to the same period, and those stamps become the
453/// period partition: the feed store is TradingView-calibrated, so "W"/"M"
454/// buckets are built from installed DAILY bars and a session with no stamp of
455/// its own folds into the next trade date's bar. A host that supplies them
456/// inherits those rules (docs/pages/native-engine.md).
457///
458/// `lookahead` is Pine's barmerge.lookahead_off (false, the default: the
459/// bucket is delivered when its last contributing input bar is accepted) or
460/// lookahead_on (true: the completed bucket's final values are delivered at
461/// its FIRST contributing input bar).
462///
463/// `gaps` is Pine's barmerge.gaps_off (false, the default: a delivered bucket
464/// stands until the next delivery replaces it) or gaps_on (true: the series is
465/// CLEARED on every accepted input that delivers no bucket of its own, so
466/// native_series_bar() answers nullopt — the empty that stands for na — on
467/// exactly the bars the series does not publish on). It changes nothing about
468/// which buckets complete, when they are delivered, or what they contain.
469///
470/// `source` names the bars the series is built from. Input (the default, and
471/// the whole surface described above) aggregates the accepted input.
472/// AuxiliaryFeed aggregates NativeRunSpec::auxiliary_feed instead — the finer
473/// bars the input does not have — so `tf` then pairs with the FEED's timeframe
474/// exactly as script_tf pairs with input_tf, and may be finer than the input,
475/// equal to it or coarser. See NativeAuxiliaryFeed for the routing rule.
476enum class NativeSeriesSource : std::uint8_t {
477 Input = 0,
479};
480
488
489/// An auxiliary feed of the run's OWN symbol at a timeframe strictly finer
490/// than the input: the bars the input feed does not have. It drives nothing by
491/// itself — no matching point, no calculation, no script bar — and is read
492/// only by the declared series whose `source` is AuxiliaryFeed.
493///
494/// `tf` must parse, and input_tf must pair over it exactly as a script_tf pairs
495/// over an input_tf, strictly coarser: "1" under a "15" input, "15" under "D".
496/// `bars` are strictly increasing in time and may be empty (a stream that
497/// learns every bar live, NativeStrategyHost::append_auxiliary_bars).
498///
499/// Routing is by time and by nothing else. When an input bar is accepted, every
500/// feed bar not yet consumed that opened BEFORE that input's period ended
501/// (NativeInterval::next_period_open_ms of the input's own interval) is folded,
502/// in feed order, into each AuxiliaryFeed series, ahead of that input's
503/// aggregation, matching and calculation — the delivery point a series built
504/// from the input has. So bars inside the input ride on it; bars in a hole of
505/// the input ride on the next accepted input; bars earlier than the first
506/// input are folded on that first input, which is how a host supplies history
507/// the input does not reach; and bars later than the last input's period are
508/// never folded. There is no chart-slice mapping, no label re-keying and no
509/// deferred publication: those are a source layer's.
511 std::string tf;
512 std::vector<Bar> bars;
513};
514
515/// One complete setup value, staged/copied by NativeStrategyHost before it is
516/// applied at begin. This aggregate owns no host phase, consumed-run counter,
517/// parsed-calendar authority, physical account, or C transport presence mask.
518/// Empty required strings and zero capital/value/FX defaults make an incomplete
519/// value invalid; price_tick == 0 explicitly selects unquantized prices. No
520/// timezone/timeframe/instrument facts are inferred.
523 std::string input_tf;
524 std::string script_tf;
525 /// A public begin with fewer than two bars may not establish a timeframe.
526 /// This preserves that explicit state without inventing a clock literal.
528 /// Strict native hosts retain the canonical slot-label rule. A provider
529 /// feed may opt into raw, strictly-increasing caller labels. The declared
530 /// type spellings below are the ones the native C++ ABI guard pins.
534
535 std::string ticker;
536 std::string tickerid;
537 std::string type;
538 std::string currency;
539 std::string basecurrency;
540 std::string description;
541 std::string volumetype;
542
543 std::string timezone;
544 std::string session; // Empty and "24x7" are distinct all-day literals.
545 std::string chart_timezone; // Optional observation metadata; empty stays empty.
546
547 double initial_capital = 0.0;
548 double point_value = 0.0;
549 double account_fx = 0.0; // One positive scalar, not a timestamped FX series.
550 double price_tick = 0.0; // Finite, nonnegative; zero means unquantized prices.
551 std::uint32_t slippage_ticks = 0; // <= INT_MAX; raw +/- ticks*tick, no snap.
552 /// Opt-in instrument grid. None keeps unquantized prices; both quantizing
553 /// modes require price_tick > 0 and round before slippage.
557 double fee_value = 0.0; // Percent/100 of absolute account notional, or
558 // account-currency cash per unit/execution.
559 std::optional<double> quantity_grid; // Positive; admission only, no resize.
562 std::optional<double> max_abs_units; // Positive resulting-book opening cap.
563 std::optional<std::uint64_t> max_open_lots; // Positive surviving+new lot cap.
565 std::optional<double> initial_margin_fraction; // Positive fraction, not percent;
566 // no maintenance liquidation.
567 /// Opt-in generic margin model. Mutually exclusive with
568 /// initial_margin_fraction, which remains the one-scalar spelling. Folded
569 /// into the continuation digest only when it is present, so a spec that
570 /// declares none keeps its pre-margin-model identity byte for byte.
571 std::optional<NativeMarginModel> margin;
572 /// Opt-in generic account risk limits. Absent is the whole default
573 /// surface: nothing is measured, no risk event is appended, and the
574 /// continuation digest is the pre-risk one. Folded into it only when the
575 /// block is present, exactly as `margin` is.
576 std::optional<NativeRiskLimits> risk;
578 /// Report a position still open at run end as a mark-to-market closed row
579 /// at the last close. KernelRecorded only, and reporting only: the live
580 /// book, the realized sums and every hash are left exactly as the run left
581 /// them. Inert under HostRecorded, whose host owns the whole report series.
583 /// Calculation timing. BarClose is the established cadence and the whole
584 /// default surface; the other triggers only add calculations, never move
585 /// or remove one. max_recalculations_per_point bounds the fill cascade at
586 /// one matching point: further executions at that point are still applied
587 /// and still delivered to on_native_applied, they just stop driving a new
588 /// calculation. Zero is a legal bound and means "deliver, never
589 /// recalculate". All three fold into the continuation hash only once the
590 /// trigger or the open-bar view is non-default, so a spec that leaves the
591 /// cadence alone keeps the continuation identity it had before these
592 /// fields existed.
597 /// Declared higher-timeframe series. Empty is the whole default surface:
598 /// no evaluator is registered, no feed is prepared, and the run spec's
599 /// continuation digest is the pre-subscription one.
600 std::vector<NativeTimeframeSubscription> subscriptions;
601 /// Opt-in auxiliary finer feed. Absent is the whole default surface:
602 /// nothing is stored, nothing is routed, and the continuation digest is the
603 /// pre-feed one. Folded into it only when the block is present, exactly as
604 /// `margin` and `risk` are.
605 std::optional<NativeAuxiliaryFeed> auxiliary_feed;
606};
607
608/// Which field a validation refused, in deterministic first-error order. It is
609/// what lets a host report "which field" instead of "invalid"; every enumerator
610/// names a NativeRunSpec member or one of its nested blocks.
635
636/// Why a field was refused. Read it beside NativeRunSpecValidation::field: the
637/// error says what is wrong and the field says where. Each feature's own suite
638/// pins the refusals of the fields it owns — the subscription and auxiliary-feed
639/// rows in tests/test_native_auxiliary_feed.cpp, the margin rows in
640/// tests/test_native_margin_model.cpp, the risk rows in
641/// tests/test_native_risk_limits.cpp.
642enum class NativeRunSpecError : std::uint8_t {
672 // A declared higher-timeframe series whose literal does not parse, or
673 // whose pairing with input_tf is not one script_tf would accept.
675 // A declared series strictly finer than input_tf. Lower-timeframe arrays
676 // are a separate contract; this is never silently promoted.
678 // Two declared series of the same period whose `authoritative_bars`
679 // differ. Same-period series are otherwise independent instances, but the
680 // feed store is keyed by the timeframe's duration, so they could not own
681 // their own conflicting bars. Declaring the same bars twice, or leaving
682 // one (or both) empty, is accepted.
684 // Authoritative bars that are not strictly increasing in time.
686 // Subscriptions declared with no detected input timeframe to pair with.
688 // Both the generic margin model and the one-scalar initial-margin gate
689 // were set. They are two spellings of the same admission authority.
693 // A margin equity basis / liquidation level base outside its enumeration.
696 // A calculation trigger / open-bar view outside its enumeration.
699 // A risk day basis / breach action outside its enumeration.
702 // A declared risk count of zero. A limit of "no losing day at all" or
703 // "no fill at all" is a blocked run, never a threshold, so it is named
704 // rather than silently enforced.
706 // A margin-model side that declares neither an opening requirement
707 // (`initial_*` is zero) nor a liquidation one (`maintenance_*` is unset).
708 // The two are separate broker functions and a side may waive either, but
709 // waiving both states nothing at all, so it is named rather than read as
710 // "unlimited leverage, never liquidated".
712 // An auxiliary feed whose literal does not parse, or whose pairing under
713 // input_tf is not one an input would accept under a script_tf.
715 // An auxiliary feed that is not strictly finer than input_tf. The input
716 // already carries its own timeframe and everything coarser.
718 // Auxiliary bars that are not strictly increasing in time, or that carry
719 // a non-finite price or volume.
722 // An auxiliary feed declared with no detected input timeframe to be
723 // finer than.
725 // A series source outside its enumeration.
727 // A series built from the auxiliary feed in a spec that declares none.
729 // A series built from the auxiliary feed and strictly finer than it.
731};
732
733/// Allocation-free facts suitable for the host's durable failure variant.
734/// Render text at the presentation boundary, never store exception.what().
738
739 constexpr bool ok() const noexcept { return error == NativeRunSpecError::None; }
740 constexpr explicit operator bool() const noexcept { return ok(); }
741};
742
743/// Complete validation, with deterministic first-error field order. Every
744/// string is semantic UTF-8 without embedded NUL (all cross C-string v1).
745/// Required: identity key, tickerid, scheduling timezone, and both timeframe
746/// literals unless timeframe_undetected is explicitly set.
747/// Empty chart timezone is preserved as optional observation metadata.
748/// Calendar parsing/compatibility remain in native_calendar. Batch monthly
749/// pairings are accepted here; stream-only restrictions belong to begin.
750/// Calendar parsing may allocate. Allocation/other dependency exceptions are
751/// converted into typed failure facts; the supplied spec is never changed.
753
754/// Validate the WHOLE value first, then canonicalize its admitted numeric
755/// negative zero (fee_value) to positive zero. Failure preserves every input
756/// bit/string/optional. Positive-only fields cannot admit either zero sign;
757/// price_tick admits and preserves both zero signs, and absent optionals have no
758/// payload. Literal strings/numbers are never otherwise rewritten. There is no
759/// second validated/live configuration wrapper.
760/// Host usage: copy input into a candidate, normalize candidate, then stage
761/// that same spec atomically; own copy-allocation/lifecycle failure handling.
763
764/// Exactly the part of validate_native_run_spec that judges declared
765/// higher-timeframe series, against a stated input timeframe: the pairing rule
766/// (as script_tf pairs, never strictly finer), the literals, the order of any
767/// authoritative bars, the one conflicting-feed refusal, and the
768/// undetected-timeframe rule. A host that declares its series at begin
769/// (NativeStrategyHost::declare_timeframe_subscriptions) is judged by this same
770/// function, so a list accepted there is one configure_native would also have
771/// accepted. Calendar parsing may allocate; failures are converted into typed
772/// facts and nothing is changed.
774 const std::vector<NativeTimeframeSubscription>& subscriptions,
775 const std::string& input_tf, bool timeframe_undetected) noexcept;
776
777/// The same judgement for a run that declares an auxiliary feed: the feed is
778/// judged first (validate_native_auxiliary_feed), then every series, a series
779/// built from the feed pairing with the FEED's timeframe. The three-argument
780/// form above is this one with no feed, where such a series is refused as
781/// SubscriptionWithoutAuxiliaryFeed.
783 const std::vector<NativeTimeframeSubscription>& subscriptions,
784 const std::string& input_tf, bool timeframe_undetected,
785 const std::optional<NativeAuxiliaryFeed>& auxiliary_feed) noexcept;
786
787/// Exactly the part of validate_native_run_spec that judges the auxiliary
788/// feed, against a stated input timeframe: the literal, the strictly-finer
789/// pairing under the input, the order and structure of its bars, and the
790/// undetected-timeframe rule. An absent feed is always valid. A host that
791/// declares its feed at begin (NativeStrategyHost::declare_auxiliary_feed) is
792/// judged by this same function.
794 const std::optional<NativeAuxiliaryFeed>& auxiliary_feed,
795 const std::string& input_tf, bool timeframe_undetected) noexcept;
796
797/// Exact FNV-1a content digest for a retained intrabar path. It includes the
798/// mode, lower bars in caller order when present, and every sampling parameter,
799/// so continuation identity cannot silently reuse a path from another begin.
800std::uint64_t native_intrabar_path_digest(const IntrabarPath& path) noexcept;
801
802/// Exact FNV-1a content digest for the declared higher-timeframe series. It
803/// includes each subscription's timeframe literal, publication modes and
804/// authoritative bars in caller order, so a continuation cannot silently reuse
805/// another begin's series. Callers fold it only when `subscriptions` is
806/// non-empty, keeping the default spec's continuation identity unchanged, and
807/// `gaps` folds only where a series set it, keeping every series declared
808/// before that field existed at the digest it already had. `source` folds the
809/// same way: only where a series left the input.
811 const std::vector<NativeTimeframeSubscription>& subscriptions) noexcept;
812
813/// Exact FNV-1a content digest for the declared auxiliary feed: its timeframe
814/// literal and every bar in caller order, so a continuation cannot silently
815/// reuse another begin's feed. Callers fold it only when `auxiliary_feed` is
816/// present, keeping the default spec's continuation identity unchanged.
817std::uint64_t native_auxiliary_feed_digest(const NativeAuxiliaryFeed& feed) noexcept;
818
819/// Machine-independent digest of a run spec: exactly the fields the consumer
820/// folds into the continuation identity for the spec, and nothing else — no
821/// timezone resources, no session identity beyond the spec's own. Two specs
822/// with equal digests drive identical continuation identities on every machine
823/// with the same tz resources.
824///
825/// It is the consumer's own spec fold (`hash_spec`) over a freshly seeded
826/// accumulator: the same FNV-1a offset basis `continuation_hash()` starts from,
827/// with nothing folded before the spec (no semantic-version markers, no state)
828/// and the relative-generation base set to `spec.identity.run_number`, exactly
829/// as the consumer seeds it for the run this spec describes. The folded
830/// generation distance is therefore zero and the digest is a property of the
831/// spec value alone. It is NOT a continuation hash and never comparable with
832/// one: a raw continuation hash also folds the resolved timezone resources
833/// (zoneinfo root and zone file paths), which differ per machine, so only this
834/// digest is portable enough to pin as a constant.
835std::uint64_t native_run_spec_digest(const NativeRunSpec& spec) noexcept;
836
837/// Exact FNV-1a content digest for the generic margin model. It includes every
838/// per-side fraction, the liquidation policy and its parameters, so two runs
839/// that differ only in their margin model cannot share a continuation identity.
840/// Callers fold it only when `margin` is present, keeping the default spec's
841/// continuation identity unchanged.
842std::uint64_t native_margin_model_digest(const NativeMarginModel& margin) noexcept;
843
844/// Exact FNV-1a content digest for the generic risk limits. It includes every
845/// threshold, its percent flag, the day basis and the breach action, so two
846/// runs that differ only in their risk limits cannot share a continuation
847/// identity. Callers fold it only when `risk` is present, keeping the default
848/// spec's continuation identity unchanged.
849std::uint64_t native_risk_limits_digest(const NativeRiskLimits& risk) noexcept;
850
851static_assert(std::is_trivially_copyable_v<NativeRunSpecValidation>);
852static_assert(std::is_nothrow_move_constructible_v<NativeRunSpec>);
853static_assert(std::is_nothrow_move_assignable_v<NativeRunSpec>);
854
855} // inline namespace native_run_spec_v3
856} // namespace pineforge
NativeRunSpecError
Why a field was refused.
NativeLiquidationSizing
Which units a kernel-issued liquidation reduces (L4).
NativeRunSpecValidation validate_native_timeframe_subscriptions(const std::vector< NativeTimeframeSubscription > &subscriptions, const std::string &input_tf, bool timeframe_undetected) noexcept
Exactly the part of validate_native_run_spec that judges declared higher-timeframe series,...
NativeCalculationTrigger
When the kernel asks the host to calculate.
NativeRiskDay
Which day a risk limit's "day" is.
NativeSeriesSource
One declared higher-timeframe series of the run's own symbol, the native equivalent of request....
NativeRunSpecValidation validate_native_auxiliary_feed(const std::optional< NativeAuxiliaryFeed > &auxiliary_feed, const std::string &input_tf, bool timeframe_undetected) noexcept
Exactly the part of validate_native_run_spec that judges the auxiliary feed, against a stated input t...
constexpr bool native_feed_tolerance_enabled(NativeFeedTolerance enabled, NativeFeedTolerance requested) noexcept
Whether one NativeFeedTolerance bit is set in a run's mask.
NativeRunSpecValidation normalize_native_run_spec(NativeRunSpec &spec) noexcept
Validate the WHOLE value first, then canonicalize its admitted numeric negative zero (fee_value) to p...
NativeReportPolicy
Who records the per-script-bar report series.
NativeCloseExecution
When a request born at a script calculation may first match.
NativeLiquidationLevelBase
Which base the liquidation level is solved from.
constexpr bool native_legacy_tolerance_enabled(NativeFeedTolerance enabled, NativeFeedTolerance requested) noexcept
Deprecated spelling of native_feed_tolerance_enabled.
NativeOpenBarView
What a bar-open callback is handed.
std::uint64_t native_margin_model_digest(const NativeMarginModel &margin) noexcept
Exact FNV-1a content digest for the generic margin model.
NativePriceGrid
A generic instrument price grid.
NativePathOrder
Generic ordering for a modeled OHLC path.
NativeSlotLabelPolicy
Native hosts normally require every confirmed bar to name a canonical input slot.
@ LegacyTolerant
Deprecated spelling of FeedTolerant; identical value, kept so existing hosts and the source adapter c...
NativeOpenDirections
Which opening directions the run admits at all.
NativeAbortReporting
Abort presentation is a run-level policy rather than an exception-path convention.
NativeFeedTolerance
Explicit, opt-in admission exceptions for a tolerated input-feed shape.
@ WarmupNonNegativeOHLC
Source-compatible stream warmups admit finite, non-negative interim OHLC values.
@ BatchStructuralBars
Match the batch structural check: finite OHLC values need not be positive, and NaN volume means unava...
std::uint64_t native_risk_limits_digest(const NativeRiskLimits &risk) noexcept
Exact FNV-1a content digest for the generic risk limits.
std::uint64_t native_run_spec_digest(const NativeRunSpec &spec) noexcept
Machine-independent digest of a run spec: exactly the fields the consumer folds into the continuation...
NativeFeedTolerance NativeLegacyTolerance
Deprecated spelling of the tolerance type.
std::uint64_t native_intrabar_path_digest(const IntrabarPath &path) noexcept
Exact FNV-1a content digest for a retained intrabar path.
NativeFeeKind
Encodings coincide with the versioned native-v1 C transport.
std::uint64_t native_timeframe_subscriptions_digest(const std::vector< NativeTimeframeSubscription > &subscriptions) noexcept
Exact FNV-1a content digest for the declared higher-timeframe series.
NativeLiquidationCheck
When the kernel tests the maintenance requirement.
NativeRunSpecField
Which field a validation refused, in deterministic first-error order.
std::uint64_t native_auxiliary_feed_digest(const NativeAuxiliaryFeed &feed) noexcept
Exact FNV-1a content digest for the declared auxiliary feed: its timeframe literal and every bar in c...
NativeGridRounding
HalfUp is the nearest tick with ties away from zero.
NativeMarginEquityBasis
Which equity the maintenance requirement is tested against.
NativeRunSpecValidation validate_native_run_spec(const NativeRunSpec &spec) noexcept
Complete validation, with deterministic first-error field order.
MagnifierDistribution
Definition magnifier.hpp:7
A RETAINED finer feed: the host's own lower-timeframe bars, the literal they are at,...
No intrabar path: the confirmed OHLC waypoints are the whole modeled walk, and on_native_sub_bar neve...
A synthesized path has no retained lower feed.
An owned lower-timeframe execution path.
const lower_tf * lower() const noexcept
The retained finer feed, or nullptr when this is not the lower_tf alternative.
@ ContinuousSegments
Native hosts retain continuous matching between generated samples unless they explicitly request poin...
bool is_none() const noexcept
Whether this run declares no intrabar path at all.
std::variant< none, lower_tf, synthesized > value_type
The three alternatives as one value.
synthesized * synthesized_path() noexcept
The mutable synthesized sampling policy, or nullptr.
lower_tf * lower() noexcept
The mutable retained finer feed, or nullptr; a staged spec's feed is copied by configure_native,...
const synthesized * synthesized_path() const noexcept
The synthesized sampling policy, or nullptr when this is not that alternative.
An auxiliary feed of the run's OWN symbol at a timeframe strictly finer than the input: the bars the ...
A generic per-side broker margin model (L4).
Generic account risk limits (L9), entirely opt-in: a spec that leaves NativeRunSpec::risk unset evalu...
std::optional< NativeLossLimit > max_intraday_loss
std::optional< std::uint32_t > max_consecutive_loss_days
std::optional< NativeLossLimit > max_drawdown
Allocation-free facts suitable for the host's durable failure variant.
One complete setup value, staged/copied by NativeStrategyHost before it is applied at begin.
NativeSlotLabelPolicy slot_label_policy
Strict native hosts retain the canonical slot-label rule.
NativeCalculationTrigger calculation
Calculation timing.
std::vector< NativeTimeframeSubscription > subscriptions
Declared higher-timeframe series.
std::optional< NativeRiskLimits > risk
Opt-in generic account risk limits.
std::optional< NativeMarginModel > margin
Opt-in generic margin model.
std::optional< std::uint64_t > max_open_lots
bool timeframe_undetected
A public begin with fewer than two bars may not establish a timeframe.
NativePriceGrid price_grid
Opt-in instrument grid.
std::optional< NativeAuxiliaryFeed > auxiliary_feed
Opt-in auxiliary finer feed.
bool report_open_position_at_end
Report a position still open at run end as a mark-to-market closed row at the last close.