From 303964ea672017abc0ede7d86d256e18af8170a6 Mon Sep 17 00:00:00 2001 From: GitHub Date: Fri, 12 Jun 2026 20:11:28 +0800 Subject: [PATCH] feat(factor): add analysis/transform exports, CLI analyze command, bump v1.12.0 --- pyproject.toml | 2 +- src/easy_tdx/cli/cmd_factor.py | 35 ++++++++++++++++++++++++ src/easy_tdx/factor/__init__.py | 17 ++++++++++++ src/easy_tdx/factor/analysis.py | 10 ++++--- src/easy_tdx/factor/transform.py | 1 + welcome-discussion.md | 46 ++++++++++++++++++++++++++++++++ 6 files changed, 107 insertions(+), 4 deletions(-) create mode 100644 welcome-discussion.md diff --git a/pyproject.toml b/pyproject.toml index 0aaf1e2..fb15205 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "easy-tdx" -version = "1.11.0" +version = "1.12.0" description = "通达信 TCP 协议行情数据客户端,支持在线行情、离线数据读取与写入同步" readme = "README.md" requires-python = ">=3.10" diff --git a/src/easy_tdx/cli/cmd_factor.py b/src/easy_tdx/cli/cmd_factor.py index df1f55b..4d78fd4 100644 --- a/src/easy_tdx/cli/cmd_factor.py +++ b/src/easy_tdx/cli/cmd_factor.py @@ -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']}") else: 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, + ) + ) diff --git a/src/easy_tdx/factor/__init__.py b/src/easy_tdx/factor/__init__.py index 167a3b6..18b4b3a 100644 --- a/src/easy_tdx/factor/__init__.py +++ b/src/easy_tdx/factor/__init__.py @@ -3,17 +3,34 @@ from __future__ import annotations +from easy_tdx.factor.analysis import FactorAnalyzer, FactorReport from easy_tdx.factor.base import FACTORY_REGISTRY, Factor, register_factor # 导入 builtin 触发自动注册 from easy_tdx.factor.builtin import get_factor, list_factors # noqa: F401 from easy_tdx.factor.engine import FactorEngine +from easy_tdx.factor.transform import ( + fill_missing, + orthogonalize, + preprocess, + rank_normalize, + winsorize, + zscore, +) __all__ = [ "Factor", "register_factor", "FACTORY_REGISTRY", "FactorEngine", + "FactorAnalyzer", + "FactorReport", "list_factors", "get_factor", + "fill_missing", + "orthogonalize", + "preprocess", + "rank_normalize", + "winsorize", + "zscore", ] diff --git a/src/easy_tdx/factor/analysis.py b/src/easy_tdx/factor/analysis.py index 6497917..fdc5357 100644 --- a/src/easy_tdx/factor/analysis.py +++ b/src/easy_tdx/factor/analysis.py @@ -1,5 +1,6 @@ # src/easy_tdx/factor/analysis.py """因子有效性分析引擎。""" + from __future__ import annotations from dataclasses import dataclass @@ -64,7 +65,7 @@ class FactorAnalyzer: def compute_quantile_returns(self) -> pd.DataFrame: """分层收益分析。""" 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]] = [] for date in dates: sub = self._merged[self._merged["date"] == date] @@ -74,7 +75,10 @@ class FactorAnalyzer: continue valid = valid.copy() 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() 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_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) diff --git a/src/easy_tdx/factor/transform.py b/src/easy_tdx/factor/transform.py index 94271ec..d5ee461 100644 --- a/src/easy_tdx/factor/transform.py +++ b/src/easy_tdx/factor/transform.py @@ -1,5 +1,6 @@ # src/easy_tdx/factor/transform.py """因子预处理 — 纯函数管道。""" + from __future__ import annotations import numpy as np diff --git a/welcome-discussion.md b/welcome-discussion.md new file mode 100644 index 0000000..91051cc --- /dev/null +++ b/welcome-discussion.md @@ -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 在做什么,或者打算拿它做什么。