feat(factor): add analysis/transform exports, CLI analyze command, bump v1.12.0

This commit is contained in:
GitHub
2026-06-12 20:11:28 +08:00
parent 9d7bf84d5d
commit 303964ea67
6 changed files with 107 additions and 4 deletions
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project] [project]
name = "easy-tdx" name = "easy-tdx"
version = "1.11.0" version = "1.12.0"
description = "通达信 TCP 协议行情数据客户端,支持在线行情、离线数据读取与写入同步" description = "通达信 TCP 协议行情数据客户端,支持在线行情、离线数据读取与写入同步"
readme = "README.md" readme = "README.md"
requires-python = ">=3.10" requires-python = ">=3.10"
+35
View File
@@ -54,3 +54,38 @@ def factor_list(category: str | None, use_table: bool) -> None:
click.echo(f"{f['name']}\t{f['category']}\t{f['description']}") click.echo(f"{f['name']}\t{f['category']}\t{f['description']}")
else: else:
click.echo(json.dumps(factors, ensure_ascii=False, indent=2)) click.echo(json.dumps(factors, ensure_ascii=False, indent=2))
@factor.command("analyze")
@click.argument("factor_name")
@click.option("--period", default=5, type=int, help="远期收益天数")
@click.option("--n-quantiles", default=5, type=int, help="分层数")
def factor_analyze(factor_name: str, period: int, n_quantiles: int) -> None:
"""分析指定因子的有效性。
示例:
easy-tdx factor analyze momentum_20d
easy-tdx factor analyze rsi_14 --period 10 --n-quantiles 10
"""
click.echo(
json.dumps(
{
"message": "factor analyze 需要行情数据,请使用 Python API",
"factor": factor_name,
"example": (
f"from easy_tdx.factor import FactorEngine, FactorAnalyzer, preprocess\n"
f"engine = FactorEngine()\n"
f"factor_data = engine.compute_cross_section(data, ['{factor_name}'])\n"
f"clean = preprocess(factor_data, ['{factor_name}'])\n"
f"return_data = engine.compute_forward_returns(data, period={period})\n"
f"analyzer = FactorAnalyzer(clean, return_data, n_quantiles={n_quantiles})\n"
f"report = analyzer.full_report()\n"
f"print(f'IC={{report.ic_mean:.3f}}, IR={{report.ir:.3f}}')"
),
},
ensure_ascii=False,
indent=2,
)
)
+17
View File
@@ -3,17 +3,34 @@
from __future__ import annotations from __future__ import annotations
from easy_tdx.factor.analysis import FactorAnalyzer, FactorReport
from easy_tdx.factor.base import FACTORY_REGISTRY, Factor, register_factor from easy_tdx.factor.base import FACTORY_REGISTRY, Factor, register_factor
# 导入 builtin 触发自动注册 # 导入 builtin 触发自动注册
from easy_tdx.factor.builtin import get_factor, list_factors # noqa: F401 from easy_tdx.factor.builtin import get_factor, list_factors # noqa: F401
from easy_tdx.factor.engine import FactorEngine from easy_tdx.factor.engine import FactorEngine
from easy_tdx.factor.transform import (
fill_missing,
orthogonalize,
preprocess,
rank_normalize,
winsorize,
zscore,
)
__all__ = [ __all__ = [
"Factor", "Factor",
"register_factor", "register_factor",
"FACTORY_REGISTRY", "FACTORY_REGISTRY",
"FactorEngine", "FactorEngine",
"FactorAnalyzer",
"FactorReport",
"list_factors", "list_factors",
"get_factor", "get_factor",
"fill_missing",
"orthogonalize",
"preprocess",
"rank_normalize",
"winsorize",
"zscore",
] ]
+7 -3
View File
@@ -1,5 +1,6 @@
# src/easy_tdx/factor/analysis.py # src/easy_tdx/factor/analysis.py
"""因子有效性分析引擎。""" """因子有效性分析引擎。"""
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass from dataclasses import dataclass
@@ -64,7 +65,7 @@ class FactorAnalyzer:
def compute_quantile_returns(self) -> pd.DataFrame: def compute_quantile_returns(self) -> pd.DataFrame:
"""分层收益分析。""" """分层收益分析。"""
dates = sorted(self._merged["date"].unique()) dates = sorted(self._merged["date"].unique())
q_names = [f"q{i+1}" for i in range(self._n_quantiles)] q_names = [f"q{i + 1}" for i in range(self._n_quantiles)]
rows: list[list[float]] = [] rows: list[list[float]] = []
for date in dates: for date in dates:
sub = self._merged[self._merged["date"] == date] sub = self._merged[self._merged["date"] == date]
@@ -74,7 +75,10 @@ class FactorAnalyzer:
continue continue
valid = valid.copy() valid = valid.copy()
valid["_q"] = pd.qcut( valid["_q"] = pd.qcut(
valid[self._factor_col], self._n_quantiles, labels=False, duplicates="drop", valid[self._factor_col],
self._n_quantiles,
labels=False,
duplicates="drop",
) )
means = valid.groupby("_q")[self._return_col].mean() means = valid.groupby("_q")[self._return_col].mean()
rows.append([float(means.get(q, np.nan)) for q in range(self._n_quantiles)]) rows.append([float(means.get(q, np.nan)) for q in range(self._n_quantiles)])
@@ -125,7 +129,7 @@ class FactorAnalyzer:
quantile_means = qr.mean() quantile_means = qr.mean()
quantile_returns = { quantile_returns = {
f"q{i+1}": float(quantile_means.iloc[i]) for i in range(len(quantile_means)) f"q{i + 1}": float(quantile_means.iloc[i]) for i in range(len(quantile_means))
} }
top_minus_bottom = quantile_returns.get("q5", 0.0) - quantile_returns.get("q1", 0.0) top_minus_bottom = quantile_returns.get("q5", 0.0) - quantile_returns.get("q1", 0.0)
+1
View File
@@ -1,5 +1,6 @@
# src/easy_tdx/factor/transform.py # src/easy_tdx/factor/transform.py
"""因子预处理 — 纯函数管道。""" """因子预处理 — 纯函数管道。"""
from __future__ import annotations from __future__ import annotations
import numpy as np import numpy as np
+46
View File
@@ -0,0 +1,46 @@
# Welcome to easy-tdx Discussions!
## 👋 欢迎!
感谢你来到 easy-tdx 社区!
这个项目的初心很简单:**打破金融数据的获取门槛,让每个人都能拿到和机构一样的行情数据。** 免费、开源、无需注册、无需 API Key。
不管你是量化新手还是老手,这里就是我们的交流阵地。
## 💬 在这里你可以
- **提问** — 安装报错、API 用法、数据字段含义……任何问题都可以问,没有"太基础"这回事
- **分享策略** — 写了不错的回测策略?发现了好用的指标组合?发出来大家一起看看
- **反馈 Bug** — 命令跑不通、数据不对劲、接口返回异常,直接贴出来
- **提 Feature** — 觉得缺了什么功能?说出来,社区一起评估优先级
- **晒成果** — 用 easy-tdx 做了什么有意思的东西?数据分析、可视化、自动化交易面板……都欢迎展示
## 🚀 快速上手
还没装过的朋友,一行命令搞定:
```bash
pip install easy-tdx
```
然后试试:
```bash
easy-tdx kline SZ 000001 --count 30 --table
easy-tdx indicator MACD -m SH -c 600519 --table
easy-tdx serve # 启动 Web API,浏览器打开 http://localhost:8000/docs
```
📖 完整文档看 [README](https://github.com/handsomejustin/easy_tdx) 和 [Wiki](https://github.com/handsomejustin/easy_tdx/wiki)。
## 🤝 社区共识
- 互相尊重,友善讨论
- 提问时尽量附上:版本号、命令/代码、报错信息
- 答疑是社区互助,没有义务回复,但每一条都会有人看到
- **不构成投资建议** — 本工具仅供学习研究,投资决策风险自担
---
**现在就从下面留个言开始吧** 🎉 告诉我们你用 easy-tdx 在做什么,或者打算拿它做什么。