PineForge unreleased
Deterministic PineScript v6 backtest runtime — C ABI reference
Loading...
Searching...
No Matches
native_module.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file native_module.hpp
4/// One-line C ABI export for a hand-written `NativeStrategyHost`.
5///
6/// A loadable PineForge strategy is a shared object that exports a small,
7/// fixed set of `extern "C"` entry points (`strategy_create`, `run_backtest`,
8/// …) declared in `<pineforge/pineforge.h>`. Codegen emits them for a
9/// PineScript strategy; a native C++ host had to hand-write them. This header
10/// generates exactly that surface from one macro:
11///
12/// @code
13/// #include <pineforge/native_module.hpp>
14///
15/// class MyHost : public pineforge::NativeStrategyHost {
16/// void on_native_bar(const pineforge::Bar&,
17/// const pineforge::NativeDecisionContext&) override { … }
18/// };
19///
20/// PINEFORGE_EXPORT_NATIVE_STRATEGY(MyHost);
21/// @endcode
22///
23/// The ordinary entry points are declared in `<pineforge/pineforge.h>`.
24/// The optional metrics-only capability below belongs to each module, not
25/// the static runtime. The remaining runtime exports
26/// (`strategy_configure_native_v1`, the `strategy_stream_*` family,
27/// `strategy_get_last_error`, …) come from the engine's own `c_abi.cpp`; the
28/// generated `strategy_create` references `pf_abi_version()` so the linker
29/// keeps that object when the module links `libpineforge.a` statically.
30///
31/// The host class must be a `NativeStrategyHost` subclass and must not be
32/// `final`: the module wraps it in one derived class, which is what makes the
33/// engine's protected presentation-error string (read back through
34/// `strategy_get_last_error`) writable from the C boundary.
35
37#include <pineforge/pineforge.h>
39
40#include <atomic>
41#include <cstddef>
42#include <cstdint>
43#include <cstring>
44#include <exception>
45#include <limits>
46#include <optional>
47#include <string>
48#include <type_traits>
49#include <vector>
50
51/// Optional per-module probe. Call it, rather than testing symbol presence:
52/// Ok (0) certifies this module's metrics-only adapter; NotImplemented (-1)
53/// keeps the actual curve. Callers must resolve LegacyRerun before publishing
54/// any report, using a fresh strategy with the same settings and frozen bars.
55extern "C" PF_API std::int32_t strategy_metrics_only_capability_v1(void) noexcept;
56
57namespace pineforge {
58namespace native_module {
59
60/// Specialize for the EXACT host passed to PINEFORGE_EXPORT_NATIVE_STRATEGY.
61/// Return its versioned declaration, or nullopt when it cannot certify itself.
62/// No specialization means no certification; a subclass does not inherit one.
63///
64/// In addition to the declaration's presenter/marks/scheduler facts, opting in
65/// certifies one final metric point per input bar, with no extra anchor, on the
66/// admitted passthrough schedule, and deterministic whole-run replay on a fresh
67/// instance with identical settings and frozen input. Source hosts with private
68/// tail/probe modes need their own adapter: this wrapper certifies only the
69/// native defaults of those setters. A modified presenter or mark implementation
70/// needs a new, truthful specialization, never a caller-provided declaration.
71template <typename Host>
73 static std::optional<MetricsPlainDeclarationV1> declaration() {
74 return std::nullopt;
75 }
76};
77
78template <typename Host>
80 try {
81 const auto declaration = MetricsPlainHostV1<Host>::declaration();
82 if (!declaration) return false;
83 const auto& d = *declaration;
84 if (d.struct_size != sizeof(d) || d.version != 1
85 || d.builtin_presenter != 1 || d.builtin_marks != 1
86 || d.builtin_scheduler != 1 || d.security_sites != 0
87 || d.lower_tf_sites != 0 || d.magnifier_declared != 0
88 || d.calculation_trigger > 1 || d.calc_on_every_history_tick != 0)
89 return false;
90 out = d;
91 return true;
92 } catch (...) {
93 return false;
94 }
95}
96
97template <typename Host>
98std::int32_t metrics_only_capability_v1() noexcept {
99 MetricsPlainDeclarationV1 declaration;
100 return static_cast<std::int32_t>(metrics_plain_declaration_v1<Host>(declaration)
103}
104
105// Report-only invocation identities. They never enter broker decisions or
106// continuation hashes. Exhaustion disables the optimization instead of wrapping
107// an attempt identity. Fresh handles used for recovery receive fresh attempts.
108inline std::uint64_t next_metrics_attempt_v1() noexcept {
109 static std::atomic<std::uint64_t> last{0};
110 auto previous = last.load(std::memory_order_relaxed);
111 while (previous != std::numeric_limits<std::uint64_t>::max()) {
112 if (last.compare_exchange_weak(previous, previous + 1,
113 std::memory_order_relaxed, std::memory_order_relaxed))
114 return previous + 1;
115 }
116 return 0;
117}
118
119// Hash scalar words explicitly: neither Bar padding nor host byte order is an
120// input. NaN payloads and signed zero remain distinct input generations.
122 std::uint64_t value_ = 14695981039346656037ULL;
123public:
124 void word(std::uint64_t value) noexcept {
125 for (unsigned i = 0; i < 8; ++i) {
126 value_ ^= value & 0xffu;
127 value_ *= 1099511628211ULL;
128 value >>= 8;
129 }
130 }
131 void number(double value) noexcept {
132 std::uint64_t bits;
133 static_assert(sizeof(bits) == sizeof(value), "binary64 input binding");
134 std::memcpy(&bits, &value, sizeof(bits));
135 word(bits);
136 }
137 std::uint64_t value() const noexcept { return value_; }
138};
139
140// Bars are borrowed through the C mirror and reports are memcpy'd, so
141// their layouts must agree exactly.
142static_assert(sizeof(pf_bar_t) == sizeof(Bar),
143 "pf_bar_t / pineforge::Bar size mismatch");
144static_assert(offsetof(pf_bar_t, open) == offsetof(Bar, open),
145 "pf_bar_t::open offset mismatch");
146static_assert(offsetof(pf_bar_t, high) == offsetof(Bar, high),
147 "pf_bar_t::high offset mismatch");
148static_assert(offsetof(pf_bar_t, low) == offsetof(Bar, low),
149 "pf_bar_t::low offset mismatch");
150static_assert(offsetof(pf_bar_t, close) == offsetof(Bar, close),
151 "pf_bar_t::close offset mismatch");
152static_assert(offsetof(pf_bar_t, volume) == offsetof(Bar, volume),
153 "pf_bar_t::volume offset mismatch");
154static_assert(offsetof(pf_bar_t, timestamp) == offsetof(Bar, timestamp),
155 "pf_bar_t::timestamp offset mismatch");
156
157static_assert(sizeof(pf_report_t) == sizeof(ReportC),
158 "pf_report_t / pineforge::ReportC size mismatch");
159static_assert(offsetof(pf_report_t, total_trades) == offsetof(ReportC, total_trades),
160 "pf_report_t::total_trades offset mismatch");
161static_assert(offsetof(pf_report_t, trades) == offsetof(ReportC, trades),
162 "pf_report_t::trades offset mismatch");
163static_assert(offsetof(pf_report_t, net_profit) == offsetof(ReportC, net_profit),
164 "pf_report_t::net_profit offset mismatch");
165static_assert(offsetof(pf_report_t, security_diag) == offsetof(ReportC, security_diag),
166 "pf_report_t::security_diag offset mismatch");
167static_assert(offsetof(pf_report_t, metrics) == offsetof(ReportC, metrics),
168 "pf_report_t::metrics offset mismatch");
169static_assert(offsetof(pf_report_t, equity_curve) == offsetof(ReportC, equity_curve),
170 "pf_report_t::equity_curve offset mismatch");
171static_assert(offsetof(pf_report_t, equity_curve_len) == offsetof(ReportC, equity_curve_len),
172 "pf_report_t::equity_curve_len offset mismatch");
173static_assert(offsetof(pf_report_t, broker_state_hash) == offsetof(ReportC, broker_state_hash),
174 "pf_report_t::broker_state_hash offset mismatch");
175
176/// The one class the macro instantiates: the host plus the module boundary.
177/// Deriving is deliberate — `last_error_` is protected engine state, written
178/// here only through note_run_failure (run_failure.hpp), with its code.
179template <typename Host>
180class Module final : public Host {
181 static_assert(std::is_base_of<NativeStrategyHost, Host>::value,
182 "PINEFORGE_EXPORT_NATIVE_STRATEGY requires a NativeStrategyHost subclass");
183
184public:
185 /// This is a speculative report optimization only. Failure to establish
186 /// any proof leaves the existing run and its refusal ordering in charge.
187 void prepare_metrics_only_v1(const Bar* bars, int count,
188 const char* input_tf, const char* script_tf,
189 bool with_timeframes) noexcept {
190 using namespace metrics;
191 if (this->metrics_streaming_.configuration.streaming_sharpe
192 != StreamingSharpeV1::On)
193 return;
194 // Existing/default requests retain their sequence and do not acquire
195 // an admission or recovery proof just by using this wrapper.
196 const auto needed = this->metrics_streaming_.configuration.metrics_needed;
197 if (needed & (kMetricsNeedReturnsV1 | kMetricsNeedCurveV1)) return;
198 MetricsPlainDeclarationV1 declaration;
199 if (!metrics_plain_declaration_v1<Host>(declaration)) return;
200 try {
201 const auto state = this->native_state();
202 if (state.kind != NativeLifecycleKind::Ready || !state.spec
203 || count < 0 || (count > 0 && !bars))
204 return;
205 const auto& spec = *state.spec;
206 // Empty timeframe arguments are forwarded unchanged by run_batch.
207 // They cannot certify a different staged view before native begin
208 // has resolved/refused them.
209 const bool bound_timeframes = !with_timeframes
210 || (input_tf && script_tf && spec.input_tf == input_tf
211 && spec.script_tf == script_tf);
212 MetricsExtentPlanV1 plan;
213 plan.present = 1;
214 plan.extent.count = count;
215 plan.extent.empty = count == 0;
216 if (count > 0) {
217 plan.extent.first_time_ms = bars[0].timestamp;
218 plan.extent.last_time_ms = bars[count - 1].timestamp;
219 }
220 plan.attempt = next_metrics_attempt_v1();
221 if (plan.attempt == 0) return;
222 // The caller freezes these borrowed bars through synchronous run
223 // and recovery. Bind their actual words, not their storage address.
224 MetricsBindingHashV1 generation;
225 generation.word(static_cast<std::uint64_t>(count));
226 for (int i = 0; i < count; ++i) {
227 const auto& bar = bars[i];
228 generation.word(static_cast<std::uint64_t>(bar.timestamp));
229 generation.number(bar.open);
230 generation.number(bar.high);
231 generation.number(bar.low);
232 generation.number(bar.close);
233 generation.number(bar.volume);
234 }
235 plan.input_generation = generation.value();
236 plan.spec_identity = native_run_spec_digest(spec);
237 // Bind only the planned input extent. Final equity is a receipt
238 // observation produced after execution and never part of this hash.
240 view.word(plan.input_generation);
241 view.word(plan.spec_identity);
242 view.word(static_cast<std::uint64_t>(plan.extent.count));
243 view.word(static_cast<std::uint64_t>(plan.extent.first_time_ms));
244 view.word(static_cast<std::uint64_t>(plan.extent.last_time_ms));
245 view.word(plan.extent.empty);
246 plan.admitted_view = view.value();
247
249 inputs.declaration = &declaration;
250 inputs.module_contract_bound = declaration.calculation_trigger
251 == static_cast<std::uint32_t>(spec.calculation);
252 inputs.historical_batch = 1;
253 inputs.equal_resolved_timeframes = bound_timeframes
254 && !spec.input_tf.empty() && spec.input_tf == spec.script_tf;
255 inputs.timeframe_undetected = spec.timeframe_undetected;
256 inputs.intrabar_none = spec.intrabar.is_none();
257 inputs.builtin_host_marks =
258 spec.report_policy == NativeReportPolicy::KernelRecorded
259 || spec.report_policy == NativeReportPolicy::KernelRecordedAtHostMarks;
261 spec.slot_label_policy == NativeSlotLabelPolicy::FeedTolerant
262 && spec.subscriptions.empty() && !spec.auxiliary_feed
263 && spec.instrument_feeds.empty();
264 // An override may keep private state this wrapper cannot inspect.
265 // Unknown is PLAIN=no, even if a module supplied a declaration.
266 const bool native_tails = this->template native_tail_defaults<Host>(0);
267 inputs.realtime_tail = native_tails ? 0u : 1u;
268 inputs.probe_suppress_tail_logic = native_tails ? 0u : 1u;
269 inputs.calculation_trigger = static_cast<std::uint32_t>(spec.calculation);
270 inputs.final_admitted_view_bound = bound_timeframes;
271 // Selection is owned by native begin, which rechecks pending AND
272 // admitted selection after all legacy refusals, before consuming
273 // this candidate proof. No selected run can use the input extent.
274 MetricsExtentReceiptV1 receipt;
275 if (this->admit_metrics_plain_v1(inputs, plan, &receipt)
276 != MetricsStreamingStatusV1::Ok
277 || receipt.plain_subset != 1 || receipt.failed_plain_inputs != 0)
278 return;
279 MetricsStreamingConfigV1 config;
280 config.streaming_sharpe = StreamingSharpeV1::On;
281 config.metrics_needed = needed;
282 config.attempt = plan.attempt;
283 config.input_generation = plan.input_generation;
284 config.admitted_view = plan.admitted_view;
285 config.spec_identity = plan.spec_identity;
286 config.deterministic_replay = 1; // the exact-host certification above
287 config.curve_available = 0;
288 if (this->prepare_metrics_streaming_v1(config, plan)
289 == MetricsStreamingStatusV1::Ok)
290 return;
291 } catch (...) {
292 // Admission is optional, including allocation in a module's hook.
293 // The ordinary run still owns every execution failure.
294 }
295 this->metrics_streaming_.pending_plain = false;
296 this->metrics_streaming_.prepared = false;
297 this->metrics_streaming_.pending_plan = {};
298 this->metrics_streaming_.pending_failed_plain_inputs = 0;
299 }
300
301 /// Presentation text for `strategy_get_last_error`, written with its run
302 /// failure code (`strategy_get_last_error_code`) so the two never
303 /// disagree. Never throws: it runs on the C boundary, often from a catch
304 /// handler.
305 void note_error(const char* text, const RunFailureValue& value) noexcept {
306 try {
307 note_run_failure(*this, std::string(text ? text : ""), value);
308 } catch (...) {
309 }
310 }
311 /// The text alone, as before run-failure codes: engine_unclassified_error.
312 void note_error(const char* text) noexcept {
314 }
315
316private:
317 // An overload or inaccessible setter is unknown, rather than a new compile
318 // requirement on arbitrary uncertified hosts using the existing macro.
319 template <typename H>
320 static auto native_tail_defaults(int) noexcept
321 -> decltype(&H::set_realtime_tail, &H::set_probe_suppress_tail_logic, bool{}) {
322 return std::is_same<decltype(&H::set_realtime_tail),
323 decltype(&BacktestEngine::set_realtime_tail)>::value
324 && std::is_same<decltype(&H::set_probe_suppress_tail_logic),
325 decltype(&BacktestEngine::set_probe_suppress_tail_logic)>::value;
326 }
327 template <typename H>
328 static bool native_tail_defaults(...) noexcept { return false; }
329};
330
332 return static_cast<BacktestEngine*>(strategy);
333}
334
335template <typename Host>
337 return strategy ? dynamic_cast<Module<Host>*>(as_engine(strategy)) : nullptr;
338}
339
340template <typename Host>
341void note_error(pf_strategy_t strategy, const char* text, const RunFailureValue& value) {
342 if (auto* module = as_module<Host>(strategy)) module->note_error(text, value);
343}
344
345/// The code of an exception no coded site raised: engine_unclassified_error.
349
350/// The text alone, as before run-failure codes: engine_unclassified_error.
351template <typename Host>
355
356/// Runs `fn` and converts any escaping exception into presentation text and
357/// its code. C callers have no exception channel, so nothing may propagate
358/// past here.
359template <typename Host, typename Fn>
361 if (!strategy) return;
362 try {
363 fn();
364 } catch (const std::exception& error) {
365 note_error<Host>(strategy, error.what(), classify_run_failure(error));
366 } catch (...) {
367 note_error<Host>(strategy, "native host refuses source mutation",
369 }
370}
371
372inline Bar copy_bar(const pf_bar_t& in) {
373 Bar out;
374 out.open = in.open;
375 out.high = in.high;
376 out.low = in.low;
377 out.close = in.close;
378 out.volume = in.volume;
379 out.timestamp = in.timestamp;
380 return out;
381}
382
383/// A native host owns its own intrabar path through `NativeRunSpec::intrabar`,
384/// so the C magnifier arguments have no meaning here. Only the defaults are
385/// accepted; anything else is refused before the run starts.
386inline bool magnifier_unsupported(int bar_magnifier, int magnifier_samples,
387 pf_magnifier_distribution_t distribution) {
388 return bar_magnifier != 0 || magnifier_samples != 4
389 || distribution != PF_MAGNIFIER_ENDPOINTS;
390}
391
392/// Fills a local report first: the caller's struct is written only once the
393/// engine has produced a complete, owned value.
394inline void publish_report(BacktestEngine* engine, pf_report_t* out) {
395 if (!engine || !out) return;
396 ReportC local{};
397 try {
398 engine->fill_report(&local);
399 std::memcpy(out, &local, sizeof(local));
400 } catch (...) {
401 BacktestEngine::free_report(&local);
402 throw;
403 }
404}
405
406template <typename Host>
408 try {
409 // Also the link-time anchor: referencing one PF_API symbol pulls the
410 // engine's C ABI object (and with it every other runtime export) out
411 // of libpineforge.a.
412 (void)pf_abi_version();
413 BacktestEngine* engine = new Module<Host>();
414 return static_cast<pf_strategy_t>(engine);
415 } catch (...) {
416 return nullptr;
417 }
418}
419
421 delete as_engine(strategy);
422}
423
424template <typename Host>
425void set_input(pf_strategy_t strategy, const char* key, const char* value) {
427 as_engine(strategy)->set_input(key ? key : "", value ? value : "");
428 });
429}
430
431template <typename Host>
432void set_override(pf_strategy_t strategy, const char* key, const char* value) {
434 // No BacktestEngine override mutator exists. Forward to the public
435 // guarded source-configuration entry; a host that refuses source
436 // mutation latches before inputs_ is written.
437 as_engine(strategy)->set_input(key ? key : "strategy_set_override",
438 value ? value : "");
439 });
440}
441
442template <typename Host>
448
449template <typename Host>
451 const char* input_tf, const char* script_tf, bool with_timeframes,
452 pf_report_t* out) {
453 if (!strategy) return;
454 auto* engine = as_engine(strategy);
455 try {
456 const Bar* source = reinterpret_cast<const Bar*>(bars);
457 // The size and every field offset are pinned above, matching the
458 // generated strategy wrapper's borrowed Bar array. The engine reads
459 // these bars only during run(); making a temporary vector here would
460 // charge another complete chart array to every native-module call.
461 // Keep malformed pointer/count pairs intact so the engine's common
462 // begin preflight still owns their diagnostic and lifecycle result.
463 // The caller keeps this array alive through the synchronous run.
464 // Reports own their arrays separately, as publish_report requires.
465 if (auto* module = as_module<Host>(strategy))
466 module->prepare_metrics_only_v1(source, count, input_tf, script_tf,
467 with_timeframes);
468 if (with_timeframes) {
469 engine->run(source, count, input_tf ? input_tf : "",
470 script_tf ? script_tf : "");
471 } else {
472 engine->run(source, count);
473 }
474 publish_report(engine, out);
475 } catch (const std::exception& error) {
476 note_error<Host>(strategy, error.what(), classify_run_failure(error));
477 } catch (...) {
478 note_error<Host>(strategy, "native module C batch failed", unclassified_failure());
479 }
480}
481
482template <typename Host>
484 run_batch<Host>(strategy, bars, count, nullptr, nullptr, false, out);
485}
486
487template <typename Host>
489 const char* input_tf, const char* script_tf,
490 int bar_magnifier, int magnifier_samples,
491 pf_magnifier_distribution_t magnifier_distribution,
492 pf_report_t* out) {
493 if (!strategy) return;
494 if (magnifier_unsupported(bar_magnifier, magnifier_samples, magnifier_distribution)) {
495 // Wrapper preflight only: the output report is left untouched and the
496 // host stays Ready, so the caller may retry with supported arguments.
497 try {
498 note_error<Host>(strategy, "native module refuses unsupported magnifier arguments",
500 {{"option", "magnifier"}}));
501 } catch (...) {
502 }
503 return;
504 }
505 run_batch<Host>(strategy, bars, count, input_tf, script_tf, true, out);
506}
507
509 if (!report) return;
510 ReportC local{};
511 std::memcpy(&local, report, sizeof(local));
512 BacktestEngine::free_report(&local);
513 std::memcpy(report, &local, sizeof(local));
514}
515
516} // namespace native_module
517} // namespace pineforge
518
519/// Define the loadable-module C ABI for one `NativeStrategyHost` subclass.
520/// Use it once, at namespace scope, in the module's translation unit.
521#define PINEFORGE_EXPORT_NATIVE_STRATEGY(Class) \
522 extern "C" { \
523 PF_API std::int32_t strategy_metrics_only_capability_v1(void) noexcept { \
524 return ::pineforge::native_module::metrics_only_capability_v1<Class>(); \
525 } \
526 PF_API pf_strategy_t strategy_create(const char*) { \
527 return ::pineforge::native_module::create<Class>(); \
528 } \
529 PF_API void strategy_free(pf_strategy_t s) { \
530 ::pineforge::native_module::destroy(s); \
531 } \
532 PF_API void strategy_set_input(pf_strategy_t s, const char* key, \
533 const char* value) { \
534 ::pineforge::native_module::set_input<Class>(s, key, value); \
535 } \
536 PF_API void strategy_set_override(pf_strategy_t s, const char* key, \
537 const char* value) { \
538 ::pineforge::native_module::set_override<Class>(s, key, value); \
539 } \
540 PF_API void strategy_set_magnifier_volume_weighted(pf_strategy_t s, int on) { \
541 ::pineforge::native_module::set_magnifier_volume_weighted<Class>(s, on); \
542 } \
543 PF_API void run_backtest(pf_strategy_t s, pf_bar_t* bars, int n, \
544 pf_report_t* out) { \
545 ::pineforge::native_module::run_backtest<Class>(s, bars, n, out); \
546 } \
547 PF_API void run_backtest_full(pf_strategy_t s, pf_bar_t* bars, int n, \
548 const char* input_tf, const char* script_tf, \
549 int bar_magnifier, int magnifier_samples, \
550 pf_magnifier_distribution_t magnifier_dist, \
551 pf_report_t* out) { \
552 ::pineforge::native_module::run_backtest_full<Class>( \
553 s, bars, n, input_tf, script_tf, bar_magnifier, magnifier_samples, \
554 magnifier_dist, out); \
555 } \
556 PF_API void report_free(pf_report_t* report) { \
557 ::pineforge::native_module::report_free(report); \
558 } \
559 } \
560 static_assert(true, "PINEFORGE_EXPORT_NATIVE_STRATEGY expects a trailing ';'")
void set_input(const std::string &key, const std::string &value)
Definition engine.hpp:2279
void word(std::uint64_t value) noexcept
The one class the macro instantiates: the host plus the module boundary.
void prepare_metrics_only_v1(const Bar *bars, int count, const char *input_tf, const char *script_tf, bool with_timeframes) noexcept
This is a speculative report optimization only.
void note_error(const char *text) noexcept
The text alone, as before run-failure codes: engine_unclassified_error.
void note_error(const char *text, const RunFailureValue &value) noexcept
Presentation text for strategy_get_last_error, written with its run failure code (strategy_get_last_e...
pf_magnifier_distribution_t
Bar-magnifier sub-bar sampling distribution.
Definition pineforge.h:128
@ PF_MAGNIFIER_ENDPOINTS
Default — exact O,H,L,C points plus uniform fill between.
Definition pineforge.h:132
int pf_abi_version(void)
std::uint64_t native_run_spec_digest(const NativeRunSpec &spec, const NativeBarViews &views)
Portable spec fold with an explicit context, sharing the existing fold and relative run base; never i...
std::uint64_t next_metrics_attempt_v1() noexcept
std::int32_t metrics_only_capability_v1() noexcept
void run_backtest_full(pf_strategy_t strategy, pf_bar_t *bars, int count, const char *input_tf, const char *script_tf, int bar_magnifier, int magnifier_samples, pf_magnifier_distribution_t magnifier_distribution, pf_report_t *out)
Bar copy_bar(const pf_bar_t &in)
BacktestEngine * as_engine(pf_strategy_t strategy)
void publish_report(BacktestEngine *engine, pf_report_t *out)
Fills a local report first: the caller's struct is written only once the engine has produced a comple...
RunFailureValue unclassified_failure() noexcept
The code of an exception no coded site raised: engine_unclassified_error.
void set_override(pf_strategy_t strategy, const char *key, const char *value)
void set_input(pf_strategy_t strategy, const char *key, const char *value)
void guarded(pf_strategy_t strategy, Fn &&fn)
Runs fn and converts any escaping exception into presentation text and its code.
void destroy(pf_strategy_t strategy)
void note_error(pf_strategy_t strategy, const char *text, const RunFailureValue &value)
void report_free(pf_report_t *report)
void run_backtest(pf_strategy_t strategy, pf_bar_t *bars, int count, pf_report_t *out)
void run_batch(pf_strategy_t strategy, pf_bar_t *bars, int count, const char *input_tf, const char *script_tf, bool with_timeframes, pf_report_t *out)
void set_magnifier_volume_weighted(pf_strategy_t strategy, int on)
Module< Host > * as_module(pf_strategy_t strategy)
bool metrics_plain_declaration_v1(MetricsPlainDeclarationV1 &out) noexcept
bool magnifier_unsupported(int bar_magnifier, int magnifier_samples, pf_magnifier_distribution_t distribution)
A native host owns its own intrabar path through NativeRunSpec::intrabar, so the C magnifier argument...
void note_run_failure(BacktestEngine &engine, std::string text, RunFailureCode code, const RunFailureArgs &args={})
RunFailureValue make_run_failure(RunFailureCode code, const RunFailureArgs &args) noexcept
Validate args against code's registry entry and canonicalize them.
RunFailureValue classify_run_failure(const std::exception &error) noexcept
The code an exception carries: a coded exception's own; else the safety net by type (std::bad_alloc -...
std::int32_t strategy_metrics_only_capability_v1(void) noexcept
Optional per-module probe.
void * pf_strategy_t
Opaque handle to a compiled strategy instance.
Definition pineforge.h:441
#define PF_API
Definition pineforge.h:83
Single OHLCV bar pushed into the engine.
Definition pineforge.h:140
double volume
Bar volume.
Definition pineforge.h:145
double high
High price.
Definition pineforge.h:142
double low
Low price.
Definition pineforge.h:143
double close
Close price.
Definition pineforge.h:144
double open
Open price.
Definition pineforge.h:141
int64_t timestamp
Bar open time, Unix milliseconds.
Definition pineforge.h:146
Backtest report filled by run_backtest / run_backtest_full.
Definition pineforge.h:366
double open
Definition bar.hpp:7
double close
Definition bar.hpp:7
double low
Definition bar.hpp:7
double volume
Definition bar.hpp:7
int64_t timestamp
Definition bar.hpp:8
double high
Definition bar.hpp:7
std::uint32_t module_contract_bound
Definition engine.hpp:349
std::uint32_t equal_resolved_timeframes
Definition engine.hpp:351
std::uint32_t probe_suppress_tail_logic
Definition engine.hpp:358
std::uint32_t feed_tolerant_passthrough
Definition engine.hpp:360
std::uint32_t final_admitted_view_bound
Definition engine.hpp:362
std::uint32_t calculation_trigger
Definition engine.hpp:361
std::uint32_t timeframe_undetected
Definition engine.hpp:352
const MetricsPlainDeclarationV1 * declaration
Definition engine.hpp:348
A validated code and its canonical arguments: the JSON object with its keys sorted and no whitespace,...
Specialize for the EXACT host passed to PINEFORGE_EXPORT_NATIVE_STRATEGY.
static std::optional< MetricsPlainDeclarationV1 > declaration()