mirror of
https://ghfast.top/https://github.com/aeroxw/easy-tdx.git
synced 2026-09-12 16:54:17 +08:00
feat(factor): add analysis/transform exports, CLI analyze command, bump v1.12.0
This commit is contained in:
+1
-1
@@ -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"
|
||||||
|
|||||||
@@ -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,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|||||||
@@ -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",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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,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
|
||||||
|
|||||||
@@ -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 在做什么,或者打算拿它做什么。
|
||||||
Reference in New Issue
Block a user