71template <
typename Host>
73 static std::optional<MetricsPlainDeclarationV1>
declaration() {
78template <
typename Host>
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)
97template <
typename Host>
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))
122 std::uint64_t value_ = 14695981039346656037ULL;
125 for (
unsigned i = 0; i < 8; ++i) {
126 value_ ^=
value & 0xffu;
127 value_ *= 1099511628211ULL;
133 static_assert(
sizeof(bits) ==
sizeof(
value),
"binary64 input binding");
134 std::memcpy(&bits, &
value,
sizeof(bits));
137 std::uint64_t
value() const noexcept {
return value_; }
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");
158 "pf_report_t / pineforge::ReportC size mismatch");
160 "pf_report_t::total_trades offset mismatch");
162 "pf_report_t::trades offset mismatch");
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");
168 "pf_report_t::metrics offset mismatch");
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");
179template <
typename Host>
181 static_assert(std::is_base_of<NativeStrategyHost, Host>::value,
182 "PINEFORGE_EXPORT_NATIVE_STRATEGY requires a NativeStrategyHost subclass");
188 const char* input_tf,
const char* script_tf,
189 bool with_timeframes)
noexcept {
191 if (this->metrics_streaming_.configuration.streaming_sharpe
192 != StreamingSharpeV1::On)
196 const auto needed = this->metrics_streaming_.configuration.metrics_needed;
197 if (needed & (kMetricsNeedReturnsV1 | kMetricsNeedCurveV1))
return;
201 const auto state = this->native_state();
202 if (state.kind != NativeLifecycleKind::Ready || !state.spec
203 || count < 0 || (count > 0 && !bars))
205 const auto& spec = *state.spec;
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;
214 plan.extent.count = count;
215 plan.extent.empty = count == 0;
217 plan.extent.first_time_ms = bars[0].timestamp;
218 plan.extent.last_time_ms = bars[count - 1].timestamp;
221 if (plan.attempt == 0)
return;
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);
235 plan.input_generation = generation.value();
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();
251 ==
static_cast<std::uint32_t
>(spec.calculation);
254 && !spec.input_tf.empty() && spec.input_tf == spec.script_tf;
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();
266 const bool native_tails = this->
template native_tail_defaults<Host>(0);
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)
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;
287 config.curve_available = 0;
288 if (this->prepare_metrics_streaming_v1(config, plan)
289 == MetricsStreamingStatusV1::Ok)
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;
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;
327 template <
typename H>
328 static bool native_tail_defaults(...) noexcept {
return false; }
335template <
typename Host>
340template <
typename Host>
351template <
typename Host>
359template <
typename Host,
typename Fn>
364 }
catch (
const std::exception& error) {
388 return bar_magnifier != 0 || magnifier_samples != 4
395 if (!engine || !out)
return;
399 std::memcpy(out, &local,
sizeof(local));
401 BacktestEngine::free_report(&local);
406template <
typename Host>
424template <
typename Host>
431template <
typename Host>
442template <
typename Host>
449template <
typename Host>
451 const char* input_tf,
const char* script_tf,
bool with_timeframes,
466 module->prepare_metrics_only_v1(source, count, input_tf, script_tf,
468 if (with_timeframes) {
469 engine->run(
source, count, input_tf ? input_tf :
"",
470 script_tf ? script_tf :
"");
472 engine->run(
source, count);
475 }
catch (
const std::exception& error) {
482template <
typename Host>
487template <
typename Host>
489 const char* input_tf,
const char* script_tf,
490 int bar_magnifier,
int magnifier_samples,
500 {{
"option",
"magnifier"}}));
511 std::memcpy(&local,
report,
sizeof(local));
512 BacktestEngine::free_report(&local);
513 std::memcpy(
report, &local,
sizeof(local));
521#define PINEFORGE_EXPORT_NATIVE_STRATEGY(Class) \
523 PF_API std::int32_t strategy_metrics_only_capability_v1(void) noexcept { \
524 return ::pineforge::native_module::metrics_only_capability_v1<Class>(); \
526 PF_API pf_strategy_t strategy_create(const char*) { \
527 return ::pineforge::native_module::create<Class>(); \
529 PF_API void strategy_free(pf_strategy_t s) { \
530 ::pineforge::native_module::destroy(s); \
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); \
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); \
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); \
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); \
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); \
556 PF_API void report_free(pf_report_t* report) { \
557 ::pineforge::native_module::report_free(report); \
560 static_assert(true, "PINEFORGE_EXPORT_NATIVE_STRATEGY expects a trailing ';'")
void set_input(const std::string &key, const std::string &value)
void set_magnifier_volume_weighted(bool on)
void fill_report(ReportC *out) const
std::uint64_t value() const noexcept
void number(double value) noexcept
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.
@ PF_MAGNIFIER_ENDPOINTS
Default — exact O,H,L,C points plus uniform fill between.
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.
@ engine_unclassified_error
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.
Single OHLCV bar pushed into the engine.
int64_t timestamp
Bar open time, Unix milliseconds.
Backtest report filled by run_backtest / run_backtest_full.
std::uint32_t calculation_trigger
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()