架构与设计取舍
本页说明库是如何组织的,以及公共接口为什么是现在这个样子。它是参考的“为什么”伴侣,参考回答的是“是什么”。
分层
data dataframe, calendar, UTC timestamps, frequency helpers
metrics returns, ratios, drawdowns, rolling windows, statistics
domain risk, portfolio, factor, attribution, optimization
presentation report documents, HTML/XLSX/PDF/Plotly/Bokeh/axes renderers
runtime operation catalog, sessions, serialization, extensions
每一层可以使用它上面的层,绝不使用下面的层:指标不知道报表文档的存在,渲染器也从不重算统计量。这就是报表模型携带数值而不是帧的原因 —— 渲染不能改变数字,只能呈现它。
所有权
一条规则解释了大部分 API 形状:输入在边界处复制,结果拥有自己的数据。
portfolio_inputs、analysis_context与rolling_engine以按值方式接收帧并保存自己的副本,因此调用方修改源帧不会改变已经产出的结果。返回
dataframe的函数返回新帧。返回 span 或std::string_view的访问器是视图,只在其所有者存活期间有效。materialize()每次调用都返回新帧,这正是修改其结果不会破坏下一次调用的原因。
这里的取舍是分配与可预测性。对本库面向的日频与月频面板规模,可预测的所有权值得这份复制;基准框架的存在就是为了在更大规模上让这个判断保持诚实。
确定性
任何可能被比较、存储或重放的东西都被归约为摘要:
运行时序列化给每个字段加长度前缀并标注类型,因此两份不同载荷不会因为分隔符或
bool/1的歧义而碰撞。因子 fingerprint 哈希的是输入;报表摘要覆盖文档,因此渲染同一文档的两个后端在构造上一致。
字节重要的场合,浮点值按 IEEE-754 位模式序列化,从而保留
NaN载荷与带符号零。
随机方法接受显式种子。种子只在本库内复现一条流;只有文档写明参考映射的函数才承诺与 NumPy 相同的数值。
错误与退化结果
库区分三种结果,而这种区分是契约的一部分,不是实现细节:
结果 |
含义 |
例子 |
|---|---|---|
异常( |
请求在结构上是错的 |
列缺失、索引重复、内嵌 NUL |
|
请求本身合法,但统计量无定义 |
观测太少、零偏差、无回撤 |
状态字段 |
拟合或校验确实运行并给出自己的结论 |
|
状态字段存在的意义,正是让调用方不必从一个看起来合理的数字里推断失败。
并发
自由函数是无状态的:相同参数产生相同结果,两个线程可以在各自帧上并发调用。有状态对象是显式的:
analysis_context会缓存结果,因此单个实例在构造上就是单线程的。analysis_session拥有自己的输入与输出,并在close()之后拒绝调用。rolling_engine拥有副本,一次只宜在一个线程中使用。
没有隐藏的线程池、没有全局缓存、没有后台工作:一次调用在返回之前就完成了它承诺的事。
扩展点
扩展点 |
契约 |
|---|---|
数据提供者 |
返回帧的可调用对象;校验与摘要仍留在库内 |
报表后端 |
渲染器消费 |
扩展 |
原生快照/hook ABI:hook 携带事件与优先级,并列时按 fingerprint 决定 |
操作 |
描述符列出输入与输出;执行返回带类型的 |
可选接口保持可选:ImPlot 适配器及其 GUI 依赖永远不会出现在核心 fincore::fincore 路径上。
刻意不提供的东西
不承诺 Python 对象兼容。
fincore::dataframe是自己的类型;parity 工作关注数值,而不是对象身份。没有隐式数据访问。 不会在你不知情时下载价格序列或读文件;I/O 是调用方的决定。
不做单位换算。 收益是小数分数,年化是参数,权重沿用调用处的含义。
不做静默修复。 重复索引会被拒绝而不是去重;缺失观测是
NaN,不是 0。
文档如何映射这一点
站点遵循同样的划分:教程与配方集展示用法,指南解释行为,参考由头文件生成,本页解释结构。发布状态存放在仓库中,从不在首页上断言。