Metrics

Return, risk, drawdown, distribution, rolling, yearly and performance-attribution statistics.

See the usage guide for task-oriented examples. Signatures below are generated from the public headers at the same commit as the rest of this site.

class matrix

Row-major dense matrix used where the pinned Python source consumes a NumPy 2-D array.

values().size() is always rows() * columns().

Public Functions

matrix() = default

Constructs an empty 0 x 0 matrix.

matrix(std::size_t row_count, std::size_t column_count, std::vector<double> values)

Constructs a row_count x column_count matrix from a row-major buffer.

Throws input_error when the shape is not representable or values.size() differs from row_count * column_count.

std::size_t rows() const noexcept

Returns the number of rows.

std::size_t columns() const noexcept

Returns the number of columns.

std::span<const double> values() const noexcept

Returns the row-major element buffer, sized rows() * columns().

std::span<const double> row(std::size_t row_index) const

Returns a view of row_index; throws input_error when out of range.

double at(std::size_t row_index, std::size_t column_index) const

Returns the element at (row_index, column_index); throws input_error when either index is out of range.

double &at(std::size_t row_index, std::size_t column_index)

Returns a mutable reference to the element at (row_index, column_index); throws input_error when either index is out of range.

struct bootstrap_metric_stats

Bootstrap distribution of one performance metric.

Public Members

std::string name

Display name of the metric.

std::vector<double> samples

Statistic of each draw, in draw order.

distribution_stats summary

Summary of samples.

struct daily_consecutive_statistics

Daily run statistics from a return series.

Public Members

double max_consecutive_up_days

Longest run of positive observations.

double max_consecutive_down_days

Longest run of negative observations.

double max_consecutive_gain

Largest sum over a positive run.

double max_consecutive_loss

Most negative sum over a negative run.

struct distribution_stats

Descriptive summary of a set of metric samples.

Every field is NaN for an empty sample. Quantiles use linear interpolation and std is the population deviation.

Public Members

double mean

Arithmetic mean.

double median

50th percentile.

double std

Population standard deviation (divides by the count).

double p5

5th percentile.

double p25

25th percentile.

double p75

75th percentile.

double p95

95th percentile.

double iqr

Interquartile range, p75 - p25.

struct drawdown_period

One underwater episode, from the first observation below the running peak through its valley and optional recovery.

Public Members

timestamp start

First timestamp below the prior peak.

timestamp valley

Timestamp of the deepest point.

std::optional<timestamp> recovery

Return to the prior peak; nullopt when the episode never recovers.

double value

Depth at the valley, a negative fraction of peak.

std::size_t duration_periods

Observations from start to valley.

std::optional<std::size_t> recovery_duration_periods

Observations from valley to recovery.

struct drawdown_table_row

One row of gen_drawdown_table.

Public Members

double net_drawdown_percent = std::numeric_limits<double>::quiet_NaN()

Depth as a positive percentage of peak wealth; NaN for an unused row.

std::optional<timestamp> peak

Timestamp of the prior peak.

std::optional<timestamp> valley

Timestamp of the deepest point.

std::optional<timestamp> recovery

Return to the peak, if any.

std::optional<std::size_t> duration_business_days

Monday-Friday days from peak through recovery, inclusive; nullopt without a recovery.

struct drawdown_window

Peak, valley and optional recovery timestamps of a drawdown.

Public Members

timestamp peak

Timestamp of the running peak before the decline.

timestamp valley

Timestamp of the deepest point.

std::optional<timestamp> recovery

Return to the peak; nullopt when the episode never recovers.

struct gpd_risk_estimates_result

Generalized Pareto tail estimates of a return distribution.

Public Members

double threshold

Loss threshold selected for the tail fit.

double scale

GPD scale parameter.

double shape

GPD shape parameter (positive for a fitted tail).

double var

Value-at-risk estimate at the requested probability.

double es

Expected shortfall estimate.

struct named_returns

One named historical window extracted from a return series.

Public Members

std::string name

Window label, for example “Lehman”.

dataframe returns

Returns of the selected column inside the window.

struct perf_attribution_result

Per-date risk exposures and return attribution.

Public Members

dataframe risk_exposures

Factor exposures on the attribution index.

dataframe attribution

Factor contributions plus the total, common, specific, tilt and timing return columns.

struct perf_attribution_statistics

Summary statistics of a perf_attribution_result.

Public Members

std::map<std::string, double> summary

Seven named return statistics.

dataframe factor_summary

One row per factor: its name, average exposure, and annualized and cumulative contribution.

enum class fincore::frequency

Compounding period of a return series.

Values:

enumerator daily

Daily observations, annualized with 252 periods.

enumerator weekly

Weekly observations, annualized with 52 periods.

enumerator monthly

Monthly observations, annualized with 12 periods.

enumerator quarterly

Quarterly observations, annualized with 4 periods.

enumerator yearly

Yearly observations, annualized with 1 period.

enum class fincore::return_aggregation_period

Calendar bucket used by aggregate_returns.

Values:

enumerator weekly

ISO-8601 weeks, keyed by week number within the year.

enumerator monthly

Calendar months.

enumerator quarterly

Calendar quarters.

enumerator yearly

Calendar years.

dataframe fincore::adjust_returns(const dataframe &returns, double adjustment_factor, std::string_view column = returns_column)

Returns a copy of returns with adjustment_factor subtracted from each value in column.

Timestamps are preserved and the result is stored under column; NaN values remain NaN. Throws input_error when column is absent, is not a double column, or does not match the frame index length.

double fincore::adjusted_sharpe_ratio(const dataframe &returns, double risk_free = 0.0, std::string_view column = returns_column)

Returns the Sharpe ratio adjusted for the skewness and kurtosis of the return distribution.

The Sharpe ratio is annualized with daily_annualization and multiplied by a dampened adjustment built from the biased skewness and excess kurtosis of the non-NaN values. The adjustment is clamped to [0.9, 1.1] for fewer than 20 raw observations and to [0.8, 1.3] otherwise. Fewer than four raw values or a NaN Sharpe ratio yield NaN; an embedded NUL in column raises input_error.

dataframe fincore::aggregate_returns(const dataframe &returns, return_aggregation_period period = return_aggregation_period::monthly, std::string_view column = returns_column)

Returns returns compounded into calendar buckets of period.

The output index holds each bucket’s start timestamp (Monday for weeks, clamped to January 1; the first day of the month or quarter; January 1 for years), with integer year and period columns and the compounded return under column. NaN returns are treated as zero. column must not be return_aggregation_year_column or return_aggregation_period_column; otherwise input_error is thrown.

std::pair<dataframe, dataframe> fincore::aligned_series(const dataframe &left, std::string_view left_column, const dataframe &right, std::string_view right_column)

Returns two single-column series aligned on their common timestamps.

Native contract: the common index is emitted in left-frame order (the align_on_common_index semantics), timestamps absent from either frame are dropped, and present NaN values are retained. Both indexes must contain unique timestamps and must not contain the NaT sentinel (numeric_limits<timestamp>::min()); duplicates are ambiguous for keyed alignment and raise input_error.

Declared source variances: the pinned Python two-Series path uses pd.concat(..., sort=False) (an outer union that keeps right-only rows as NaN), while its DataFrame and mixed DataFrame/Series paths intersect. This native helper follows the P101 common-index intersection for every input, and emits left order where the source DataFrame.align path sorts ascending.

std::pair<dataframe, dataframe> fincore::aligned_series(const dataframe &left, const dataframe &right)

Returns two frames aligned on their common timestamps, keeping every double column of each frame in source column order.

Same uniqueness, NaT, missingness and ordering rules as the single-column overload.

double fincore::alpha(const dataframe &returns, const dataframe &benchmark, double risk_free = 0.0, double annualization = daily_annualization, std::string_view returns_column_name = returns_column, std::string_view benchmark_column_name = benchmark_column)

Returns the annualized Jensen alpha on the common timestamps.

NaN pairs are dropped. The mean per-period excess return less beta times the excess benchmark return is compounded at annualization. Returns NaN when beta is not finite. risk_free is subtracted from both series.

std::pair<double, double> fincore::alpha_beta(const dataframe &returns, const dataframe &benchmark, double risk_free = 0.0, double annualization = daily_annualization, std::string_view returns_column_name = returns_column, std::string_view benchmark_column_name = benchmark_column)

Returns {alpha, beta} computed on the common timestamps.

Only finite pairs are used. Returns {NaN, NaN} when fewer than two pairs remain, risk_free or annualization is not finite, annualization is not positive, or either result is not finite.

double fincore::alpha_percentile_rank(const dataframe &strategy_returns, std::span<const dataframe> peer_returns, const dataframe &benchmark, double risk_free = 0.0, double annualization = daily_annualization, std::string_view returns_column_name = returns_column, std::string_view benchmark_column_name = benchmark_column)

Returns the fraction of peer strategy alphas strictly below the strategy’s alpha.

Peer series with fewer than three observations or a NaN alpha are ignored. Returns NaN when the strategy alpha is unavailable or no peer alpha is usable. Embedded NUL characters in the column names raise input_error.

double fincore::annual_active_return(const dataframe &returns, const dataframe &benchmark, frequency period = frequency::daily, std::optional<double> annualization = std::nullopt, std::string_view returns_column_name = returns_column, std::string_view benchmark_column_name = benchmark_column)

Returns the difference between the annual returns of returns and benchmark on their common timestamps.

Both annual returns use annualization_factor(period, annualization). Returns NaN when the aligned pair is empty. An unknown frequency without an explicit annualization raises input_error.

dataframe fincore::annual_active_return_by_year(const dataframe &returns, const dataframe &benchmark, frequency period = frequency::daily, std::optional<double> annualization = std::nullopt, std::string_view returns_column_name = returns_column, std::string_view benchmark_column_name = benchmark_column)

Returns the active return per UTC calendar year in column “annual_active_return”.

The two frames are aligned on their common timestamps before bucketing, and each year holds the difference of the two annual returns using annualization_factor(period, annualization). Years are indexed by January 1 and sorted ascending; an empty aligned pair yields an empty typed frame. An unknown frequency without an explicit annualization raises input_error.

dataframe fincore::annual_alpha(const dataframe &returns, const dataframe &benchmark, double risk_free = 0.0, frequency period = frequency::daily, std::optional<double> annualization = std::nullopt, std::string_view returns_column_name = returns_column, std::string_view benchmark_column_name = benchmark_column)

Returns annual alpha per UTC calendar year, indexed by January 1 and stored in column “alpha”.

Each year uses annualization_factor(period, annualization) and the same NaN-pair rules as alpha.

dataframe fincore::annual_beta(const dataframe &returns, const dataframe &benchmark, double risk_free = 0.0, frequency period = frequency::daily, std::optional<double> annualization = std::nullopt, std::string_view returns_column_name = returns_column, std::string_view benchmark_column_name = benchmark_column)

Returns annual beta per UTC calendar year, indexed by January 1 and stored in column “beta”.

period and annualization are ignored because beta is not annualized.

double fincore::annual_return(const dataframe &returns, double annualization = daily_annualization, std::string_view column = returns_column)

Returns the geometric annualized return of column.

Total wealth is raised to annualization / observation_count, with NaN returns treated as zero. An empty frame yields NaN and non-positive ending wealth yields -1.

dataframe fincore::annual_return_by_year(const dataframe &returns, frequency period = frequency::daily, std::optional<double> annualization = std::nullopt, std::string_view column = returns_column)

Returns the annual return per UTC calendar year in column “annual_return”.

Each year is indexed by its January 1 timestamp and uses annual_return with annualization_factor(period, annualization). Years are sorted ascending, while observations keep their input order inside a year. An empty frame yields an empty typed frame, and an unknown frequency without an explicit annualization raises input_error.

double fincore::annual_volatility(const dataframe &returns, double annualization = daily_annualization, std::string_view column = returns_column)

Returns the annualized sample standard deviation of column.

Non-NaN values are used with one degree of freedom and the result is scaled by sqrt(annualization); fewer than two valid values yield NaN.

dataframe fincore::annual_volatility_by_year(const dataframe &returns, frequency period = frequency::daily, std::optional<double> annualization = std::nullopt, std::string_view column = returns_column)

Returns the annualized volatility per UTC calendar year in column “annual_volatility”.

Each year uses annual_volatility, so NaN observations are ignored, with annualization_factor(period, annualization). Years are indexed by January 1 and sorted ascending. An empty frame yields an empty typed frame, and an unknown frequency without an explicit annualization raises input_error.

double fincore::annualization_factor(frequency period, std::optional<double> annualization = std::nullopt)

Returns the number of periods per year used to annualize period.

An explicit annualization value is returned unchanged; otherwise the defaults are 252, 52, 12, 4 and 1 for daily through yearly observations.

double fincore::beta(const dataframe &returns, const dataframe &benchmark, double risk_free = 0.0, std::string_view returns_column_name = returns_column, std::string_view benchmark_column_name = benchmark_column)

Returns the OLS beta of returns against benchmark on their common timestamps.

NaN pairs are dropped and risk_free is subtracted from both series before the regression. Returns NaN with fewer than two valid pairs, near-zero benchmark variance, or a non-finite covariance/beta result.

double fincore::beta_fragility_heuristic(const dataframe &returns, const dataframe &benchmark, std::string_view returns_column_name = returns_column, std::string_view benchmark_column_name = benchmark_column)

Returns a heuristic beta-fragility score comparing returns at the low and high ends of the factor-sorted sample with the middle observation.

Requires at least three aligned non-NaN pairs; otherwise returns NaN. Embedded NUL characters in the column names raise input_error.

double fincore::burke_ratio(const dataframe &returns, double risk_free = 0.0, double annualization = daily_annualization, std::string_view column = returns_column)

Returns the annual return in excess of risk_free divided by the Burke drawdown risk measure.

The risk measure is the square root of the sum of squared drawdown depths; when there are no drawdowns or all are zero, it falls back to the population standard deviation of the negative returns (or of all usable values when none are negative). The annual return is compounded at annualization. Returns positive infinity when the risk measure is below 1e-10 and the excess annual return is positive, NaN in the corresponding non-positive case, and NaN for fewer than two raw values or no usable value. An embedded NUL in column raises input_error.

double fincore::cal_treynor_ratio(const dataframe &returns, const dataframe &benchmark, double risk_free = 0.0, double annualization = daily_annualization, std::string_view returns_column_name = returns_column, std::string_view benchmark_column_name = benchmark_column)

Returns the annualized Treynor ratio of returns against benchmark.

Both series are aligned on their common timestamps; the annual return in excess of risk_free is divided by the beta of the aligned pair. Returns NaN when fewer than two aligned returns remain or beta is NaN, zero or negative. An embedded NUL in either column name raises input_error.

distribution_stats fincore::calc_distribution_stats(std::span<const double> values)

Returns the mean, deviation and percentiles of values.

Values are used as given (no NaN filtering); percentiles use linear interpolation at (n - 1) * p / 100 and std divides by the sample count. An empty span yields NaN in every field.

double fincore::calmar_ratio(const dataframe &returns, double risk_free = 0.0, double annualization = daily_annualization, std::string_view column = returns_column)

Returns the annual return in excess of risk_free divided by the absolute maximum drawdown.

The annual return is compounded at annualization. Returns NaN when the series never declines (the maximum drawdown is zero or NaN) or the quotient is not finite.

double fincore::capm_r_squared(const dataframe &returns, const dataframe &benchmark, std::string_view returns_column_name = returns_column, std::string_view benchmark_column_name = benchmark_column)

Returns the squared CAPM correlation, clamped to [0, 1].

Returns NaN when the correlation is unavailable.

double fincore::capture(const dataframe &returns, const dataframe &benchmark, double annualization = daily_annualization, std::string_view returns_column_name = returns_column, std::string_view benchmark_column_name = benchmark_column)

Returns the ratio of the annualized geometric return of returns to that of benchmark.

Both series are aligned on their common timestamps and only finite pairs are kept; each side is compounded over its paired observations and annualized with annualization. Returns NaN when no pair remains, either annual return is non-finite, the benchmark annual return is zero, or the quotient is not finite.

double fincore::common_sense_ratio(const dataframe &returns, std::string_view column = returns_column)

Returns the common-sense ratio: the tail ratio times the win rate divided by the loss rate.

Both components are taken from column with their own NaN rules. Returns NaN for fewer than two raw values, a NaN component, or a zero win rate, and positive infinity when every usable observation is a gain. An embedded NUL in column raises input_error.

dataframe fincore::compute_bayes_cone(const dataframe &predicted_returns, double starting_value = 1.0)

Returns the cross-path percentile cone of cumulative predicted wealth.

Each double column of predicted_returns is one path and each row is a forecast step, in input order. Wealth starts at starting_value, is multiplied by 1 + return per row, and at every row the seven linear percentiles across paths are stored in the fixed percentile_* columns. The input index, including duplicates, is preserved, and the returned frame owns its values.

At least one column is required, every column must be a double column matching the frame index, and starting_value, every return, cumulative wealth and interpolated percentile must be finite; otherwise input_error is thrown, including for unusable shapes and allocation failures. An index-empty frame with typed empty columns yields a typed empty seven-column frame.

std::vector<double> fincore::compute_consistency_score(const dataframe &returns_test, const matrix &predicted_returns, std::string_view column = returns_column)

Returns the per-date fraction of cumulative predicted paths strictly below the realized cumulative return.

predicted_returns is row-major with one row per sampled path and one column per out-of-sample date. returns_test must have one value per column. Source formula: realized wealth = cumprod(1 + returns) with NaN returns treated as 0 (the source cum_returns policy), predicted wealth = cumprod(1 + preds) per row, score[t] = |{path : cum_pred[path, t] < realized[t]}| / path_count. Predicted values must be finite with a finite 1 + value; realized values may be NaN (treated as 0) but not infinite. Zero paths yields NaN scores for every date, matching the source mean of an empty axis.

dataframe fincore::compute_exposures(const dataframe &positions, const dataframe &factor_loadings, std::span<const std::string_view> factor_column_names, std::string_view cash_column_name = cash_column, std::string_view symbol_column_name = attribution_symbol_column)

Returns per-date factor exposures of positions.

For each requested factor, the exposure sums position value times factor loading over the assets, matched by (timestamp, symbol) against factor_loadings; NaN products are skipped. The result keeps the positions index and one column per name in factor_column_names, in that order. Duplicate position timestamps, empty, duplicated or symbol-colliding factor names, a missing or non-string symbol column, a non-double factor-loading column, a duplicate (timestamp, symbol) key, or a missing key for a positioned asset raise input_error.

double fincore::conditional_sharpe_ratio(const dataframe &returns, double cutoff = 0.05, double risk_free = 0.0, double annualization = daily_annualization, std::string_view column = returns_column)

Returns the Sharpe ratio of the left tail defined by cutoff.

The cutoff quantile is linearly interpolated at (n - 1) * cutoff; every value at or below it forms the tail. The tail mean excess over risk_free is divided by the tail sample deviation (one degree of freedom) and scaled by sqrt(annualization). Returns NaN when cutoff is not in (0, 1), any raw value is NaN, the tail has fewer than two values, or the deviation is zero or non-finite. An embedded NUL in column raises input_error.

double fincore::conditional_value_at_risk(const dataframe &returns, double cutoff = 0.05, std::string_view column = returns_column)

Returns the mean of all observations at or below the empirical cutoff quantile of column.

Returns NaN unless every value is finite and cutoff lies in [0, 1].

std::string fincore::cone_bounds_column_name(double num_std)

Returns the native column label for one cone bound width, for example cone_1 or cone_-1.

The width is rendered in its shortest round-trip form; a non-finite width raises input_error.

std::map<std::string, double> fincore::consecutive_stats(const dataframe &returns, std::string_view column = returns_column)

Returns a map of the six daily, weekly and monthly up/down run lengths, keyed by their snake_case names.

double fincore::cornell_timing(const dataframe &returns, const dataframe &benchmark, double risk_free = 0.0, std::string_view returns_column_name = returns_column, std::string_view benchmark_column_name = benchmark_column)

Returns the Cornell timing measure: the up-market slope minus the down-market slope.

Fits excess_return = a + b * up + c * down, where up is max(0, excess_market) and down is min(0, excess_market), by ordinary least squares and returns b - c. Only finite pairs are used and risk_free is subtracted from both series. Returns NaN with fewer than ten usable pairs, a non-finite risk_free, a singular design, or a non-finite result.

perf_attribution_statistics fincore::create_perf_attrib_stats(const perf_attribution_result &result, double annualization = daily_annualization)

Returns the return summary and per-factor statistics of an attribution.

summary

holds “Annualized Specific Return”, “Annualized Common Return”, “Annualized Total Return”, “Specific Sharpe Ratio”, “Cumulative Specific

Return”, “Cumulative Common Return” and “Total Returns”; annualized values compound with

annualization, and the specific Sharpe ratio also uses it. factor_summary has a positional 0..n-1 index with columns “factor”, “Average Risk Factor Exposure” (mean non-NaN exposure), “Annualized Return” and “Cumulative Return” of each factor’s contribution. Mismatched indexes or a missing standard attribution column raise input_error.

double fincore::cumulative_return(const dataframe &returns, std::string_view column = returns_column)

Returns the total compounded return of column, treating NaN returns as zero; an empty frame yields NaN.

double fincore::cumulative_return(const dataframe &returns, std::initializer_list<std::string_view> legacy_column)

Returns cumulative_return for the legacy single-name column form.

The initializer list may hold at most one name; more than one throws input_error.

double fincore::cumulative_return(const dataframe &returns, double starting_value, std::string_view column = returns_column)

Returns the final cumulative value of column scaled by starting_value.

The result is starting_value * wealth when starting_value is nonzero and wealth - 1 when it is zero. NaN returns are treated as zero.

dataframe fincore::cumulative_returns(const dataframe &returns, std::string_view column = returns_column)

Returns the cumulative growth of column as wealth - 1 at each timestamp, with NaN returns treated as zero.

dataframe fincore::cumulative_returns(const dataframe &returns, std::initializer_list<std::string_view> legacy_column)

Returns cumulative_returns for the legacy single-name column form.

The initializer list may hold at most one name; more than one throws input_error.

dataframe fincore::cumulative_returns(const dataframe &returns, double starting_value, std::string_view column = returns_column)

Returns the cumulative growth of column scaled by starting_value.

Each output value is starting_value * wealth when starting_value is nonzero and wealth - 1 when it is zero. NaN returns are treated as zero.

dataframe fincore::cumulative_returns_less_costs(const dataframe &returns, std::string_view returns_column_name = returns_column)

Returns cumulative_returns of returns_column_name.

This overload applies no cost deduction.

dataframe fincore::cumulative_returns_less_costs(const dataframe &returns, const dataframe &costs, std::string_view returns_column_name = returns_column, std::string_view costs_column_name = returns_column)

Returns cumulative growth after subtracting per-date costs.

returns and costs must have identical indexes; each return is reduced by the cost in the same row before the result is compounded with cumulative_returns. Mismatched indexes, malformed columns or invalid selectors raise input_error.

daily_consecutive_statistics fincore::daily_consecutive_stats(const dataframe &returns, std::string_view column = returns_column)

Returns the daily run statistics of column.

NaN values break a run. An empty input yields all-NaN fields; the gain/loss fields are also NaN when no run of that sign exists, while the run lengths are 0.

double fincore::deflated_sharpe_ratio(const dataframe &returns, double risk_free = 0.0, int num_trials = 1, std::string_view column = returns_column)

Returns the deflated Sharpe ratio probability for num_trials trials.

NaN observations are dropped and the per-period Sharpe ratio uses a one-degree-of-freedom deviation. The trial-adjusted benchmark uses the sample skewness and kurtosis and the expected maximum of num_trials independent trials (values below one are treated as one). The result is the standard normal CDF of the observed excess over that benchmark, or exactly 0 or 1 when the estimated Sharpe variance is non-positive. Fewer than three usable values yield NaN. An embedded NUL in column raises input_error.

std::pair<double, double> fincore::down_alpha_beta(const dataframe &returns, const dataframe &benchmark, double risk_free = 0.0, double annualization = daily_annualization, std::string_view returns_column_name = returns_column, std::string_view benchmark_column_name = benchmark_column)

Returns {alpha, beta} using only dates with non-positive benchmark returns (zero is included).

Otherwise follows alpha_beta.

double fincore::down_capture(const dataframe &returns, const dataframe &benchmark, double annualization = daily_annualization, std::string_view returns_column_name = returns_column, std::string_view benchmark_column_name = benchmark_column)

Returns the capture ratio on dates where the benchmark return is strictly negative.

Follows capture on the selected common finite pairs. Returns NaN when no date qualifies or the ratio is not finite.

double fincore::down_capture_return(const dataframe &returns, const dataframe &benchmark, double annualization = daily_annualization, std::string_view returns_column_name = returns_column, std::string_view benchmark_column_name = benchmark_column)

Returns the annualized geometric return of returns on dates where the benchmark return is strictly negative.

Matches up_capture_return apart from selecting dates where the benchmark return is negative, including its NaN and -1 outcomes and its embedded-NUL input_error.

double fincore::downside_risk(const dataframe &returns, double required_return = 0.0, double annualization = daily_annualization, std::string_view column = returns_column)

Returns the annualized downside deviation relative to required_return.

The result is the square root of the mean squared shortfall over all finite observations, scaled by sqrt(annualization). Returns NaN when there are no finite values or annualization is negative or non-finite.

dataframe fincore::drawdown_series(const dataframe &returns, std::string_view column = returns_column)

Returns the underwater series of column: wealth relative to its running peak, as a non-positive fraction.

NaN returns are treated as zero and the result is stored under returns_column. An empty frame yields an empty frame.

dataframe fincore::ensure_datetime_index_series(const dataframe &frame, frequency period = frequency::daily)

Validates and normalizes a frame’s UTC-nanosecond index.

A present index must be strictly increasing, duplicate-free and free of the NaT sentinel; the returned frame is an owning copy. A frame without an index but with exactly one double column receives the synthetic UTC index the source builds from pd.date_range("1970-01-01", periods=n, freq=...): daily -> 1970-01-01, 1970-01-02, … weekly -> Sundays from 1970-01-04 monthly -> month ends from 1970-01-31 quarterly -> quarter ends from 1970-03-31 yearly -> 1970-12-31, 1971-12-31, … An empty frame is returned unchanged. Frames with several columns and no index, or with a length mismatch between columns and index, raise input_error; the source silently regenerates an index for an unindexed Series only.

dataframe fincore::ensure_datetime_index_series(std::span<const double> values, frequency period = frequency::daily, std::string_view column = returns_column)

Returns a single-column frame with a synthetic index built from values.

An empty span returns an empty frame. Values are copied verbatim (NaN retained).

double fincore::excess_sharpe(const dataframe &returns, const dataframe &benchmark, std::string_view returns_column_name = returns_column, std::string_view benchmark_column_name = benchmark_column)

Returns the mean active return divided by its sample deviation.

Both series are aligned on their common timestamps and only finite pairs are kept. The result is not annualized. Returns NaN when fewer than two pairs remain or the tracking error is below 1e-15.

std::vector<named_returns> fincore::extract_interesting_date_ranges(const dataframe &returns, std::string_view column = returns_column)

Returns the pinned historical crisis windows that overlap returns.

Each window is inclusive of both UTC endpoints and contributes an entry only when it holds at least one observation. Entries follow the pinned window order, while observations keep their input order and the input column name; each returned frame owns its data. An embedded NUL, empty or invalid column raises input_error.

std::vector<double> fincore::flatten(const dataframe &frame)

Returns a frame flattened to a row-major vector.

Columns are visited in source insertion order and rows in index order, so the result is row0[col0..colN], row1[col0..colN], .... Only double columns are supported; every column must have the frame index length. Present NaN cells are preserved (the native frame has no absent-cell state); a frame whose columns are longer than its index (unindexed data) raises input_error. For raw matrices use the matrix overload, which returns the stored row-major buffer.

std::vector<double> fincore::flatten(const matrix &values)

Returns a copy of the matrix’s stored row-major buffer.

dataframe fincore::forecast_cone_bootstrap(const dataframe &in_sample_returns, std::size_t num_days, std::span<const double> cone_std = default_cone_standard_deviations, double starting_value = 1.0, std::size_t num_samples = 1000U, std::uint32_t random_seed = 0U, std::string_view column = returns_column)

Returns the forecast cone by composing simulate_paths and summarize_paths.

Simulated paths use starting_value, num_samples, random_seed and column; the cone uses cone_std and starting_value. Errors are those of the two composed functions.

dataframe fincore::forecast_cone_bootstrap(const dataframe &in_sample_returns, std::size_t num_days, double cone_std, double starting_value = 1.0, std::size_t num_samples = 1000U, std::uint32_t random_seed = 0U, std::string_view column = returns_column)

Returns forecast_cone_bootstrap for a single cone width.

std::string_view fincore::frequency_name(frequency period)

Returns the canonical source name for a native frequency.

std::vector<drawdown_table_row> fincore::gen_drawdown_table(const dataframe &returns, std::size_t top = 10, std::string_view column = returns_column)

Returns top table rows for the deepest drawdown windows.

Ranking matches get_top_drawdowns. Unused rows keep a NaN net_drawdown_percent and nullopt timestamps.

std::vector<double> fincore::get_all_drawdowns(const dataframe &returns, std::string_view column = returns_column)

Returns the depth of every distinct underwater episode in chronological order.

Values are negative fractions; an episode that has not recovered is included with its current depth.

std::vector<drawdown_period> fincore::get_all_drawdowns_detailed(const dataframe &returns, std::string_view column = returns_column)

Returns every distinct underwater episode with its start, valley, recovery and durations, in chronological order.

std::vector<drawdown_window> fincore::get_top_drawdowns(const dataframe &returns, std::size_t top = 10, std::string_view column = returns_column)

Returns up to top deepest non-overlapping drawdown windows, ordered by depth.

Ties keep the earlier window. Returns an empty vector for empty input or top == 0.

gpd_risk_estimates_result fincore::gpd_risk_estimates(const dataframe &returns, double var_p = 0.01, std::string_view column = returns_column)

Returns GPD tail estimates of losses (negated negative returns), fitted by maximum likelihood with a threshold search starting at 0.2 and halving.

var_p is the tail probability. All fields are zero when there are fewer than three returns, no positive-shape fit is found, or the search does not converge.

double fincore::henriksson_merton_timing(const dataframe &returns, const dataframe &benchmark, double risk_free = 0.0, std::string_view returns_column_name = returns_column, std::string_view benchmark_column_name = benchmark_column)

Returns the Henriksson-Merton market-timing coefficient.

Fits excess_return = a + b * excess_market + c * down, where down is one when the excess market return is negative and zero otherwise, and returns c. Only finite pairs are used and risk_free is subtracted from both series. Returns NaN with fewer than ten usable pairs, a non-finite risk_free, a singular design, or a non-finite coefficient.

double fincore::historical_value_at_risk(const dataframe &returns, double cutoff = 0.05, std::string_view column = returns_column)

Returns the empirical cutoff quantile of column using linear interpolation.

No annualization is applied. Returns NaN unless every value is finite and cutoff lies in [0, 1].

double fincore::hurst_exponent(const dataframe &returns, std::string_view column = returns_column)

Returns the rescaled-range Hurst exponent of column, ignoring NaN values.

The estimate is clamped to [0, 1]. Returns NaN with fewer than eight usable values or a degenerate range/deviation.

double fincore::information_ratio(const dataframe &returns, const dataframe &benchmark, double annualization = daily_annualization, std::string_view returns_column_name = returns_column, std::string_view benchmark_column_name = benchmark_column)

Returns the annualized information ratio of returns against benchmark.

Only finite common pairs are used. The annualized mean active return is divided by the active sample deviation scaled by sqrt(annualization). Returns NaN when fewer than two pairs remain, annualization is not finite or not positive, or the annualized tracking error is zero or non-finite.

dataframe fincore::information_ratio_by_year(const dataframe &returns, const dataframe &benchmark, frequency period = frequency::daily, std::optional<double> annualization = std::nullopt, std::string_view returns_column_name = returns_column, std::string_view benchmark_column_name = benchmark_column)

Returns the information ratio per UTC calendar year in column “information_ratio”.

The two frames are aligned on their common timestamps before bucketing, and each year uses information_ratio with annualization_factor(period, annualization); non-finite pairs are dropped within their own year. Years are indexed by January 1 and sorted ascending; an empty aligned pair yields an empty typed frame. An unknown frequency without an explicit annualization raises input_error.

double fincore::kappa_three_ratio(const dataframe &returns, double mar = 0.0, std::string_view column = returns_column)

Returns the Kappa 3 ratio: the mean excess over mar divided by the cube root of the third lower partial moment.

The lower partial moment averages max(0, mar - value)^3 over the non-NaN values. Returns NaN for fewer than two usable values, a NaN mar, or a moment below 1e-30 when the mean does not exceed mar; the same degenerate case returns positive infinity when it does. An embedded NUL in column raises input_error.

double fincore::kurtosis(const dataframe &returns, std::string_view column = returns_column)

Returns the biased excess kurtosis (fourth moment over squared variance, minus 3) of column, ignoring NaN values.

Returns NaN with fewer than four usable values or zero variance.

double fincore::loss_rate(const dataframe &returns, std::string_view column = returns_column)

Returns the fraction of usable observations in column that are strictly negative.

NaN values are ignored; an empty input yields NaN.

double fincore::m_squared(const dataframe &returns, const dataframe &benchmark, double risk_free = 0.0, double annualization = daily_annualization, std::string_view returns_column_name = returns_column, std::string_view benchmark_column_name = benchmark_column)

Returns the M-squared measure of returns against benchmark.

Both series are aligned on their common timestamps. The excess annual return is rescaled by benchmark volatility over portfolio volatility and risk_free is added back. Returns NaN when fewer than two aligned returns remain, portfolio volatility is zero, negative or NaN, or benchmark volatility is NaN. An embedded NUL in either column name raises input_error.

double fincore::mar_ratio(const dataframe &returns, double annualization = daily_annualization, std::string_view column = returns_column)

Returns the annualized arithmetic mean return divided by the absolute maximum drawdown.

The mean uses the non-NaN values and is scaled by annualization rather than compounded. Returns NaN for fewer than two raw values, a maximum drawdown that is non-negative or NaN, or a non-finite quotient. An embedded NUL in column raises input_error.

double fincore::market_timing_return(const dataframe &returns, const dataframe &benchmark, double risk_free = 0.0, std::string_view returns_column_name = returns_column, std::string_view benchmark_column_name = benchmark_column)

Returns the Treynor-Mazuy coefficient times the mean squared excess market return.

Uses the same quadratic fit, finite-pair filtering and minimum of ten observations as treynor_mazuy_timing. Returns NaN under the same conditions or when the resulting product is not finite.

double fincore::max_consecutive_down_days(const dataframe &returns, std::string_view column = returns_column)

Returns the longest run of strictly negative observations in column.

Returns 0 when there is none and NaN for empty input.

std::optional<timestamp> fincore::max_consecutive_down_end_date(const dataframe &returns, std::string_view column = returns_column)

Returns the last timestamp of the longest negative run.

Ties keep the earliest run; returns nullopt when no negative run exists.

double fincore::max_consecutive_down_months(const dataframe &returns, std::string_view column = returns_column)

Returns the longest run of consecutive calendar months whose compounded return is negative.

Months without observations are skipped. Returns 0 when there is none and NaN for empty input.

std::optional<timestamp> fincore::max_consecutive_down_start_date(const dataframe &returns, std::string_view column = returns_column)

Returns the first timestamp of the longest negative run.

Ties keep the earliest run; returns nullopt when no negative run exists.

double fincore::max_consecutive_down_weeks(const dataframe &returns, std::string_view column = returns_column)

Returns the longest run of consecutive calendar weeks (ending Sunday) whose compounded return is negative.

Weeks without observations are skipped. Returns 0 when there is none and NaN for empty input.

double fincore::max_consecutive_gain(const dataframe &returns, std::string_view column = returns_column)

Returns the largest sum of returns over a consecutive positive run.

Returns NaN when no positive run exists.

double fincore::max_consecutive_loss(const dataframe &returns, std::string_view column = returns_column)

Returns the most negative sum of returns over a consecutive negative run.

Returns NaN when no negative run exists.

double fincore::max_consecutive_up_days(const dataframe &returns, std::string_view column = returns_column)

Returns the longest run of strictly positive observations in column.

Returns 0 when there is none and NaN for empty input.

std::optional<timestamp> fincore::max_consecutive_up_end_date(const dataframe &returns, std::string_view column = returns_column)

Returns the last timestamp of the longest positive run.

Ties keep the earliest run; returns nullopt when no positive run exists.

double fincore::max_consecutive_up_months(const dataframe &returns, std::string_view column = returns_column)

Returns the longest run of consecutive calendar months whose compounded return is positive.

Months without observations are skipped. Returns 0 when there is none and NaN for empty input.

std::optional<timestamp> fincore::max_consecutive_up_start_date(const dataframe &returns, std::string_view column = returns_column)

Returns the first timestamp of the longest positive run.

Ties keep the earliest run; returns nullopt when no positive run exists.

double fincore::max_consecutive_up_weeks(const dataframe &returns, std::string_view column = returns_column)

Returns the longest run of consecutive calendar weeks (ending Sunday) whose compounded return is positive.

Weeks without observations are skipped. Returns 0 when there is none and NaN for empty input.

double fincore::max_drawdown(const dataframe &returns, std::string_view column = returns_column)

Returns the deepest drawdown of column as a negative fraction.

Returns 0 when the series never declines and NaN for an empty frame.

dataframe fincore::max_drawdown_by_year(const dataframe &returns, std::string_view column = returns_column)

Returns the maximum drawdown per UTC calendar year in column “max_drawdown”.

Each year uses max_drawdown, so a year that never declines reports 0. Years are indexed by January 1 and sorted ascending. An empty frame yields an empty typed frame.

double fincore::max_drawdown_days(const dataframe &returns, std::string_view column = returns_column)

Returns the elapsed UTC days from the peak to the valley of the deepest drawdown.

Returns 0 when the series never declines and NaN for an empty frame. The value is fractional when timestamps are not midnight-aligned.

double fincore::max_drawdown_months(const dataframe &returns, std::string_view column = returns_column)

Returns max_drawdown_days divided by 30 (approximate months).

std::pair<std::optional<timestamp>, std::optional<timestamp>> fincore::max_drawdown_period(const dataframe &returns, std::string_view column = returns_column)

Returns the {peak, valley} timestamps of the maximum drawdown.

Returns {first timestamp, first timestamp} when the series never declines and {nullopt, nullopt} for an empty frame.

double fincore::max_drawdown_recovery_days(const dataframe &returns, std::string_view column = returns_column)

Returns the elapsed UTC days from the valley to the recovery of the deepest drawdown.

Returns NaN when the episode has not recovered or the frame is empty, and 0 when the series never declines.

double fincore::max_drawdown_recovery_months(const dataframe &returns, std::string_view column = returns_column)

Returns max_drawdown_recovery_days divided by 30 (approximate months).

double fincore::max_drawdown_recovery_weeks(const dataframe &returns, std::string_view column = returns_column)

Returns max_drawdown_recovery_days divided by 7 (fractional weeks).

std::optional<drawdown_window> fincore::max_drawdown_underwater(const dataframe &underwater, std::string_view column = "drawdown")

Returns the peak/valley/recovery window from a precomputed underwater series.

The source helper returns (NaT, NaT, NaT) when the series never goes below zero; the native form returns nullopt for that case and for empty/non-finite data.

double fincore::max_drawdown_weeks(const dataframe &returns, std::string_view column = returns_column)

Returns max_drawdown_days divided by 7 (fractional weeks).

std::optional<drawdown_window> fincore::max_drawdown_window(const dataframe &returns, std::string_view column = returns_column)

Returns the peak, valley and optional recovery of the deepest episode.

Returns nullopt when the series never declines or the input is empty.

double fincore::max_single_day_gain(const dataframe &returns, std::string_view column = returns_column)

Returns the largest non-NaN observation in column; NaN for empty input.

std::optional<timestamp> fincore::max_single_day_gain_date(const dataframe &returns, std::string_view column = returns_column)

Returns the timestamp of the largest non-NaN observation in column.

Ties keep the earliest occurrence; returns nullopt when no value exists.

double fincore::max_single_day_loss(const dataframe &returns, std::string_view column = returns_column)

Returns the smallest non-NaN observation in column; NaN for empty input.

std::optional<timestamp> fincore::max_single_day_loss_date(const dataframe &returns, std::string_view column = returns_column)

Returns the timestamp of the smallest non-NaN observation in column.

Ties keep the earliest occurrence; returns nullopt when no value exists.

dataframe fincore::normalize(const dataframe &values, double starting_value = 1.0, std::string_view column = returns_column)

Returns values rescaled so the first value of column equals starting_value.

A zero first value makes every output NaN; other NaN values propagate. The result keeps the input timestamps and column name.

dataframe fincore::normalize_attribution_positions(const dataframe &positions, bool positions_in_dollars = true, std::string_view cash_column_name = cash_column)

Returns positions converted to per-timestamp weights when requested.

Every double column except cash_column_name is kept; non-double columns are ignored. With positions_in_dollars, each value is divided by that row’s gross (the sum of absolute non-NaN positions), so NaN positions stay NaN and an all-zero row yields NaN weights. Otherwise values are copied. The input index and order are preserved. Duplicate timestamps, a non-double cash column, an empty asset name, or no asset columns raise input_error.

double fincore::omega_ratio(const dataframe &returns, double risk_free = 0.0, double required_return = 0.0, double annualization = daily_annualization, std::string_view column = returns_column)

Returns the Omega ratio of column above the required threshold.

Non-finite values are dropped. The per-period threshold is risk_free plus required_return converted to a per-period rate when annualization is not one. Gains above and losses below the threshold are summed and the ratio is gains over losses. Returns NaN for fewer than two usable values, a non-finite parameter, a non-positive annualization, a required return at or below -1 with an annualization other than one, or a non-positive or non-finite gain/loss sum.

frequency fincore::pandas_frequency(std::string_view period)

Maps one canonical source frequency string to the native frequency enum.

Source contract (fincore.metrics.frequencies.pandas_frequency): only the five canonical names are accepted; any other value raises ValueError with the “Period cannot be …: unknown frequency” message. The native function raises input_error with the same predicate and message text.

std::string_view fincore::pandas_frequency_alias(frequency period)

Returns the pandas resampling alias for period (D, W, ME, QE, YE).

Alias for the pinned pandas (>= 2.2) environment, exposed as provenance for the source mapping; the native code consumes the enum, not the alias string.

perf_attribution_result fincore::perf_attrib(const dataframe &returns, const dataframe &positions, const dataframe &factor_returns, const dataframe &factor_loadings, bool positions_in_dollars = true, std::string_view returns_column_name = returns_column, std::string_view cash_column_name = cash_column, std::string_view symbol_column_name = attribution_symbol_column)

Returns risk exposures and attribution from raw positions.

With positions_in_dollars, positions are first passed through normalize_attribution_positions; otherwise they are treated as weights and forwarded unchanged. The computation and errors are those of normalize_attribution_positions when used, followed by perf_attrib_core.

perf_attribution_result fincore::perf_attrib_core(const dataframe &returns, const dataframe &normalized_positions, const dataframe &factor_returns, const dataframe &factor_loadings, std::string_view returns_column_name = returns_column, std::string_view cash_column_name = cash_column, std::string_view symbol_column_name = attribution_symbol_column)

Returns risk exposures and return attribution for normalized positions.

returns, normalized_positions and factor_returns must have identical, duplicate-free indexes. Factors are all double columns of factor_returns, and at least one is required; their names must differ from the symbol column and the reserved attribution names. The attribution frame holds one contribution column per factor in factor-return column order, followed by “total_returns” (the input returns), “common_returns” (sum of finite contributions), “specific_returns” (total - common), “tilt_returns” (mean exposure per factor times that date’s factor return) and “timing_returns” (common - tilt). Both returned frames own their data.

Mismatched or duplicate indexes, missing or non-string symbol columns, missing or non-double factor columns, reserved factor names, duplicate factor-loading keys, or factor-loading keys that do not cover the positions raise input_error.

std::map<std::string, double> fincore::perf_stats(const dataframe &returns, frequency period = frequency::daily, std::string_view returns_column_name = returns_column)

Returns the standard performance statistics of returns keyed by display name.

The map always holds the thirteen keys “Annual return”, “Cumulative

returns”, “Annual volatility”, “Sharpe ratio”, “Calmar ratio”, “Stability”, “Max drawdown”, “Omega ratio”, “Sortino ratio”, “Skew”, “Kurtosis”, “Tail

ratio” and “Daily value at risk”. Period-sensitive metrics use

annualization_factor(period), while the Omega ratio is always daily annualized. The column must be a double column matching the frame index, or input_error is thrown.

std::map<std::string, double> fincore::perf_stats(const dataframe &returns, const dataframe &benchmark, frequency period = frequency::daily, std::string_view returns_column_name = returns_column, std::string_view benchmark_column_name = benchmark_column)

Returns the unary statistics plus “Alpha” and “Beta”.

The thirteen unary keys are computed as in the unary overload. Alpha and beta come from alpha_beta on the common timestamps with a zero risk-free rate and daily_annualization regardless of period. Both selectors must be double columns matching their frame indexes, or input_error is thrown.

std::vector<bootstrap_metric_stats> fincore::perf_stats_bootstrap(const dataframe &returns, std::size_t n_samples = 1000, std::uint64_t seed = 0, std::string_view column = returns_column)

Returns bootstrap distributions for the thirteen perf_stats metrics.

Each of the n_samples draws resamples returns.size() observations with replacement from column using std::mt19937_64 seeded with seed; NaN values are drawn verbatim. Period-sensitive metrics use daily_annualization. The result always has thirteen entries in the fixed bootstrap metric order; an empty input yields empty samples and NaN summaries. An embedded NUL, empty or invalid column raises input_error.

std::vector<bootstrap_metric_stats> fincore::perf_stats_bootstrap(const dataframe &returns, std::span<const std::vector<std::size_t>> draw_indices, std::string_view column = returns_column)

Returns bootstrap distributions from caller-supplied draw indices.

Each draw contains positional row indices into the original input column. Rows must have the same length as the input population. Empty input accepts only an empty draw matrix and preserves the seeded overload’s empty result. An out-of-range index or an embedded NUL in column raises input_error.

double fincore::r_cubed(const dataframe &returns, const dataframe &benchmark, std::string_view returns_column_name = returns_column, std::string_view benchmark_column_name = benchmark_column)

Returns the cube of safe_correlation.

Returns NaN when the correlation itself is NaN. Embedded NUL characters in the column names raise input_error.

double fincore::r_cubed_turtle(const dataframe &returns, double annualization = daily_annualization, std::string_view column = returns_column)

Returns the RAR/Turtle ratio: the annualized slope of NAV against time divided by the mean annual maximum drawdown magnitude.

Returns infinity when that mean drawdown is effectively zero and the annualized slope is positive; NaN when it cannot be computed.

double fincore::relative_win_rate(const dataframe &returns, const dataframe &benchmark, std::string_view returns_column_name = returns_column, std::string_view benchmark_column_name = benchmark_column)

Returns the fraction of common finite dates where the strategy return is strictly greater than the benchmark return.

double fincore::residual_risk(const dataframe &returns, const dataframe &benchmark, double risk_free = 0.0, double annualization = daily_annualization, std::string_view returns_column_name = returns_column, std::string_view benchmark_column_name = benchmark_column)

Returns the annualized sample standard deviation of the residuals from a CAPM regression of excess strategy returns on excess benchmark returns.

Returns NaN when risk_free or beta is not finite.

dataframe fincore::roll_alpha(const dataframe &returns, const dataframe &benchmark, std::size_t window = 252U, double risk_free = 0.0, frequency period = frequency::daily, std::optional<double> annualization = std::nullopt, std::string_view returns_column_name = returns_column, std::string_view benchmark_column_name = benchmark_column)

Returns the rolling annualized Jensen alpha at each window end.

Windows are built from the common timestamps of both frames and delegated to alpha with risk_free and annualization_factor(period, annualization). Every complete window produces one row indexed by the aligned window end; fewer than window aligned observations, including no common timestamps, produce an empty typed frame. A zero window, an unknown frequency without an explicit annualization, or an invalid column raises input_error.

dataframe fincore::roll_alpha_beta(const dataframe &returns, const dataframe &benchmark, std::size_t window = 252U, double risk_free = 0.0, frequency period = frequency::daily, std::optional<double> annualization = std::nullopt, std::string_view returns_column_name = returns_column, std::string_view benchmark_column_name = benchmark_column)

Returns the rolling alpha and beta in the columns “alpha” and “beta”.

Windows are built from the common timestamps and delegated to alpha_beta with risk_free and annualization_factor(period, annualization); windows that alpha_beta cannot fit keep NaN values in both columns. Every complete window produces one row indexed by the aligned window end; fewer than window aligned observations produce an empty two-column frame. A zero window, or an unknown frequency without an explicit annualization, raises input_error.

dataframe fincore::roll_annual_volatility(const dataframe &returns, std::size_t window, frequency period = frequency::daily, std::optional<double> annualization = std::nullopt, std::string_view column = returns_column)

Returns the annualized rolling volatility at each window end.

Unlike rolling_volatility, rows exist only for complete windows and NaN observations are dropped by annual_volatility inside each window, so a poisoned window is still evaluated when at least two finite observations remain. Fewer than window observations produce an empty typed frame. A zero window, or an unknown frequency without an explicit annualization, raises input_error.

dataframe fincore::roll_beta(const dataframe &returns, const dataframe &benchmark, std::size_t window = 252U, double risk_free = 0.0, frequency period = frequency::daily, std::optional<double> annualization = std::nullopt, std::string_view returns_column_name = returns_column, std::string_view benchmark_column_name = benchmark_column)

Returns the rolling OLS beta at each window end.

Beta is not annualized, so period and annualization are ignored. Windows are built from the common timestamps and delegated to beta with risk_free. Every complete window produces one row indexed by the aligned window end; fewer than window aligned observations produce an empty typed frame. A zero window or an invalid column raises input_error.

dataframe fincore::roll_down_capture(const dataframe &returns, const dataframe &benchmark, std::size_t window = 252U, frequency period = frequency::daily, std::string_view returns_column_name = returns_column, std::string_view benchmark_column_name = benchmark_column)

Returns the rolling down-capture ratio at each window end.

Every complete window of the common timestamps is delegated to down_capture with the annualization_factor(period) factor, so period is the only annualization control. Fewer than window aligned observations produce an empty typed frame. A zero window or an unknown frequency raises input_error.

dataframe fincore::roll_max_drawdown(const dataframe &returns, std::size_t window = 252U, std::string_view column = returns_column)

Returns the maximum drawdown at each window end.

Wealth restarts at 1 for every window and NaN observations never update the running peak or the result, so a poisoned window reports 0.0 when no finite drawdown is seen. Every complete window produces one row indexed by its last timestamp; fewer than window observations produce an empty typed frame. A zero window raises input_error.

dataframe fincore::roll_sharpe_ratio(const dataframe &returns, std::size_t window = 252U, double risk_free = 0.0, frequency period = frequency::daily, std::optional<double> annualization = std::nullopt, std::string_view column = returns_column)

Returns the rolling Sharpe ratio at each window end.

Every complete window of window observations produces one row, indexed by its last timestamp; fewer than window observations produce an empty typed frame. The factor is annualization_factor(period, annualization) and NaN values are dropped by sharpe_ratio. A zero window, or an unknown frequency without an explicit annualization, raises input_error.

dataframe fincore::roll_sortino_ratio(const dataframe &returns, std::size_t window, double required_return = 0.0, frequency period = frequency::daily, std::optional<double> annualization = std::nullopt, std::string_view column = returns_column)

Returns the rolling annualized Sortino ratio at each window end.

Every complete window is delegated to sortino_ratio with required_return and annualization_factor(period, annualization); NaN values are dropped by the scalar metric. Fewer than window observations produce an empty typed frame. A zero window, or an unknown frequency without an explicit annualization, raises input_error.

dataframe fincore::roll_up_capture(const dataframe &returns, const dataframe &benchmark, std::size_t window = 252U, frequency period = frequency::daily, std::string_view returns_column_name = returns_column, std::string_view benchmark_column_name = benchmark_column)

Returns the rolling up-capture ratio at each window end.

Every complete window of the common timestamps is delegated to up_capture with the annualization_factor(period) factor, so period is the only annualization control. Fewer than window aligned observations produce an empty typed frame. A zero window or an unknown frequency raises input_error.

dataframe fincore::roll_up_down_capture(const dataframe &returns, const dataframe &benchmark, std::size_t window = 252U, frequency period = frequency::daily, std::string_view returns_column_name = returns_column, std::string_view benchmark_column_name = benchmark_column)

Returns the rolling up-down capture ratio at each window end.

Every complete window of the common timestamps is delegated to up_down_capture with the annualization_factor(period) factor, so period is the only annualization control. Fewer than window aligned observations produce an empty typed frame. A zero window or an unknown frequency raises input_error.

dataframe fincore::rolling_beta(const dataframe &returns, const dataframe &factor_returns, std::size_t rolling_window = 126U, std::string_view returns_column_name = returns_column, std::string_view factor_column_name = benchmark_column)

Returns the rolling beta of returns against one factor column.

The result keeps the aligned common index; rows before the first complete window are NaN, as is any window with a non-finite value or zero factor variance. Beta is the covariance over factor variance, with no risk-free adjustment. A zero rolling_window or an invalid column raises input_error.

dataframe fincore::rolling_beta(const dataframe &returns, const dataframe &factor_returns, std::span<const std::string_view> factor_column_names, std::size_t rolling_window = 126U, std::string_view returns_column_name = returns_column)

Returns one rolling beta column per factor, named after the factor columns.

Each factor is aligned against returns separately using the same warm-up and invalid-window rules as the single-factor overload. Factor names must be non-empty, unique and present as double columns; otherwise input_error is raised, as it is for a zero rolling_window.

dataframe fincore::rolling_regression(const dataframe &returns, const dataframe &benchmark, std::size_t rolling_window = 126U, std::string_view returns_column_name = returns_column, std::string_view benchmark_column_name = benchmark_column)

Returns the rolling OLS alpha and beta in the columns “alpha” and “beta”.

Windows are built from the common timestamps; beta is the covariance over factor variance and alpha is the mean return minus beta times the mean benchmark return, with no risk-free adjustment. Rows are indexed by the aligned window end, and windows with a non-finite value, zero factor variance or non-finite coefficients are omitted, so the index can be sparse. A zero rolling_window or an invalid column raises input_error.

dataframe fincore::rolling_sharpe(const dataframe &returns, std::size_t window, frequency period = frequency::daily, std::optional<double> annualization = std::nullopt, std::string_view column = returns_column)

Returns the rolling Sharpe ratio aligned to the input index.

The index and warm-up shape match rolling_volatility, the risk-free rate is zero, and a window containing NaN yields NaN. The factor is annualization_factor(period, annualization). A zero window, or an unknown frequency without an explicit annualization, raises input_error.

dataframe fincore::rolling_volatility(const dataframe &returns, std::size_t window, frequency period = frequency::daily, std::optional<double> annualization = std::nullopt, std::string_view column = returns_column)

Returns the annualized rolling volatility aligned to the input index.

Rows before the first complete window are NaN, and a window containing NaN yields NaN. Each window uses annual_volatility with annualization_factor(period, annualization). Fewer than window observations produce all-NaN values. A zero window, or an unknown frequency without an explicit annualization, raises input_error.

double fincore::safe_correlation(const dataframe &returns, const dataframe &benchmark, std::string_view returns_column_name = returns_column, std::string_view benchmark_column_name = benchmark_column)

Returns the Pearson correlation of the two series on their common finite timestamps.

Returns NaN with fewer than two pairs or a near-zero standard deviation in either series.

double fincore::second_max_drawdown(const dataframe &returns, std::string_view column = returns_column)

Returns the second-deepest drawdown as a negative fraction.

Returns NaN when fewer than two episodes exist.

double fincore::second_max_drawdown_days(const dataframe &returns, std::string_view column = returns_column)

Returns the elapsed UTC days from the first underwater observation to the valley of the second-deepest drawdown.

Returns NaN when fewer than two episodes exist.

double fincore::second_max_drawdown_recovery_days(const dataframe &returns, std::string_view column = returns_column)

Returns the elapsed UTC days from the valley to the recovery of the second-deepest drawdown.

Returns NaN when that episode has not recovered or fewer than two episodes exist.

double fincore::serial_correlation(const dataframe &returns, std::ptrdiff_t lag = 1, std::string_view column = returns_column)

Returns the Pearson autocorrelation of column at lag observations.

NaN values are removed before lagging. Returns NaN when lag is less than one or fewer than lag + 2 values remain.

double fincore::sharpe_ratio(const dataframe &returns, double risk_free = 0.0, double annualization = daily_annualization, std::string_view column = returns_column)

Returns the annualized Sharpe ratio of column.

NaN observations are dropped; the mean of value - risk_free is divided by the sample standard deviation (one degree of freedom) and scaled by sqrt(annualization). Fewer than two usable observations yield NaN, and a zero deviation is not special-cased, so the quotient follows IEEE rules.

dataframe fincore::sharpe_ratio_by_year(const dataframe &returns, double risk_free = 0.0, frequency period = frequency::daily, std::optional<double> annualization = std::nullopt, std::string_view column = returns_column)

Returns the Sharpe ratio per UTC calendar year in column “sharpe_ratio”.

Each year uses sharpe_ratio with risk_free and annualization_factor(period, annualization). Years are indexed by January 1 and sorted ascending. An empty frame yields an empty typed frame, and an unknown frequency without an explicit annualization raises input_error.

dataframe fincore::simple_returns(const dataframe &prices, std::string_view price_column)

Returns the period-over-period simple returns of price_column, dropping the first observation.

The result is stored under returns_column and keeps the timestamp of the later price of each pair. Fewer than two prices yield an empty frame.

matrix fincore::simulate_paths(std::span<const double> in_sample_returns, std::size_t num_days, double starting_value = 1.0, std::size_t num_samples = 1000U, std::uint32_t random_seed = 0U)

Draws num_samples x num_days bootstrap returns from in-sample returns.

RNG: NumPy legacy RandomState-compatible MT19937 with the explicit 32-bit random_seed (default 0; the source default None is not reproduced). Integer draws use NumPy’s masked rejection (rk_interval), so seeded sequences match np.random.RandomState(seed).randint(0, n, size=...) exactly for 32-bit seeds. This is not a claim about array seeding or the source’s non-deterministic None default.

starting_value is accepted for source-signature parity and ignored, as in the source (_starting_value). NaN in-sample values are drawn verbatim; infinite values raise input_error. An empty population with a non-empty draw shape raises input_error (source rng.randint(0, 0, ...) raises ValueError), as do an unrepresentable shape and a population larger than a 32-bit draw index.

matrix fincore::simulate_paths(const dataframe &in_sample_returns, std::size_t num_days, double starting_value = 1.0, std::size_t num_samples = 1000U, std::uint32_t random_seed = 0U, std::string_view column = returns_column)

Returns the same paths as the span overload, selecting one double column.

A source DataFrame with several columns would make NumPy indexing return a 3-D array; that shape is not representable, so callers select the column to bootstrap.

double fincore::skewness(const dataframe &returns, std::string_view column = returns_column)

Returns the biased (population) skewness of column, ignoring NaN values.

Returns NaN with fewer than three usable values or zero variance.

double fincore::sortino_ratio(const dataframe &returns, double required_return = 0.0, double annualization = daily_annualization, std::string_view column = returns_column)

Returns the annualized Sortino ratio of column.

NaN observations are dropped; the annualized mean excess over required_return is divided by the annualized downside deviation, where only negative excess values contribute and the squared deviations divide by the number of usable observations. Fewer than two raw values, or no usable observation, yield NaN, and a zero downside deviation is not special-cased.

double fincore::stability_of_timeseries(const dataframe &returns, std::string_view column = returns_column)

Returns the R-squared of cumulative log returns regressed on a linear time index.

Non-finite values are dropped and log1p is applied to each; the cumulative sum is regressed on the observation position. Returns NaN for fewer than two usable values, a non-finite log return (for example a return at or below -1), an overflowing cumulative sum, or zero explained variance.

double fincore::sterling_ratio(const dataframe &returns, double risk_free = 0.0, double annualization = daily_annualization, std::string_view column = returns_column)

Returns the annual return in excess of risk_free divided by the average drawdown magnitude.

The average drawdown is the absolute mean of all drawdown depths; with no drawdowns or only zeros it falls back to the absolute mean of the negative returns (or the absolute population deviation of all usable values when none are negative). The annual return is compounded at annualization. Returns positive infinity when the denominator is below 1e-10 and the excess annual return is positive, NaN otherwise, and NaN for fewer than two raw values or no usable value. An embedded NUL in column raises input_error.

double fincore::stutzer_index(const dataframe &returns, double target_return = 0.0, std::string_view column = returns_column)

Returns the Stutzer index of column relative to target_return.

NaN values are ignored; fewer than two usable values yield NaN. Returns 0 when the mean excess return is effectively zero.

dataframe fincore::summarize_paths(const matrix &samples, std::span<const double> cone_std = default_cone_standard_deviations, double starting_value = 1.0)

Returns the mean/standard-deviation cone from sampled paths.

samples is row-major with one row per path and one column per day. Source formula: cumulative wealth per path from cumprod(1 + returns) with NaN returns as 0; bounds mean + std * num_std and mean - std * num_std for each requested width, with sample standard deviation (ddof = 1). starting_value == 0 yields cumprod - 1; otherwise the wealth is scaled by starting_value, matching cum_returns. Columns are ordered upper then lower for each width, named cone_<shortest-round-trip-width> (for example cone_1, cone_-1, cone_1.5); Python float labels are represented as native strings, not claimed identical. Duplicate widths raise input_error.

An empty cone_std span returns an empty frame; a non-finite width, a non-finite starting_value or an infinite sampled return raises input_error.

dataframe fincore::summarize_paths(const matrix &samples, double cone_std, double starting_value = 1.0)

Returns the cone for a single standard-deviation width.

Equivalent to the span overload with a one-element width list.

double fincore::tail_ratio(const dataframe &returns, std::string_view column = returns_column)

Returns the ratio of the absolute 95th percentile to the absolute 5th percentile of the finite values in column.

Returns NaN for empty input or a zero lower tail.

double fincore::third_max_drawdown(const dataframe &returns, std::string_view column = returns_column)

Returns the third-deepest drawdown as a negative fraction.

Returns NaN when fewer than three episodes exist.

double fincore::third_max_drawdown_days(const dataframe &returns, std::string_view column = returns_column)

Returns the elapsed UTC days from the first underwater observation to the valley of the third-deepest drawdown.

Returns NaN when fewer than three episodes exist.

double fincore::third_max_drawdown_recovery_days(const dataframe &returns, std::string_view column = returns_column)

Returns the elapsed UTC days from the valley to the recovery of the third-deepest drawdown.

Returns NaN when that episode has not recovered or fewer than three episodes exist.

dataframe fincore::to_dataframe(std::span<const double> values, std::string_view column = returns_column)

Returns a frame representation of the input, replacing the source to_pandas route.

Native replacement for the source to_pandas route (route kept under the name fincore.metrics.basic.to_pandas in the ledger, implementation named to_dataframe). pandas/NumPy identity is not claimed.

1-D input becomes a single-column frame with a positional 0..n-1 index (the native analogue of a RangeIndex); 2-D input becomes one double column per matrix column named “<prefix>_0”, “<prefix>_1”, … with the same positional index. An existing frame is returned as an owning copy. Inputs with more than two dimensions cannot be represented by matrix, so the source ValueError branch has no native equivalent.

dataframe fincore::to_dataframe(const matrix &values, std::string_view column_prefix = "column")

Returns a frame with one double column per matrix column, named “<column_prefix>_0”, “<column_prefix>_1”, … and a positional index.

dataframe fincore::to_dataframe(const dataframe &frame)

Returns an owning copy of frame.

double fincore::tracking_difference(const dataframe &returns, const dataframe &benchmark, std::string_view returns_column_name = returns_column, std::string_view benchmark_column_name = benchmark_column)

Returns the cumulative strategy return minus the cumulative benchmark return over their common finite timestamps.

Returns NaN when no common finite pair exists.

double fincore::tracking_error(const dataframe &returns, const dataframe &benchmark, double annualization = daily_annualization, std::string_view returns_column_name = returns_column, std::string_view benchmark_column_name = benchmark_column)

Returns the annualized sample standard deviation of active returns (strategy minus benchmark) on common finite timestamps.

Returns NaN with fewer than two pairs, non-finite/negative annualization, or a non-finite variance.

double fincore::trading_value_at_risk(const dataframe &returns, double sigma = 2.0, std::string_view column = returns_column)

Returns mean - sigma * sample_standard_deviation of column.

Requires at least two values; NaN observations propagate to the result.

double fincore::treynor_mazuy_timing(const dataframe &returns, const dataframe &benchmark, double risk_free = 0.0, std::string_view returns_column_name = returns_column, std::string_view benchmark_column_name = benchmark_column)

Returns the Treynor-Mazuy market-timing coefficient.

Fits excess_return = a + b * excess_market + c * excess_market^2 by ordinary least squares on the common timestamps and returns c. Only finite pairs are used and risk_free is subtracted from both series. Returns NaN with fewer than ten usable pairs, a non-finite risk_free, a singular design, or a non-finite coefficient.

std::pair<double, double> fincore::up_alpha_beta(const dataframe &returns, const dataframe &benchmark, double risk_free = 0.0, double annualization = daily_annualization, std::string_view returns_column_name = returns_column, std::string_view benchmark_column_name = benchmark_column)

Returns {alpha, beta} using only dates with strictly positive benchmark returns.

Otherwise follows alpha_beta.

double fincore::up_capture(const dataframe &returns, const dataframe &benchmark, double annualization = daily_annualization, std::string_view returns_column_name = returns_column, std::string_view benchmark_column_name = benchmark_column)

Returns the capture ratio on dates where the benchmark return is strictly positive.

Follows capture on the selected common finite pairs. Returns NaN when no date qualifies or the ratio is not finite.

double fincore::up_capture_return(const dataframe &returns, const dataframe &benchmark, double annualization = daily_annualization, std::string_view returns_column_name = returns_column, std::string_view benchmark_column_name = benchmark_column)

Returns the annualized geometric return of returns on dates where the benchmark return is strictly positive.

Only common timestamps are considered. Selected NaN returns are treated as zero when compounding. Returns NaN when no date qualifies and -1 when the compounded wealth is non-positive. An embedded NUL in either column name raises input_error.

double fincore::up_down_capture(const dataframe &returns, const dataframe &benchmark, double annualization = daily_annualization, std::string_view returns_column_name = returns_column, std::string_view benchmark_column_name = benchmark_column)

Returns up_capture divided by down_capture.

Both components use the common finite pairs. Returns NaN when either component is non-finite or the down capture is zero.

double fincore::var_cov_var_normal(double portfolio_value, double confidence, double mean = 0.0, double standard_deviation = 1.0)

Returns the variance-covariance value at risk under a normal assumption: portfolio_value * (z(confidence) * standard_deviation - mean).

No annualization is applied. Returns NaN when confidence is outside [0, 1] or standard_deviation is not positive.

double fincore::var_excess_return(const dataframe &returns, double cutoff = 0.05, double risk_free = 0.0, frequency period = frequency::daily, std::optional<double> annualization = std::nullopt, std::string_view column = returns_column)

Returns the annualized return in excess of risk_free, divided by the absolute historical value at risk at cutoff.

The return is annualized with

annualization_factor(period,

annualization)

. Returns NaN with fewer than two values, a zero or NaN value at risk, or a NaN annual return.

double fincore::win_rate(const dataframe &returns, std::string_view column = returns_column)

Returns the fraction of usable observations in column that are strictly positive.

NaN values are ignored; an empty input yields NaN.

constexpr std::string_view fincore::attribution_common_returns_column{"common_returns"}

Attribution column holding the sum of finite factor contributions.

constexpr std::string_view fincore::attribution_factor_column = {"factor"}

Column holding factor names in the factor summary frame.

constexpr std::string_view fincore::attribution_specific_returns_column{"specific_returns"}

Attribution column holding the residual return (total - common).

constexpr std::string_view fincore::attribution_symbol_column = {"symbol"}

Default factor-loading column holding asset symbols.

constexpr std::string_view fincore::attribution_tilt_returns_column{"tilt_returns"}

Attribution column holding the sum of mean-exposure tilt contributions.

constexpr std::string_view fincore::attribution_timing_returns_column{"timing_returns"}

Attribution column holding the timing return (common - tilt).

constexpr std::string_view fincore::attribution_total_returns_column{"total_returns"}

Attribution column holding the total (input) returns.

constexpr std::string_view fincore::bayesian_cone_percentile_1_column = {"percentile_1"}

Result column holding the 1st percentile of cumulative path wealth.

constexpr std::string_view fincore::bayesian_cone_percentile_25_column = {"percentile_25"}

Result column holding the 25th percentile of cumulative path wealth.

constexpr std::string_view fincore::bayesian_cone_percentile_50_column = {"percentile_50"}

Result column holding the 50th percentile of cumulative path wealth.

constexpr std::string_view fincore::bayesian_cone_percentile_5_column = {"percentile_5"}

Result column holding the 5th percentile of cumulative path wealth.

constexpr std::string_view fincore::bayesian_cone_percentile_75_column = {"percentile_75"}

Result column holding the 75th percentile of cumulative path wealth.

constexpr std::string_view fincore::bayesian_cone_percentile_95_column = {"percentile_95"}

Result column holding the 95th percentile of cumulative path wealth.

constexpr std::string_view fincore::bayesian_cone_percentile_99_column = {"percentile_99"}

Result column holding the 99th percentile of cumulative path wealth.

constexpr std::string_view fincore::benchmark_column = {"benchmark"}

Default name of the column holding benchmark returns.

constexpr double fincore::daily_annualization = 252.0

Default annualization factor for daily observations (252 periods per year).

constexpr std::string_view fincore::daily_frequency_name = {"daily"}

Canonical source name of the daily frequency (“daily”).

constexpr std::array<double, 3U> fincore::default_cone_standard_deviations{1.0, 1.5, 2.0}

Default cone widths (1.0, 1.5 and 2.0 standard deviations).

constexpr std::string_view fincore::monthly_frequency_name = {"monthly"}

Canonical source name of the monthly frequency (“monthly”).

constexpr std::string_view fincore::quarterly_frequency_name = {"quarterly"}

Canonical source name of the quarterly frequency (“quarterly”).

constexpr std::string_view fincore::return_aggregation_period_column = {"period"}

Name of the integer period-within-year column added by aggregate_returns.

constexpr std::string_view fincore::return_aggregation_year_column = {"year"}

Name of the integer calendar-year column added by aggregate_returns.

constexpr std::string_view fincore::returns_column = {"returns"}

Default name of the column holding periodic simple returns.

constexpr std::string_view fincore::weekly_frequency_name = {"weekly"}

Canonical source name of the weekly frequency (“weekly”).

constexpr std::string_view fincore::yearly_frequency_name = {"yearly"}

Canonical source name of the yearly frequency (“yearly”).