Metadata-Version: 2.4
Name: silars
Version: 2026.8.6.0
Summary: Silars - Alpha lens and backtesting library
License-Expression: MIT
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Operating System :: OS Independent
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
License-File: LICENSES/Apache-2.0.txt
License-File: THIRD_PARTY_NOTICES.md
Requires-Dist: ipython>=9.5.0
Requires-Dist: lidb>=2026.7.15.2
Requires-Dist: logair
Requires-Dist: numpy>=2.3.1
Requires-Dist: pandas>=2.3.1
Requires-Dist: plotly>=6.3.0
Requires-Dist: matplotlib
Requires-Dist: polars
Requires-Dist: pyecharts
Requires-Dist: scikit-learn
Requires-Dist: scipy
Requires-Dist: mlflow>=3.8.1
Requires-Dist: polars-ds>=0.12.0
Requires-Dist: cvxpy>=1.8.1
Requires-Dist: ecos>=2.0.14
Requires-Dist: atrs
Requires-Dist: tqdm
Requires-Dist: ygo
Dynamic: license-file

# Silars

Silars 是基于 Polars 的因子分析与回测工具库，提供数据预处理、组合权重、策略和回测入口。

## 安装

Silars 需要 Python 3.12 或更高版本；CI 持续验证 Python 3.12 和 3.13。

```bash
pip install silars
```

使用 uv：

```bash
uv add silars
```

## 最小示例

下面按每个 `(date, time)` 截面选择得分最高的两个资产，并生成等权组合：

```python
from copy import deepcopy

import polars as pl

from silars.alphalens import top_k

scores = pl.DataFrame(
    {
        "date": ["2026-01-02"] * 3,
        "time": ["09:31:00"] * 3,
        "asset": ["A", "B", "C"],
        "score": [0.2, 0.8, 0.5],
    }
)

selector = deepcopy(top_k).set_params(num=2)
weights = selector.transform(scores)
print(weights.select("date", "time", "asset", "target_weight"))
```

## 主要入口

- `Preprocessor` 及预处理函数：因子清洗、标准化和中性化。
- `top_k`、`qcut`、`MFEs` / `MFEConfig`：组合权重生成。
- `Strategy`、`FactorStrategy`：策略编排。
- `BacktestEngine`、`bt`：回测。
- `Zoo`、`zoo`：因子数据工作区。

这些入口均从 `silars.alphalens` 导入。

## 研究期 tree shortlist

`silars.feature_selection.select_features` 用固定的单特征浅树，按独立验证期的每日
prediction RankIC 对已物化数值特征做粗筛：

```python
from silars.feature_selection import select_features

ranking = select_features(
    train,
    valid,
    feature_names,
    "forward_return",
    max_features=100,
)
```

它衡量的是 `tree(feature)` 对下游模型的排序潜力，不是原始特征 RankIC、独立 OOS、
显著性结论或可直接传给 MFEs 的 `score`。PIT、purge/embargo 和后续使用边界由
调用方保证。该入口也不同于
`silars.alphalens.evaluate.select_features` 的多重检验与相关性筛选。

同一模块也可以把一组显式特征通过一棵相同参数的浅树融合为 predict 区间的新特征：

```python
from silars.feature_selection import fuse_features

fused = fuse_features(
    train,
    valid,
    ["feature_a", "feature_b", "feature_c"],
    target="forward_return",
    output_name="tree_fusion",
)
valid = valid.with_columns(fused["tree_fusion"])
```

该入口只返回 predict prediction，不返回模型或 train in-sample prediction。若输入
特征是利用同一 predict 区间的 target 选出的，结果仍有 post-selection bias，不是
独立 OOS；完整历史融合列应由调用方 walk-forward/cross-fit 生成。

若 valid 含 target，可用同一棵 train-only 树得到一行 holdout 摘要及可直接执行的
Polars 表达式：

```python
from silars.feature_selection import evaluate_fusion

report = evaluate_fusion(
    train,
    valid,
    ["feature_a", "feature_b", "feature_c"],
    target="forward_return",
    output_name="tree_fusion",
)

expression = report.item(0, "expression")
valid = valid.with_columns(expression)

# 可持久化并交给另一个进程 / Dataset
lidb_expression = report.item(0, "lidb_expression")
```

返回列为 `feature`、`source_features`、`model_rank_ic_mean`、
`model_rank_icir`、`expression`、`lidb_expression`。`expression` 是当前进程可直接
执行的 `pl.Expr`；`lidb_expression` 是同一棵树生成的 LiDB/QDF 三元表达式字符串，
格式如 `((feature_a<=0.5)?(0.1):(0.2)) as tree_fusion`，可持久化并由另一个进程
转成 Dataset。融合树的输入和输出统一量化为 6 位小数，表达式中的 threshold 与
leaf value 也最多保留 6 位小数。这里只能证明 train/valid 时间分离；PIT、label
实现时点、
purge/embargo、valid 前冻结特征集合且未重复使用 valid 选 winner 均由调用方保证。
holdout 审查只调用 `evaluate_fusion`，未来预测再选择 `fuse_features` 或冻结
expression，避免对同一区间重复 fit。

大量显式组合应一次交给批量入口，避免循环调用 `evaluate_fusion` 重复准备公共数据：

```python
from itertools import product

import polars as pl

from silars.feature_selection import evaluate_fusions

groups = product(features_a, features_b)
ranking = evaluate_fusions(
    train,
    valid,
    groups,
    target="forward_return",
    output_prefix="tree_fusion",
    min_rank_ic=0.03,
    n_jobs=7,
)

expressions = (
    ranking.filter(pl.col("selected")).get_column("expression").to_list()
)
predict = predict.with_columns(expressions)

portable_ranking = ranking.drop("expression")
portable_ranking.write_parquet("fusion_ranking.parquet")
```

组合按输入顺序稳定命名为 `tree_fusion_00000`、`tree_fusion_00001` 等；组内特征名
排序，规范化后重复的组合会在首次 fit 前报错。公共 row-domain、target ranks、
sample weights 和特征值校验只准备一次，每组只保留自己的小型 Float32 矩阵。
只有通过 `min_rank_ic` 的行保存两种表达式，其余为 null。`Object` 列不能写
Parquet；持久化排名时只需移除 `expression`，`lidb_expression` 可直接保留。

另一个进程加载源 Dataset 后，可把筛选出的字符串直接交给 LiDB：

```python
import lidb
import polars as pl
from lidb import dataset

selected = pl.read_parquet("fusion_ranking.parquet").filter(pl.col("selected"))
SOURCE_FEATURES = sorted(
    {feature for group in selected["source_features"] for feature in group}
)
LIDB_EXPRESSIONS = tuple(
    selected
    .get_column("lidb_expression")
    .drop_nulls()
)


@dataset(ds_feature_source)
def ds_tree_fusions(depend: pl.LazyFrame):
    prepared = depend.with_columns(
        pl.col(SOURCE_FEATURES).cast(pl.Float64).round(6).cast(pl.Float32)
    )
    return lidb.from_polars(prepared).sql(*LIDB_EXPRESSIONS)
```

源 Dataset 必须提供 `source_features` 中完全相同的列名；表达式版本变化时应同步
更新 Dataset 的稳定版本名，避免复用旧缓存。LiDB 不支持的列名或输出名不会被猜测
转义，对应 `lidb_expression` 返回 null，当前进程的 `pl.Expr` 仍然可用。源特征在
进入 LiDB 前必须使用相同的 `round(6) -> Float32` 预处理，否则不保证与评估结果
一致。

批量入口使用 `ygo` 的 `threading` backend 并行各组合，线程共享只读的
train/valid 和公共统计状态，不复制整张 DataFrame。`n_jobs` 必须为正整数，实际
worker 数不会超过组合数；外层已经并发或内存受限时传 `n_jobs=1`。

批量筛选会把 `valid` 变成 selection set；无论筛选多少行，它都不再是独立 OOS。
筛出的表达式必须在未参与组合筛选的后续 `predict/test` 区间验证。

## 基准对冲回测

对已加载的 `Zoo`，可复用原多头回测并得到 1:1 对冲收益。传入指数代码时读取对应
指数日收益：

```python
from silars.alphalens import FactorStrategy, zoo

results = zoo.hedge(
    ["KMID"],
    FactorStrategy(),
    index_code="000300",
    times=["09:31:00", "10:00:00"],
)
print(results["KMID"]["ret"])
print(results["KMID"]["metric"])
```

`hedge()` 会立即完成回测、显示多头/基准/对冲组合净值图，并返回每个因子的
`pos`、`ret` 和对冲组合 `metric`，不是延迟任务生成器。

`index_code=""` 时不读取指数，直接从回测输入的 `prev_close/open/close` 构造等权市场
日收益，结果列为 `mkt` 和 `long-mkt`。指数路径运行时需要当前环境可导入
`dc.data.base.ds_index_retC2C`；`period` 仅控制原组合持仓周期，不缩放日度基准收益。

## 许可证

Silars 使用 MIT License。`silars/empyrical` 包含 Apache-2.0 许可的第三方代码，详见
`THIRD_PARTY_NOTICES.md` 和 `LICENSES/Apache-2.0.txt`。
