快速开始

本演练从一个小型内存帧走到一份渲染好的报表。数值是合成的,不下载行情数据;其中任何内容都不构成投资结论。

构建收益序列并读取几个数字

#include <fincore/dataframe.hpp>
#include <fincore/metrics/drawdown.hpp>
#include <fincore/metrics/ratios.hpp>

#include <cstdio>
#include <vector>

int main() {
    const std::vector<fincore::timestamp> index{
        fincore::utc_timestamp(2025, 1, 2),
        fincore::utc_timestamp(2025, 1, 3),
        fincore::utc_timestamp(2025, 1, 6),
        fincore::utc_timestamp(2025, 1, 7)};

    fincore::dataframe returns;
    returns.load_index(std::vector<fincore::timestamp>(index));
    returns.load_column<double>(
        "returns", {0.012, -0.02, 0.005, 0.03},
        hmdf::nan_policy::dont_pad_with_nans);

    std::printf("sharpe  %.4f\n", fincore::sharpe_ratio(returns, "returns"));
    std::printf("max dd  %.4f\n", fincore::max_drawdown(returns, "returns"));
    return 0;
}

有三点值得注意:

  • 索引是显式给出的;utc_timestamp 会校验日历日期,并拒绝无法表示的日期。

  • dont_pad_with_nans 按原样保留数值。若改用补齐策略,缺失位置会被填入 NaN。

  • 指标函数需要列名。传入不存在的列,或不是 double 的列,都会抛 input_error —— 拼写错误不会静默通过。

准备因子面板

analyze_factor 接受 prepared_factor_data:行、前瞻标签与清洗损失报告。每行的 forward_returns 必须按位置与 forward_labels 对齐。

#include <fincore/factor/analysis.hpp>

fincore::prepared_factor_data data;
data.forward_labels = {"1D", "5D"};
const auto start = fincore::utc_timestamp(2022, 1, 3);
const auto day = 86'400'000'000'000LL;
const char* assets[] = {"ALPHA", "BETA", "GAMMA", "DELTA", "EPSILON"};

for (int date = 0; date < 40; ++date) {
    for (int asset = 0; asset < 5; ++asset) {
        fincore::factor_row row;
        row.date = start + date * day;
        row.asset = assets[asset];
        row.factor = 0.01 * static_cast<double>(asset) -
                     0.02 * static_cast<double>(date % 7);
        row.quantile = asset + 1;                 // caller-supplied bucket
        row.forward_returns = {0.001 * (date % 5),
                               0.002 * (asset % 3)};  // aligned with 1D, 5D
        data.rows.push_back(row);
    }
}

fincore::factor_analysis_config config;
config.periods = {"1D", "5D"};
config.turnover_periods = {1, 5};
config.include_portfolio_inputs = false;

const auto model = fincore::analyze_factor(data, config);

quantile 由调用方给出而不是计算得到:分析研究的是你声明的分桶。periods 必须是已准备的标签,而 forward_returns 按位置与 forward_labels 对齐,所以这里的两个列表描述的是同样顺序的同样列。

渲染报表

#include <fincore/factor/report.hpp>
#include <fincore/report/html.hpp>
#include <fincore/report/xlsx.hpp>

#include <fstream>
#include <string>

const auto document = fincore::build_factor_report(model, "Factor Report");

fincore::report_html_options options;
options.language = "en";                 // "zh" for the Chinese labels
const std::string html = fincore::render_html(document, options);
const std::string workbook = fincore::render_xlsx(document);

std::ofstream{"factor_report.html", std::ios::binary} << html;
std::ofstream{"factor_report.xlsx", std::ios::binary} << workbook;

同一个 report_document 驱动所有后端。render_html 用同一模型为每种语言各产出一份文件,而 render_xlsx、render_pdf_bytes、render_plotly、render_bokeh 与 render_matplotlib 是它的其他视图。对确定性后端来说,渲染两次得到的字节完全一致,这正是报表可以被哈希与比较的原因。

在出错处处理失败

#include <fincore/error.hpp>

try {
    const double sharpe = fincore::sharpe_ratio(returns, "missing");
    (void)sharpe;
} catch (const fincore::input_error& error) {
    std::fprintf(stderr, "rejected: %s (%s)\n", error.what(),
                 error.code().c_str());
}

结构性问题会抛出异常;本身合法但无法算出该统计量的序列返回 NaN。前者用 fincore::fincore_error 捕获,后者用 std::isnan 判断。

后续步骤