PineForge unreleased
Deterministic PineScript v6 backtest runtime — C ABI reference
Loading...
Searching...
No Matches
metrics_fold.hpp
Go to the documentation of this file.
1#pragma once
2
3// Pure report folds retain no engine or point source. Streaming-ratio folds
4// own a bounded RAM batch and the run's positional scratch stream.
5// The caller supplies chart-civil month keys after dispatch, without holding
6// a timezone guard across an engine callback.
8#include <algorithm>
9#include <cmath>
10#include <cstddef>
11#include <cstdint>
12#include <cstring>
13#include <limits>
14#include <memory>
15#include <string>
16
17namespace pineforge {
18namespace metrics {
19
20// Same whole-second truncation and civil-month convention as the array path.
21// Non-UTC calls take and release the timezone lock within this call; callers
22// must not already hold that lock. The pure folds below also accept keys from
23// an independently verified calendar provider. Keys are year * 12 + month0
24// in int64 so every int64 millisecond timestamp's UTC month is representable.
25// An unrepresentable local civil time raises report_result_invalid.
26std::int64_t equity_month_key(std::int64_t time_ms, const std::string& chart_timezone);
27
28// Outside this domain the legacy libc fallback computes its month key in
29// int, with possible signed overflow. Monthly replay never claims that
30// domain or changes that expression; the engine keeps/recovers the curve.
31inline bool monthly_replay_time_supported_v1(std::int64_t time_ms) {
32 constexpr std::int64_t span_seconds = std::int64_t{1} << 40;
33 const auto seconds = time_ms / 1000; // same truncation as the legacy key
34 return seconds > -span_seconds && seconds < span_seconds;
35}
36
37// Streaming ratios. A fold owns a fixed-size prefix, one editable held point,
38// and an ordered packed return replay. Its fallback result is an internal
39// routing decision, never a run failure; scratch I/O failures remain typed.
40enum class MetricsStreamingStatusV1 : std::int32_t {
41 Ok = 0,
44 Busy = -3,
45};
46enum class StreamingSharpeV1 : std::uint32_t { Off = 0, On = 1 };
47using MetricsNeedMaskV1 = std::uint64_t;
55constexpr MetricsNeedMaskV1 kMetricsNeedAllV1 = (1ULL << 7) - 1;
56
57// Closed month ends only; the current bucket and editable tail are scalars.
58constexpr std::size_t kMetricsMonthEndLimitV1 = 4096;
59
60// Fixed-width, versioned diagnostic PODs; no change to pf_report_t.
61// Empty extents have empty=1 and count=0; endpoint words are not observations.
63 std::int64_t count = 0;
64 std::int64_t first_time_ms = 0;
65 std::int64_t last_time_ms = 0;
66 std::uint32_t empty = 1;
67 std::uint32_t reserved = 0;
68};
71 std::uint64_t attempt = 0;
72 std::uint64_t input_generation = 0;
73 std::uint64_t admitted_view = 0;
74 std::uint64_t spec_identity = 0;
75 std::uint32_t present = 0;
76 std::uint32_t reserved = 0;
77};
78enum class MetricsRatioRouteV1 : std::uint32_t {
80};
81enum class MetricsExtentWitnessV1 : std::uint32_t {
83};
90enum class MetricsStopBoundaryKindV1 : std::uint32_t {
92};
99// mismatch_fields: bit 0 empty, 1 count, 2 first, 3 last, 4 attempt,
100// 5 input_generation, 6 admitted_view, 7 spec_identity.
127static_assert(sizeof(MetricsExtentV1) == 32, "streaming-ratio extent POD");
128static_assert(sizeof(MetricsExtentPlanV1) == 72, "streaming-ratio plan POD");
129static_assert(sizeof(MetricsStopBoundaryV1) == 24, "streaming-ratio stop POD");
130static_assert(sizeof(MetricsExtentReceiptV1) == 248, "streaming-ratio receipt POD");
131static_assert(offsetof(MetricsExtentReceiptV1, actual_last_equity) == 232,
132 "streaming-ratio final equity offset");
133static_assert(offsetof(MetricsExtentReceiptV1, actual_last_equity_valid) == 240,
134 "streaming-ratio final equity validity offset");
135
136// The engine uses actual extent for Sharpe; only Sortino consumes the plan.
137// On is the default; explicit Off plus a Sharpe need selects the exact curve
138// route. A mask without SharpeBar permits skipping Sharpe. Returns/Curve
139// needs retain the curve under either switch setting. A mismatch or
140// failed recovery proof chooses the exact curve route, never a new run
141// failure or a changed report domain. An admitted no-curve route uses the
142// ordered scratch replay and reports OrderedReplay.
153namespace fold_detail {
154inline std::uint64_t extent_mismatch_v1(const MetricsExtentPlanV1& plan,
155 const MetricsStreamingConfigV1& config, const MetricsExtentV1& actual) {
156 if (!plan.present) return 0;
157 std::uint64_t fields = 0;
158 if (plan.extent.empty != actual.empty) fields |= 1ULL << 0;
159 if (plan.extent.count != actual.count) fields |= 1ULL << 1;
160 if (!plan.extent.empty && !actual.empty) {
161 if (plan.extent.first_time_ms != actual.first_time_ms) fields |= 1ULL << 2;
162 if (plan.extent.last_time_ms != actual.last_time_ms) fields |= 1ULL << 3;
163 }
164 if (plan.attempt != config.attempt) fields |= 1ULL << 4;
165 if (plan.input_generation != config.input_generation) fields |= 1ULL << 5;
166 if (plan.admitted_view != config.admitted_view) fields |= 1ULL << 6;
167 if (plan.spec_identity != config.spec_identity) fields |= 1ULL << 7;
168 return fields;
169}
170} // namespace fold_detail
172 const char* series_stage;
173 std::int64_t return_index;
174 const char* operation;
175 const char* code;
176 const char* field;
177 const char* reason;
178};
179using MetricsRatioTraceCallbackV1 = void (*)(void*, const MetricsRatioTraceV1&);
186 double sharpe_bar = std::numeric_limits<double>::quiet_NaN();
187 double sortino_bar = std::numeric_limits<double>::quiet_NaN();
188 double downside_sum = 0.0;
189 double centered_sum = 0.0;
190 const char* failure_code = nullptr;
191 const char* failure_field = nullptr;
192 const char* failure_reason = nullptr;
193};
194// Optional checked, final metric-point provider for the exact curve route.
195// A Streaming result must not read it. It is absent after a no-curve attempt;
196// LegacyRerun then asks the eligible host for its authoritative second run.
198 void* context = nullptr;
199 std::int64_t count = 0;
200 pf_equity_point_t (*read)(void*, std::int64_t) = nullptr;
201};
203public:
204 virtual ~StreamingEquityRatiosV1() = default;
205 virtual MetricsStreamingStatusV1 append(const pf_equity_point_t&, int month_key) = 0;
206 // zero-based logical index must be the CURRENT held point. A released
207 // point cannot be amended after its successor exists. Violation is an
208 // internal assertion (SIGABRT, also in Release), never a new run failure.
209 // Repeated amendments before release are legal.
211 std::int64_t logical_index, const pf_equity_point_t&, int month_key) = 0;
214 virtual std::size_t state_size_bytes() const = 0;
215};
218 MetricsRatioTraceSinkV1, std::unique_ptr<StreamingEquityRatiosV1>&);
219
220// Separate companion, preserving the existing result/receipt and virtual
221// interface layouts. Explicit monthly needs enable it at construction for
222// no-curve folds. The engine also enables it for bar-only needs because its
223// completed equity summary includes monthly fields under every scalar mask.
224// Enable before the first append; foreign fold implementations are refused.
226 double sharpe_monthly = std::numeric_limits<double>::quiet_NaN();
227 double sortino_monthly = std::numeric_limits<double>::quiet_NaN();
228 std::uint32_t complete = 0;
229 std::uint32_t reserved = 0;
230};
234
236 std::size_t count = 0;
237 double sum = 0.0;
238 void fold(double value) { sum += value; ++count; }
239};
240
242 std::int64_t point_count = 0;
243 std::int64_t observation_count = 0;
244 std::int64_t bars_in_market = 0;
247 double max_drawdown = 0.0, max_drawdown_pct = 0.0;
248 double max_runup = 0.0, max_runup_pct = 0.0;
250 // Report-only ordered witness, independent of the broker-state hash.
251 std::uint64_t point_witness = 1469598103934665603ULL;
252 // First failure in the ordered point walk, deferred until finalization:
253 // the array producer checks inputs and the FINAL open profit first.
254 const char* nonfinite_field = nullptr;
255
256 double span_ms() const {
257 return last.time_ms >= first.time_ms
258 ? static_cast<double>(static_cast<std::uint64_t>(last.time_ms)
259 - static_cast<std::uint64_t>(first.time_ms))
260 : -static_cast<double>(static_cast<std::uint64_t>(first.time_ms)
261 - static_cast<std::uint64_t>(last.time_ms));
262 }
263 double span_years() const { return span_ms() / (365.25 * 86400.0 * 1000.0); }
264 bool needs_second_pass() const {
265 return month_returns.count >= 2
266 || (point_count >= 3 && span_years() > 0.0 && bar_returns.count >= 2);
267 }
268 bool same_input(const EquityFoldSnapshot& other) const {
269 return point_count == other.point_count
272 && point_witness == other.point_witness
273 && ((nonfinite_field == nullptr && other.nonfinite_field == nullptr)
275 && std::strcmp(nonfinite_field, other.nonfinite_field) == 0))
276 && bar_returns.count == other.bar_returns.count
277 && month_returns.count == other.month_returns.count
278 && std::memcmp(&bar_returns.sum, &other.bar_returns.sum, sizeof(double)) == 0
279 && std::memcmp(&month_returns.sum, &other.month_returns.sum, sizeof(double)) == 0
280 && std::memcmp(&first, &other.first, sizeof(first)) == 0
281 && std::memcmp(&last, &other.last, sizeof(last)) == 0;
282 }
283};
284
285namespace fold_detail {
286constexpr double nan = std::numeric_limits<double>::quiet_NaN();
287
288// Do not throw while accumulating a prefix. A terminal replacement can remove
289// its held defect, and a completed report has checks preceding this walk.
290// Keep only the first field in each ordered stage; the producer's existing
291// report_result_invalid helper raises it at that stage during finalization.
292inline double defer_nonfinite(double value, const char* field, const char*& first) {
293 if (!std::isfinite(value) && first == nullptr) first = field;
294 return value;
295}
296
297// The state of the array path's two return generators, in their original
298// order. A bucket closes on a KEY CHANGE, including nonmonotonic timestamps.
300 bool have_point = false;
302 std::int64_t current_key = 0;
303 double previous_equity = 0.0;
305
306 template<class BarEmit, class MonthEmit>
307 void fold(const pf_equity_point_t& point, std::int64_t key, BarEmit bar, MonthEmit month) {
308 if (!have_point) {
309 have_point = true;
310 current_key = key;
312 return;
313 }
314 if (previous_equity > 0.0) bar(point.equity / previous_equity - 1.0);
315 previous_equity = point.equity;
316 if (key != current_key) {
317 finish_month(month);
319 have_previous_month = true;
320 current_key = key;
321 }
323 }
324 template<class MonthEmit>
325 void finish_month(MonthEmit month) const {
328 }
329};
330
331inline void witness_word(std::uint64_t& hash, std::uint64_t word) {
332 for (unsigned i = 0; i < 8; ++i) {
333 hash ^= (word >> (8 * i)) & 255u;
334 hash *= 1099511628211ULL;
335 }
336}
337inline void witness_double(std::uint64_t& hash, double value) {
338 std::uint64_t bits;
339 std::memcpy(&bits, &value, sizeof(bits));
340 witness_word(hash, bits);
341}
342
346 double peak = 0.0, trough = 0.0;
347
348 void fold(const pf_equity_point_t& point, std::int64_t key, bool observation, bool exposed) {
349 auto& s = values;
350 if (s.point_count == 0) {
351 s.first = point;
352 peak = trough = point.equity; // FIRST point, never initial capital
353 }
354 ++s.point_count;
355 if (observation) ++s.observation_count;
356 if (observation && exposed) ++s.bars_in_market;
357 s.last = point;
358 const auto check = [&s](double value, const char* field) {
359 return defer_nonfinite(value, field, s.nonfinite_field);
360 };
361 const double eq = check(point.equity, "equity_curve.equity");
362 check(point.open_profit, "equity_curve.open_profit");
363 if (eq > peak) { peak = eq; trough = eq; }
364 if (eq < trough) trough = eq;
365 const double dd = check(peak - eq, "metrics.equity.max_equity_drawdown");
366 if (dd > s.max_drawdown) {
367 s.max_drawdown = dd;
368 s.max_drawdown_pct = peak > 0.0
369 ? check(dd / peak * 100.0, "metrics.equity.max_equity_drawdown_pct") : nan;
370 }
371 const double ru = check(eq - trough, "metrics.equity.max_equity_runup");
372 if (ru > s.max_runup) {
373 s.max_runup = ru;
374 s.max_runup_pct = trough > 0.0
375 ? check(ru / trough * 100.0, "metrics.equity.max_equity_runup_pct") : nan;
376 }
377 returns.fold(point, key,
378 [&](double r) { s.bar_returns.fold(r); },
379 [&](double r) { s.month_returns.fold(r); });
380 witness_word(s.point_witness, static_cast<std::uint64_t>(point.time_ms));
381 witness_double(s.point_witness, point.equity);
382 witness_double(s.point_witness, point.open_profit);
383 witness_word(s.point_witness, static_cast<std::uint64_t>(key));
384 witness_word(s.point_witness, observation ? 1u : 0u);
385 witness_word(s.point_witness, observation && exposed ? 1u : 0u);
386 }
387};
388} // namespace fold_detail
389
390// Prefix excludes the held point. Replacing that point therefore requires no
391// subtraction, inverse sum, or regrouping, even across a civil-month boundary.
392// first_pass() closes the held month in a value copy and is repeatable.
394public:
395 void fold(const pf_equity_point_t& point, std::int64_t month_key,
396 bool observation = true, bool exposed = false) {
397 if (have_held_)
398 prefix_.fold(held_, held_key_, held_observation_, held_exposed_);
399 held_ = point;
400 held_key_ = month_key;
401 held_observation_ = observation;
402 held_exposed_ = exposed;
403 have_held_ = true;
404 }
405 // Returns false on an empty fold; it never invents an observation.
406 bool replace_last(const pf_equity_point_t& point, std::int64_t month_key) {
407 if (!have_held_) return false;
408 held_ = point;
409 held_key_ = month_key;
410 return true;
411 }
412 const pf_equity_point_t* held_point() const { return have_held_ ? &held_ : nullptr; }
413 std::int64_t held_month_key() const { return held_key_; }
414 bool held_observation() const { return held_observation_; }
415 bool held_exposed() const { return held_exposed_; }
417 auto result = prefix_;
418 if (have_held_)
419 result.fold(held_, held_key_, held_observation_, held_exposed_);
420 result.returns.finish_month(
421 [&](double r) { result.values.month_returns.fold(r); });
422 return result.values;
423 }
424
425private:
427 pf_equity_point_t held_{};
428 std::int64_t held_key_ = 0;
429 bool held_observation_ = false, held_exposed_ = false, have_held_ = false;
430};
431
436 double net_profit; // Report row-order sum, including presented terminal rows.
437};
438
443 const char* nonfinite_field = nullptr;
444 bool complete = false;
445 bool matches(const EquityFoldSnapshot& first) const {
446 return complete && expected.same_input(first) && replayed.same_input(first);
447 }
448};
449
450namespace fold_detail {
452 std::size_t expected_count = 0, count = 0;
453 double mean = 0.0, rf = 0.0, ann = 0.0;
454 double var = 0.0, down = 0.0;
455 const char* sharpe_field = nullptr;
456 const char* sortino_field = nullptr;
457 const char* nonfinite_field = nullptr;
458 bool enabled = false;
459 double check(double value, const char* field) {
460 return defer_nonfinite(value, field, nonfinite_field);
461 }
462 void prepare(const EquityReturnSummary& first, double risk_free, double annualization,
463 bool allowed, const char* sharpe_name, const char* sortino_name) {
464 sharpe_field = sharpe_name;
465 sortino_field = sortino_name;
466 expected_count = first.count;
467 enabled = allowed && first.count >= 2;
468 if (!enabled) return;
469 rf = check(risk_free, sharpe_field);
470 ann = check(annualization, sharpe_field);
471 check(first.sum, sharpe_field);
472 mean = check(first.sum / static_cast<double>(first.count), sharpe_field);
473 }
474 void fold(double value) {
475 if (!enabled) return;
476 check(value, sharpe_field);
477 const double delta = check(value - mean, sharpe_field);
478 var = check(var + delta * delta, sharpe_field);
479 const double d = std::min(0.0, check(value - rf, sortino_field));
480 down = check(down + d * d, sortino_field);
481 ++count;
482 }
483 bool complete() const { return !enabled || count == expected_count; }
484 void finish(double& sharpe, double& sortino) {
485 sharpe = sortino = nan;
486 if (!enabled || !complete()) return;
487 const double sd = check(std::sqrt(var / static_cast<double>(expected_count - 1)),
489 const double dd = check(std::sqrt(down / static_cast<double>(expected_count)),
491 if (sd > 0.0) sharpe = check((mean - rf) / sd * ann, sharpe_field);
492 if (dd > 0.0) sortino = check((mean - rf) / dd * ann, sortino_field);
493 }
494};
498 void fold(const pf_equity_point_t& point, std::int64_t key) {
499 returns.fold(point, key, [&](double r) { bars.fold(r); },
500 [&](double r) { months.fold(r); });
501 }
502};
503} // namespace fold_detail
504
505// A deterministic replay feeds finalized points in their original order.
506// This class does not execute an engine, retain an input, or invoke observers.
507// It also holds its last point so a replay can apply terminal/presentation
508// rewrites before finish(). Both return series share this single walk.
510public:
511 explicit EquityMetricsSecondPass(const EquityFoldSnapshot& first) : expected_(first) {
512 prefix_.months.prepare(first.month_returns, 0.02 / 12.0, std::sqrt(12.0), true,
513 "metrics.equity.sharpe_monthly", "metrics.equity.sortino_monthly");
514 const double span = first.span_years();
515 if (first.point_count >= 3 && span > 0.0) {
516 const double bars_per_year = prefix_.bars.check(
517 static_cast<double>(first.point_count - 1) / span, "metrics.equity.sharpe_bar");
518 prefix_.bars.prepare(first.bar_returns, 0.02 / bars_per_year,
519 std::sqrt(bars_per_year), true,
520 "metrics.equity.sharpe_bar", "metrics.equity.sortino_bar");
521 }
522 }
523 void fold(const pf_equity_point_t& point, std::int64_t month_key,
524 bool observation = true, bool exposed = false) {
525 if (const auto* held = replay_.held_point())
526 prefix_.fold(*held, replay_.held_month_key());
527 replay_.fold(point, month_key, observation, exposed);
528 }
529 bool replace_last(const pf_equity_point_t& point, std::int64_t month_key) {
530 return replay_.replace_last(point, month_key);
531 }
533 auto final = prefix_;
534 if (const auto* held = replay_.held_point())
535 final.fold(*held, replay_.held_month_key());
536 final.returns.finish_month([&](double r) { final.months.fold(r); });
537 EquityRatioResult result;
538 result.expected = expected_;
539 result.replayed = replay_.first_pass();
540 result.complete = expected_.same_input(result.replayed)
541 && final.bars.complete() && final.months.complete();
542 if (result.complete) {
543 final.months.finish(result.sharpe_monthly, result.sortino_monthly);
544 final.bars.finish(result.sharpe_bar, result.sortino_bar);
545 // Monthly ratios precede per-bar ratios in the array producer,
546 // regardless of when either defect was encountered during replay.
547 result.nonfinite_field = final.months.nonfinite_field
548 ? final.months.nonfinite_field : final.bars.nonfinite_field;
549 }
550 return result;
551 }
552
553private:
554 EquityFoldSnapshot expected_;
555 EquityMetricsFold replay_;
557};
558
560 const EquityFoldSnapshot& first, const EquityMetricInputs& inputs,
561 const EquityRatioResult* ratios = nullptr);
562
563// The trade blocks come from the existing fixed accumulators. Passing no
564// second pass explicitly produces first-pass fields only; callers publish
565// readiness separately (needs_second_pass), never describe pending ratios as
566// a completed summary.
568 const EquityFoldSnapshot& first, const EquityMetricInputs& inputs,
569 const pf_trade_stats_t& all, const pf_trade_stats_t& longs,
570 const pf_trade_stats_t& shorts, const EquityRatioResult* ratios = nullptr) {
571 pf_metrics_t result{};
572 result.all = all;
573 result.longs = longs;
574 result.shorts = shorts;
575 result.equity = finalize_equity_metrics(first, inputs, ratios);
576 return result;
577}
578
579} // namespace metrics
580} // namespace pineforge
const pf_equity_point_t * held_point() const
EquityFoldSnapshot first_pass() const
void fold(const pf_equity_point_t &point, std::int64_t month_key, bool observation=true, bool exposed=false)
bool replace_last(const pf_equity_point_t &point, std::int64_t month_key)
bool replace_last(const pf_equity_point_t &point, std::int64_t month_key)
void fold(const pf_equity_point_t &point, std::int64_t month_key, bool observation=true, bool exposed=false)
EquityMetricsSecondPass(const EquityFoldSnapshot &first)
virtual MetricsStreamingStatusV1 amend_held(std::int64_t logical_index, const pf_equity_point_t &, int month_key)=0
virtual MetricsStreamingStatusV1 finish(MetricsStreamingResultV1 &, MetricsRatioCurveV1={}) const =0
virtual std::size_t state_size_bytes() const =0
virtual MetricsStreamingStatusV1 append(const pf_equity_point_t &, int month_key)=0
void witness_word(std::uint64_t &hash, std::uint64_t word)
void witness_double(std::uint64_t &hash, double value)
double defer_nonfinite(double value, const char *field, const char *&first)
std::uint64_t extent_mismatch_v1(const MetricsExtentPlanV1 &plan, const MetricsStreamingConfigV1 &config, const MetricsExtentV1 &actual)
constexpr MetricsNeedMaskV1 kMetricsNeedAllV1
constexpr MetricsNeedMaskV1 kMetricsNeedTradeStatsV1
constexpr std::size_t kMetricsMonthEndLimitV1
constexpr MetricsNeedMaskV1 kMetricsNeedEquitySummaryV1
MetricsStreamingStatusV1 enable_streaming_monthly_replay_v1(StreamingEquityRatiosV1 &)
std::int64_t equity_month_key(std::int64_t time_ms, const std::string &chart_timezone)
std::uint64_t MetricsNeedMaskV1
constexpr MetricsNeedMaskV1 kMetricsNeedSharpeBarV1
MetricsStreamingStatusV1 make_streaming_equity_ratios_v1(const MetricsStreamingConfigV1 &, const MetricsExtentPlanV1 &, MetricsRatioTraceSinkV1, std::unique_ptr< StreamingEquityRatiosV1 > &)
MetricsStreamingStatusV1 streaming_monthly_result_v1(const StreamingEquityRatiosV1 &, MetricsMonthlyResultV1 &)
constexpr MetricsNeedMaskV1 kMetricsNeedCurveV1
pf_metrics_t finalize_metrics(const EquityFoldSnapshot &first, const EquityMetricInputs &inputs, const pf_trade_stats_t &all, const pf_trade_stats_t &longs, const pf_trade_stats_t &shorts, const EquityRatioResult *ratios=nullptr)
constexpr MetricsNeedMaskV1 kMetricsNeedSortinoBarV1
void(*)(void *, const MetricsRatioTraceV1 &) MetricsRatioTraceCallbackV1
pf_equity_stats_t finalize_equity_metrics(const EquityFoldSnapshot &first, const EquityMetricInputs &inputs, const EquityRatioResult *ratios=nullptr)
constexpr MetricsNeedMaskV1 kMetricsNeedReturnsV1
bool monthly_replay_time_supported_v1(std::int64_t time_ms)
constexpr MetricsNeedMaskV1 kMetricsNeedMonthlyRatiosV1
Single per-script-bar equity point.
Definition pineforge.h:326
double open_profit
Mark-to-market open P&L at bar close.
Definition pineforge.h:329
double equity
initial_capital + net_profit + open_profit.
Definition pineforge.h:328
int64_t time_ms
Script-bar OPEN timestamp (Unix ms).
Definition pineforge.h:327
Equity-curve-derived statistics (all-trades only, like TV).
Definition pineforge.h:272
Composite metrics container: trade stats (all / long / short) + equity-curve stats.
Definition pineforge.h:317
pf_trade_stats_t all
Definition pineforge.h:318
Trade-level statistics block — computed once each for all / long / short.
Definition pineforge.h:209
bool same_input(const EquityFoldSnapshot &other) const
bool matches(const EquityFoldSnapshot &first) const
pf_equity_point_t(* read)(void *, std::int64_t)
void fold(const pf_equity_point_t &point, std::int64_t key, bool observation, bool exposed)
void fold(const pf_equity_point_t &point, std::int64_t key, BarEmit bar, MonthEmit month)
void finish(double &sharpe, double &sortino)
double check(double value, const char *field)
void prepare(const EquityReturnSummary &first, double risk_free, double annualization, bool allowed, const char *sharpe_name, const char *sortino_name)
void fold(const pf_equity_point_t &point, std::int64_t key)