Metadata-Version: 2.4
Name: betaquant
Version: 0.5.5
Classifier: Programming Language :: Rust
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
Requires-Dist: numpy>=2
License-File: LICENSE
Summary: Alpha Library: A high-performance rolling window calculation library implemented in Rust with Python bindings. Used for financial data analysis and factor research.
Keywords: financial data analysis,factor research,technical indicator calculation
Author: ZhaoJun
License-Expression: BSD-2-Clause
Requires-Python: >=3.11
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
Project-URL: repository, https://home.zhaojun.com

# betaquant

高性能量化金融算子库。Rust 实现 + Python 绑定 ([PyO3](https://pyo3.rs/))。

提供因子量化交易中常用的滚动窗口高效计算。

项目源：py-alpha-lib

## 安装

```bash
pip install betaquant
```

## 使用

### 上下文设置

通过 `betaquant.set_ctx()` 控制计算行为：

- **`groups`** — 数据数组中的标的数量。每个 group 独立并行处理。`cc_rank` 等截面算子要求 `groups >= 2`。
- **`flags`** — 位标志：
  - `FLAG_SKIP_NAN` (1)：滚动窗口中跳过 NaN。
  - `FLAG_STRICTLY_CYCLE` (2)：窗口未填满前返回 NaN（与 pandas `rolling()` 默认行为一致）。
  - 用 `|` 组合：`flags=FLAG_SKIP_NAN | FLAG_STRICTLY_CYCLE`

`set_ctx` 只更新传入的字段，其它字段保持当前值。要恢复默认 (`groups=0, flags=0`) 调用 `betaquant.reset_ctx()`。

如需只算某一段，请在调用前自行切片输入数组（如 `betaquant.ts_ma(data[start:end], 3)`）。

### NaN 处理合约（所有滑窗算子）

| 输入条件 | 默认 | `FLAG_SKIP_NAN` | `FLAG_STRICTLY_CYCLE` | 两者同时 |
|---|---|---|---|---|
| `periods == 0` | 全 NaN（除累计语义算子） | 全 NaN | 全 NaN | 全 NaN |
| 当前位置是 NaN | NaN | NaN | NaN | NaN |
| 窗口含 NaN（当前有效） | NaN | 跳过窗口里的 NaN，对剩余有效值算 | NaN | 有效值数 < periods → NaN |
| `i + 1 < periods`（窗口未填满） | partial 输出 | partial 输出 | NaN | NaN |

`FLAG_SKIP_NAN` 用 **fixed-time-slot** 语义：窗口固定 `periods` 个时间槽，里面的 NaN 被跳过。
**不是** "expand-window 找最近 N 个有效值"。

少数算子有特殊"最少有效值"门槛：`var/stddev/zscore` ≥ 2，`skewness` ≥ 3，`kurtosis` ≥ 4，
不满足时仍返回 NaN。

非滑窗算子（`ts_ema / ts_lwma / ts_dma / ts_sma / ts_sumbars / ts_ref / ts_barslast` 等）
有自己的 NaN 语义，详见各算子文档。

### 截面算子（`cc_*`）的 NaN 处理

上表只适用于**滑窗算子**。截面算子（`cc_rank / cc_zscore / cc_neutralize / cc_bins /
cc_group_rank / cc_group_zscore`）跨 group 维度计算，没有滚动窗口概念，
**不受 `FLAG_SKIP_NAN` / `FLAG_STRICTLY_CYCLE` 影响**，固定按下面的规则处理：

- 某个位置输入为 NaN ⇒ 该位置输出 NaN；
- 计算截面统计量（均值 / 标准差 / 排名 / 分箱）时**自动剔除** NaN，只用有效值；
- 有效值不足时返回 NaN（如 `cc_zscore` / `cc_group_zscore` 需要 ≥ 2 个有效值，
  否则该截面 / 组输出 NaN）。

也就是说截面算子始终是"跳过 NaN"的语义，无需也无法通过 flag 切换。

### 算子专门约束

少数算子对输入有额外要求，不满足时返回 NaN 或报错：

- **`ts_max_drawdown`** — 输入应为**严格正**的价格 / 权益曲线。
  窗口里出现 ≤ 0 的 peak 时返回 NaN（百分比回撤无定义），不会静默返回 0。
- **`ts_dma` / `ts_sma`** — `weight` / `alpha` 必须是 `[0, 1]` 内的有限值；
  `NaN`、`±inf`、`ts_sma(n=0, ...)` 都会报 `InvalidParameter`，而不是产生 NaN 输出。

  ```python
  import betaquant
  import numpy as np

  data = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], dtype=np.float64)

  # 3 周期均线（预热阶段返回部分窗口结果）
  result = betaquant.ts_ma(data, 3)
  # [1.  1.5 2.  3.  4.  5.  6.  7.  8.  9.]

  # 严格模式：窗口填满前返回 NaN
  betaquant.set_ctx(flags=betaquant.FLAG_STRICTLY_CYCLE)
  result = betaquant.ts_ma(data, 3)
  # [nan nan 2.  3.  4.  5.  6.  7.  8.  9.]

  # 跳过 NaN（fixed-time-slot：窗口里跳过 NaN，剩余有效值算均值）
  betaquant.set_ctx(flags=betaquant.FLAG_SKIP_NAN)
  data_nan = np.array([1, 2, np.nan, 4, 5, 6, 7, 8, 9, 10], dtype=np.float64)
  result = betaquant.ts_ma(data_nan, 3)
  # [1.   1.5   nan 3.   4.5  5.   6.   7.   8.   9. ]
  # i=2 当前是 NaN ⇒ NaN；i=3 窗口 [2,NaN,4] valid [2,4] mean=3.0；
  # i=4 窗口 [NaN,4,5] valid [4,5] mean=4.5；之后窗口无 NaN，恢复正常。
  ```

### 命名规范

- 时序算子（rolling-window）以 `ts_` 开头：`ts_ma`、`ts_sum`、`ts_delta`、`ts_rank` 等
- 截面算子（cross-sectional，跨 group）以 `cc_` 开头：`cc_rank`、`cc_zscore`、`cc_neutralize` 等
- 元素级算子（max/min/abs/log/sign 等）无前缀


数据布局：扁平化的一维数组 `[stock1_day1, stock1_day2, ..., stockN_dayM]`，先按 securityid 再按 tradetime 排序。`groups` 参数告诉算子库每只股票的数据从哪里开始。

## 因子表达式 → Python 代码

使用 `lang` 模块把因子表达式（GTJA / WQ101 风格的 DSL）转成 Python 代码：

```bash
python -m betaquant.lang examples/wq101/alpha101.txt > factors.py
```

会读取 [`examples/wq101/alpha101.txt`](examples/wq101/alpha101.txt) 中的因子表达式，
生成对应的、调用 `betaquant` 的 Python 代码。

跑生成的因子：

```python
import betaquant
from factors import alpha_001

# 用合成面板做最小可运行例子；自己的数据用 polars.DataFrame 传入也行
data = betaquant.make_synthetic_panel(securities=50, trades=252)
ctx = betaquant.ExecContext(data)   # 自动推断 groups
result = alpha_001(ctx)
```

转译完成后可能仍需手动调整：

- 修正 `float` 与 `bool` 之间的类型转换
- 按需添加上下文设置

## 完整示例

### GTJA Alpha 191

国泰君安 Alpha 191 因子集，190 / 191 已实现，见 [`examples/gtja191/`](examples/gtja191/)。

```bash
python examples/gtja191/main.py
```

默认用 `betaquant.make_synthetic_panel()` 生成 50 股 × 252 天的合成面板，跑全部因子并打印
绩效统计。要换成自己的数据，直接调用 `examples.gtja191.al.run(data=<polars.DataFrame>,
alphas=[1, 2, 3])`。

### WorldQuant Alpha 101

完整实现 [101 Formulaic Alphas](https://arxiv.org/pdf/1601.00991.pdf)，见
[`examples/wq101/`](examples/wq101/)：

- `al/` — betaquant 实现（Rust 后端）

```bash
python examples/wq101/main.py
```

默认用合成数据跑全部 101 个因子。要换成自己的数据，编辑 `wq101/main.py` 顶部的
`DATA_FILE` 常量、或直接调用 `examples.wq101.al.run(data_path=...)` /
`run(data=<polars.DataFrame>)`。

### 已支持的算子

完整函数签名与说明：[python/betaquant/algo.md](python/betaquant/algo.md)

### 入门示例

`examples/quickstart/` 下放了几个最小可运行示例：

- `usage.py` —— 演示 `set_ctx`、各种 flag 与常见时序/截面算子
- `rank.py` —— 截面 rank 与 pandas 对比
- `verify_sumif.py` —— `ts_sumif` 行为验证
- `full_demo.py` —— 从 long-format DataFrame → `matrix_transform` → `ExecContext`
  → 因子计算 → 与 pandas 对照的端到端例子

```bash
python examples/quickstart/usage.py
```

## 开发

环境要求：

- Rust（最新 stable）
- Python 3.11+
- [maturin](https://github.com/PyO3/maturin)

```bash
# 编译并以开发模式安装
maturin develop --release
cargo build --release
# 运行 Rust 单元测试
cargo test
```


