tualpha 0.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tualpha-0.5.0/PKG-INFO +297 -0
- tualpha-0.5.0/README.md +273 -0
- tualpha-0.5.0/pyproject.toml +56 -0
- tualpha-0.5.0/pyproject.toml.orig +49 -0
- tualpha-0.5.0/src/tualpha/__init__.py +76 -0
- tualpha-0.5.0/src/tualpha/api.py +127 -0
- tualpha-0.5.0/src/tualpha/assets.py +197 -0
- tualpha-0.5.0/src/tualpha/broker.py +317 -0
- tualpha-0.5.0/src/tualpha/bundle.py +1939 -0
- tualpha-0.5.0/src/tualpha/calendar.py +103 -0
- tualpha-0.5.0/src/tualpha/cli.py +108 -0
- tualpha-0.5.0/src/tualpha/config.py +87 -0
- tualpha-0.5.0/src/tualpha/costs.py +139 -0
- tualpha-0.5.0/src/tualpha/data.py +881 -0
- tualpha-0.5.0/src/tualpha/engine.py +548 -0
- tualpha-0.5.0/src/tualpha/exceptions.py +21 -0
- tualpha-0.5.0/src/tualpha/metrics.py +114 -0
- tualpha-0.5.0/src/tualpha/models.py +268 -0
- tualpha-0.5.0/src/tualpha/reporting.py +520 -0
- tualpha-0.5.0/src/tualpha/result.py +67 -0
- tualpha-0.5.0/src/tualpha/rules.py +106 -0
- tualpha-0.5.0/src/tualpha/tushare_fields.py +131 -0
- tualpha-0.5.0/src/tualpha/updater.py +923 -0
tualpha-0.5.0/PKG-INFO
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: tualpha
|
|
3
|
+
Version: 0.5.0
|
|
4
|
+
Summary: Event-driven daily backtesting for China A-share stocks and ETFs
|
|
5
|
+
Keywords: backtest,quant,a-share,etf,tushare
|
|
6
|
+
Author: joshuaxql
|
|
7
|
+
Author-email: joshuaxql <qiuleiustc@gmail.com>
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Financial and Insurance Industry
|
|
10
|
+
Classifier: Intended Audience :: Science/Research
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Topic :: Office/Business :: Financial :: Investment
|
|
14
|
+
Requires-Dist: bcolz-zipline==1.13.0
|
|
15
|
+
Requires-Dist: duckdb>=1.4
|
|
16
|
+
Requires-Dist: filelock>=3.16
|
|
17
|
+
Requires-Dist: numpy>=2.0
|
|
18
|
+
Requires-Dist: pandas>=2.2
|
|
19
|
+
Requires-Dist: plotly>=5.24
|
|
20
|
+
Requires-Dist: tushare>=1.4
|
|
21
|
+
Requires-Dist: zipline-reloaded==3.1.1
|
|
22
|
+
Requires-Python: >=3.12
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# TuAlpha
|
|
26
|
+
|
|
27
|
+
TuAlpha 是基于tushare数据面向中国 A 股股票与 ETF 的日频事件驱动回测框架。框架借鉴 Zipline 的 `Asset → DataPortal → Blotter → Ledger → Metrics` 分层,使用 **zipline-reloaded 官方 Bundle 格式**和中国市场交易规则。
|
|
28
|
+
|
|
29
|
+
> 当前版本为 `0.5.0`,只支持多头现金账户、股票和 ETF;不支持期货、期权、融资融券或 ETF 申赎。
|
|
30
|
+
|
|
31
|
+
## 功能
|
|
32
|
+
|
|
33
|
+
- Zipline 官方 Writer 生成的 `assets-7.sqlite + daily_equities.bcolz + adjustments.sqlite` Bundle 文件
|
|
34
|
+
- 股票、ETF 日线回测,Bundle 默认根目录:`~/.tualpha`
|
|
35
|
+
- D 日收盘决策,D+1 开盘或收盘成交,避免未来函数
|
|
36
|
+
- 涨停禁止买入、跌停禁止卖出的保守成交模型
|
|
37
|
+
- 停牌、无行情、零成交量限制
|
|
38
|
+
- 主板/创业板/ETF:买入 100 股(份)整数倍
|
|
39
|
+
- 科创板:200 股起、之后按 1 股递增
|
|
40
|
+
- 北交所:100 股起、之后按 1 股递增
|
|
41
|
+
- **所有股票和 ETF 统一 T+1**
|
|
42
|
+
- 股票印花税、佣金、经手费与过户费;避免 all-in 佣金重复计费
|
|
43
|
+
- 前复权、后复权或不复权策略数据
|
|
44
|
+
- 中文 Plotly HTML 报告和每日持仓 CSV
|
|
45
|
+
- `tualpha update` 增量更新显式指定的 Tushare CSV 缓存并替换固定目录 Bundle
|
|
46
|
+
|
|
47
|
+
## 安装
|
|
48
|
+
|
|
49
|
+
推荐 64 位 CPython 3.12:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
uv add tualpha
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
从源码开发:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
uv sync --dev
|
|
59
|
+
uv run pytest
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## 数据目录
|
|
63
|
+
|
|
64
|
+
Bundle 与原始 CSV 已完全分离。Bundle 根目录默认是 `~/.tualpha`,可通过 `--bundle-root`、Python 的 `bundle_root=` 或环境变量 `TUALPHA_BUNDLE_ROOT` 显式覆盖:
|
|
65
|
+
|
|
66
|
+
```text
|
|
67
|
+
~/.tualpha/ # Bundle 根目录;可用 --bundle-root 修改
|
|
68
|
+
├── bundles/
|
|
69
|
+
│ └── tualpha/ # 固定目录,不再使用更新时间命名
|
|
70
|
+
│ ├── assets-7.sqlite
|
|
71
|
+
│ ├── daily_equities.bcolz/ # OHLCV + 全部扩展日线列
|
|
72
|
+
│ ├── index_daily.bcolz/ # 仅用于报告基准的指数日线
|
|
73
|
+
│ ├── minute_equities.bcolz/
|
|
74
|
+
│ ├── adjustments.sqlite
|
|
75
|
+
│ ├── finance.sqlite # 公告时点财务宽表
|
|
76
|
+
│ ├── manifest.json
|
|
77
|
+
│ └── READY
|
|
78
|
+
├── cache/
|
|
79
|
+
│ └── tualpha/
|
|
80
|
+
│ ├── normalized.duckdb
|
|
81
|
+
│ └── sid-map.json
|
|
82
|
+
├── update-status.json # 当前更新状态及最后一次成功更新
|
|
83
|
+
├── .locks/ # 进程锁
|
|
84
|
+
└── .staging/ # 构建临时区;成功后自动清理
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Bundle schema v4 不再生成或读取 `tualpha.duckdb`。`normalized.duckdb` 只存在于 `cache/`,用于更新和构建,不属于可发布 Bundle。旧 schema 必须通过 `tualpha update` 或 `build_bundle()` 整体重建;发布成功后旧目录(含旧数据库)会被原子替换。
|
|
88
|
+
|
|
89
|
+
CSV 缓存目录**没有默认值**,执行更新时必须显式传入,并且不能与 Bundle 根目录互为父子目录。例如:
|
|
90
|
+
|
|
91
|
+
```text
|
|
92
|
+
E:\data\tushare_data\
|
|
93
|
+
├── daily\YYYYMMDD.csv
|
|
94
|
+
├── fund_daily\YYYYMMDD.csv
|
|
95
|
+
├── adj_factor\YYYYMMDD.csv
|
|
96
|
+
├── fund_adj\YYYYMMDD.csv
|
|
97
|
+
├── stk_limit\YYYYMMDD.csv
|
|
98
|
+
├── suspend_d\YYYYMMDD.csv
|
|
99
|
+
├── daily_basic\YYYYMMDD.csv
|
|
100
|
+
├── moneyflow\YYYYMMDD.csv
|
|
101
|
+
├── industry\YYYYMMDD.csv
|
|
102
|
+
├── stock_st\YYYYMMDD.csv
|
|
103
|
+
├── index_daily\YYYYMMDD.csv
|
|
104
|
+
├── balancesheet\YYYYMMDD.csv
|
|
105
|
+
├── income\YYYYMMDD.csv
|
|
106
|
+
├── cashflow\YYYYMMDD.csv
|
|
107
|
+
├── fina_indicator\YYYYMMDD.csv
|
|
108
|
+
├── stock_basic.csv
|
|
109
|
+
├── etf_basic.csv
|
|
110
|
+
├── index_basic.csv
|
|
111
|
+
└── trade_cal.csv
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
回测只读取 `~/.tualpha/bundles/tualpha`,不会读取 CSV。`fund_daily` 中的 LOF、分级基金和异常代码不会成为可交易资产;Bundle 使用 `etf_basic.csv` 作为 ETF 白名单。
|
|
115
|
+
|
|
116
|
+
更新时先在 `.staging` 中使用 Zipline 官方 Writer 完整生成并加载验证所有文件,再替换固定的 `bundles/tualpha` 目录。回测和底层 `load_bundle_data()` 会持有读锁,更新将在现有读者关闭后发布,避免 Windows 文件句柄阻断目录替换。由于 Zipline 的 `core.load()` 强制发现时间戳子目录,固定布局不再直接支持 `core.load("tualpha")`;需要底层 Zipline Readers 时使用 `tualpha.bundle.load_bundle_data()`。详细文件、表和字段说明见 [`docs/bundle-format.md`](docs/bundle-format.md)。
|
|
117
|
+
|
|
118
|
+
## 更新数据
|
|
119
|
+
|
|
120
|
+
必须通过环境变量提供 Token;没有 Token 时命令立即中止:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
export TUSHARE_TOKEN="your-token"
|
|
124
|
+
tualpha update --csv-dir /e/data/tushare_data
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
常用参数:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
tualpha update --csv-dir /e/data/tushare_data --from 20260101 --to 20260821
|
|
131
|
+
tualpha update --csv-dir /e/data/tushare_data --repair-from 20250101
|
|
132
|
+
tualpha update --csv-dir /e/data/tushare_data --lookback 20
|
|
133
|
+
tualpha update --csv-dir /e/data/tushare_data --index-weight 000300.SH
|
|
134
|
+
tualpha update --csv-dir /e/data/tushare_data --bundle-root /d/tualpha-bundle
|
|
135
|
+
tualpha update --csv-dir /e/data/tushare_data --dry-run --json
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
更新流程:
|
|
139
|
+
|
|
140
|
+
1. 增量下载并保留全部现有数据目录,包括行情、资金流、每日指标、行业和财务报表;普通更新会逐数据集检测并补齐缺失的交易日分区。
|
|
141
|
+
2. 对超过单次行数上限的接口自动分页,并检测接口忽略分页造成的重复页;财务数据除刷新最近报告期外,还按公告日期增量抓取旧报告期修订并与历史版本合并。
|
|
142
|
+
3. 数据写入 `~/.tualpha/.staging`,校验字段、日期、唯一键和内容哈希。
|
|
143
|
+
4. 成功后原子替换 CSV,并增量同步 `~/.tualpha/cache/tualpha/normalized.duckdb`。
|
|
144
|
+
5. 在临时目录生成新的 Bundle,加载验证成功后替换固定的 `~/.tualpha/bundles/tualpha`。
|
|
145
|
+
6. 原子写入 `~/.tualpha/update-status.json`,记录 `running / succeeded / failed / dry_run_succeeded` 状态、CSV 路径、更新时间、更新日期和错误信息。
|
|
146
|
+
|
|
147
|
+
`index_weight` 官方接口要求逐个指数代码查询。命令默认更新本地已经跟踪过的指数;首次使用可通过多个 `--index-weight` 指定代码。
|
|
148
|
+
|
|
149
|
+
## 快速开始
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
from tualpha import order_target_percent, record, run_algorithm, symbol
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def initialize(context):
|
|
156
|
+
context.asset = symbol("510300.SH")
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def handle_data(context, data):
|
|
160
|
+
closes = data.history(context.asset, "close", 20)
|
|
161
|
+
if len(closes) < 20:
|
|
162
|
+
return
|
|
163
|
+
|
|
164
|
+
target = 0.95 if closes.iloc[-1] > closes.mean() else 0.0
|
|
165
|
+
order_target_percent(context.asset, target)
|
|
166
|
+
record(close=closes.iloc[-1], ma20=closes.mean())
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
result = run_algorithm(
|
|
170
|
+
start="2020-01-01",
|
|
171
|
+
end="2025-12-31",
|
|
172
|
+
initialize=initialize,
|
|
173
|
+
handle_data=handle_data,
|
|
174
|
+
capital_base=1_000_000,
|
|
175
|
+
# bundle_root="~/.tualpha", # 默认值;仅自定义时传入
|
|
176
|
+
adjustment="qfq", # qfq / hfq / raw
|
|
177
|
+
execution_time="open", # open / close
|
|
178
|
+
benchmark="000300.SH",
|
|
179
|
+
output_dir="outputs/demo",
|
|
180
|
+
strategy_name="沪深300 ETF 趋势策略",
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
print(result.summary())
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
运行后生成:
|
|
187
|
+
|
|
188
|
+
```text
|
|
189
|
+
outputs/demo/
|
|
190
|
+
├── report.html
|
|
191
|
+
└── daily_positions.csv
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
`daily_positions.csv` 使用 UTF-8-SIG 和长表结构。每个交易日都有一条 `CASH` 记录,持仓记录包含数量、可卖数量、成本、原始/复权收盘价、市值、权重和未实现盈亏。
|
|
195
|
+
|
|
196
|
+
## 核心 API
|
|
197
|
+
|
|
198
|
+
策略回调内可使用:
|
|
199
|
+
|
|
200
|
+
- `symbol(code)`
|
|
201
|
+
- `order(asset, amount)`
|
|
202
|
+
- `order_value(asset, value)`
|
|
203
|
+
- `order_target(asset, target)`
|
|
204
|
+
- `order_target_value(asset, target)`
|
|
205
|
+
- `order_percent(asset, percent)`
|
|
206
|
+
- `order_target_percent(asset, target)`
|
|
207
|
+
- `cancel_order(order)` / `get_open_orders()`
|
|
208
|
+
- `record(**values)`
|
|
209
|
+
- `data.current(asset, field)`
|
|
210
|
+
- `data.history(asset, field, bar_count)`
|
|
211
|
+
- `data.fundamental(asset, field, period="latest")`
|
|
212
|
+
- `data.fundamentals(asset, fields, periods=4)`
|
|
213
|
+
- `data.available_fields(namespace=None)`
|
|
214
|
+
- `data.can_trade(asset)`
|
|
215
|
+
|
|
216
|
+
所有新订单默认只在下一交易日尝试一次。未成交订单会记录 `limit_up`、`limit_down`、`suspended`、`t_plus_one`、`invalid_lot` 等原因,并在 HTML 报告中汇总。
|
|
217
|
+
|
|
218
|
+
## 日频因子、行业和 ST 数据
|
|
219
|
+
|
|
220
|
+
扩展日频字段使用 `<数据集>.<字段>` 命名,并与价格一样支持 `current()` 和 `history()`:
|
|
221
|
+
|
|
222
|
+
```python
|
|
223
|
+
pe_ttm = data.current(context.asset, "daily_basic.pe_ttm")
|
|
224
|
+
net_flow = data.history(context.asset, "moneyflow.net_mf_amount", 20)
|
|
225
|
+
industry = data.current(context.asset, "industry.l1_name")
|
|
226
|
+
is_st = data.current(context.asset, "stock_st.is_st")
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
支持的数据集:
|
|
230
|
+
|
|
231
|
+
- `daily_basic`:估值、换手率、股本、市值等;单位保持 Tushare 原始口径;
|
|
232
|
+
- `moneyflow`:大小单量和金额,量为手、金额为万元;
|
|
233
|
+
- `industry`:申万一至三级行业代码及名称;
|
|
234
|
+
- `stock_st`:ST 名称、类型及虚拟字段 `is_st`,非 ST 日返回 `0`。
|
|
235
|
+
|
|
236
|
+
可通过 `data.available_fields("daily_basic")` 查看当前 Bundle 实际包含的字段。所有扩展日线字段都是 `daily_equities.bcolz` 的物理 CTable 列,例如 `ta_daily_basic__pe_ttm`;行业和 ST 字符串采用字典编码列,并由根属性中的字段注册表透明还原。回测不会读取 CSV 或 `normalized.duckdb`。
|
|
237
|
+
|
|
238
|
+
## 无未来函数的财务查询
|
|
239
|
+
|
|
240
|
+
财务字段必须使用 `fundamental()` 或 `fundamentals()`,不能通过 `current()` 读取:
|
|
241
|
+
|
|
242
|
+
```python
|
|
243
|
+
roe = data.fundamental(context.asset, "fina_indicator.roe")
|
|
244
|
+
revenue_2023 = data.fundamental(
|
|
245
|
+
context.asset,
|
|
246
|
+
"income.revenue",
|
|
247
|
+
period="20231231",
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
reports = data.fundamentals(
|
|
251
|
+
context.asset,
|
|
252
|
+
[
|
|
253
|
+
"fina_indicator.roe",
|
|
254
|
+
"income.revenue",
|
|
255
|
+
"balancesheet.total_assets",
|
|
256
|
+
"cashflow.n_cashflow_act",
|
|
257
|
+
],
|
|
258
|
+
periods=4,
|
|
259
|
+
)
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
四张财务宽表位于 Bundle 根目录的 `finance.sqlite`。为保守避免收盘后公告造成未来函数,财务记录从公告日后的首个交易日才可见,即查询要求 `coalesce(f_ann_date, ann_date) < 当前回测日`;同一报告期按当时可见的最新公告、`update_flag` 和修订顺序选择版本。利润表和现金流量表保留 Tushare 的年初至今累计口径,不自动伪造单季度或 TTM 数据。报表默认选择 `report_type="1"` 的合并报表。
|
|
263
|
+
|
|
264
|
+
## 复权与账户处理
|
|
265
|
+
|
|
266
|
+
- 前复权历史窗口:`原始价格 × 当日复权因子 / 当前回调日复权因子`
|
|
267
|
+
- 后复权:`原始价格 × 当日复权因子`
|
|
268
|
+
|
|
269
|
+
前复权窗口以策略当前可见的最后交易日为基准,延长回测结束日期不会改写过去回调看到的数据。成交、现金、费用和涨跌停判断始终使用原始价格。
|
|
270
|
+
|
|
271
|
+
持仓跨越复权因子变化时,框架按 Tushare 的“分红再投”总收益语义调整经济持仓数量和单位成本。这是只有复权因子、没有分红明细时的近似模型,并不等同于真实现金红利到账。
|
|
272
|
+
|
|
273
|
+
## 费用默认值
|
|
274
|
+
|
|
275
|
+
- 券商佣金:成交额 `0.03%`,最低 5 元,默认视为已含交易所经手费
|
|
276
|
+
- 股票卖出印花税:2023-08-28 前 `0.1%`,之后 `0.05%`
|
|
277
|
+
- 股票过户费:2022-04-29 前 `0.002%`,之后 `0.001%`
|
|
278
|
+
- ETF:不收股票印花税和股票过户费
|
|
279
|
+
|
|
280
|
+
可传入 `ChinaFeeModel` 修改费率、最低佣金和“佣金是否含经手费”的口径。
|
|
281
|
+
|
|
282
|
+
## Agent 策略编写 Skill
|
|
283
|
+
|
|
284
|
+
项目提供 [`.agents/skills/tualpha-strategy/`](.agents/skills/tualpha-strategy/) Skill,使 Agent 能按照 TuAlpha 的 D/D+1 时序、A 股交易规则、扩展字段和财务 PIT 语义编写策略。Pi 项目配置会自动发现该目录,也可显式调用:
|
|
285
|
+
|
|
286
|
+
```text
|
|
287
|
+
/skill:tualpha-strategy
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
详见 [`.agents/skills/README.md`](.agents/skills/README.md)。
|
|
291
|
+
|
|
292
|
+
## 已知边界
|
|
293
|
+
|
|
294
|
+
- 触及涨跌停并非交易所法律意义上的绝对不能成交;框架采用“涨停不买、跌停不卖”的保守流动性假设。
|
|
295
|
+
- 日内临时停牌在日频模型中按全天不可交易处理。
|
|
296
|
+
- 不模拟涨跌停排队、盘口深度、部分成交和冲击成本。
|
|
297
|
+
- `etf_basic.csv` 不提供退市日期;退市 ETF 在缺少额外清算数据时以最后交易日作为资产结束日。
|
tualpha-0.5.0/README.md
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
# TuAlpha
|
|
2
|
+
|
|
3
|
+
TuAlpha 是基于tushare数据面向中国 A 股股票与 ETF 的日频事件驱动回测框架。框架借鉴 Zipline 的 `Asset → DataPortal → Blotter → Ledger → Metrics` 分层,使用 **zipline-reloaded 官方 Bundle 格式**和中国市场交易规则。
|
|
4
|
+
|
|
5
|
+
> 当前版本为 `0.5.0`,只支持多头现金账户、股票和 ETF;不支持期货、期权、融资融券或 ETF 申赎。
|
|
6
|
+
|
|
7
|
+
## 功能
|
|
8
|
+
|
|
9
|
+
- Zipline 官方 Writer 生成的 `assets-7.sqlite + daily_equities.bcolz + adjustments.sqlite` Bundle 文件
|
|
10
|
+
- 股票、ETF 日线回测,Bundle 默认根目录:`~/.tualpha`
|
|
11
|
+
- D 日收盘决策,D+1 开盘或收盘成交,避免未来函数
|
|
12
|
+
- 涨停禁止买入、跌停禁止卖出的保守成交模型
|
|
13
|
+
- 停牌、无行情、零成交量限制
|
|
14
|
+
- 主板/创业板/ETF:买入 100 股(份)整数倍
|
|
15
|
+
- 科创板:200 股起、之后按 1 股递增
|
|
16
|
+
- 北交所:100 股起、之后按 1 股递增
|
|
17
|
+
- **所有股票和 ETF 统一 T+1**
|
|
18
|
+
- 股票印花税、佣金、经手费与过户费;避免 all-in 佣金重复计费
|
|
19
|
+
- 前复权、后复权或不复权策略数据
|
|
20
|
+
- 中文 Plotly HTML 报告和每日持仓 CSV
|
|
21
|
+
- `tualpha update` 增量更新显式指定的 Tushare CSV 缓存并替换固定目录 Bundle
|
|
22
|
+
|
|
23
|
+
## 安装
|
|
24
|
+
|
|
25
|
+
推荐 64 位 CPython 3.12:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
uv add tualpha
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
从源码开发:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
uv sync --dev
|
|
35
|
+
uv run pytest
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## 数据目录
|
|
39
|
+
|
|
40
|
+
Bundle 与原始 CSV 已完全分离。Bundle 根目录默认是 `~/.tualpha`,可通过 `--bundle-root`、Python 的 `bundle_root=` 或环境变量 `TUALPHA_BUNDLE_ROOT` 显式覆盖:
|
|
41
|
+
|
|
42
|
+
```text
|
|
43
|
+
~/.tualpha/ # Bundle 根目录;可用 --bundle-root 修改
|
|
44
|
+
├── bundles/
|
|
45
|
+
│ └── tualpha/ # 固定目录,不再使用更新时间命名
|
|
46
|
+
│ ├── assets-7.sqlite
|
|
47
|
+
│ ├── daily_equities.bcolz/ # OHLCV + 全部扩展日线列
|
|
48
|
+
│ ├── index_daily.bcolz/ # 仅用于报告基准的指数日线
|
|
49
|
+
│ ├── minute_equities.bcolz/
|
|
50
|
+
│ ├── adjustments.sqlite
|
|
51
|
+
│ ├── finance.sqlite # 公告时点财务宽表
|
|
52
|
+
│ ├── manifest.json
|
|
53
|
+
│ └── READY
|
|
54
|
+
├── cache/
|
|
55
|
+
│ └── tualpha/
|
|
56
|
+
│ ├── normalized.duckdb
|
|
57
|
+
│ └── sid-map.json
|
|
58
|
+
├── update-status.json # 当前更新状态及最后一次成功更新
|
|
59
|
+
├── .locks/ # 进程锁
|
|
60
|
+
└── .staging/ # 构建临时区;成功后自动清理
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Bundle schema v4 不再生成或读取 `tualpha.duckdb`。`normalized.duckdb` 只存在于 `cache/`,用于更新和构建,不属于可发布 Bundle。旧 schema 必须通过 `tualpha update` 或 `build_bundle()` 整体重建;发布成功后旧目录(含旧数据库)会被原子替换。
|
|
64
|
+
|
|
65
|
+
CSV 缓存目录**没有默认值**,执行更新时必须显式传入,并且不能与 Bundle 根目录互为父子目录。例如:
|
|
66
|
+
|
|
67
|
+
```text
|
|
68
|
+
E:\data\tushare_data\
|
|
69
|
+
├── daily\YYYYMMDD.csv
|
|
70
|
+
├── fund_daily\YYYYMMDD.csv
|
|
71
|
+
├── adj_factor\YYYYMMDD.csv
|
|
72
|
+
├── fund_adj\YYYYMMDD.csv
|
|
73
|
+
├── stk_limit\YYYYMMDD.csv
|
|
74
|
+
├── suspend_d\YYYYMMDD.csv
|
|
75
|
+
├── daily_basic\YYYYMMDD.csv
|
|
76
|
+
├── moneyflow\YYYYMMDD.csv
|
|
77
|
+
├── industry\YYYYMMDD.csv
|
|
78
|
+
├── stock_st\YYYYMMDD.csv
|
|
79
|
+
├── index_daily\YYYYMMDD.csv
|
|
80
|
+
├── balancesheet\YYYYMMDD.csv
|
|
81
|
+
├── income\YYYYMMDD.csv
|
|
82
|
+
├── cashflow\YYYYMMDD.csv
|
|
83
|
+
├── fina_indicator\YYYYMMDD.csv
|
|
84
|
+
├── stock_basic.csv
|
|
85
|
+
├── etf_basic.csv
|
|
86
|
+
├── index_basic.csv
|
|
87
|
+
└── trade_cal.csv
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
回测只读取 `~/.tualpha/bundles/tualpha`,不会读取 CSV。`fund_daily` 中的 LOF、分级基金和异常代码不会成为可交易资产;Bundle 使用 `etf_basic.csv` 作为 ETF 白名单。
|
|
91
|
+
|
|
92
|
+
更新时先在 `.staging` 中使用 Zipline 官方 Writer 完整生成并加载验证所有文件,再替换固定的 `bundles/tualpha` 目录。回测和底层 `load_bundle_data()` 会持有读锁,更新将在现有读者关闭后发布,避免 Windows 文件句柄阻断目录替换。由于 Zipline 的 `core.load()` 强制发现时间戳子目录,固定布局不再直接支持 `core.load("tualpha")`;需要底层 Zipline Readers 时使用 `tualpha.bundle.load_bundle_data()`。详细文件、表和字段说明见 [`docs/bundle-format.md`](docs/bundle-format.md)。
|
|
93
|
+
|
|
94
|
+
## 更新数据
|
|
95
|
+
|
|
96
|
+
必须通过环境变量提供 Token;没有 Token 时命令立即中止:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
export TUSHARE_TOKEN="your-token"
|
|
100
|
+
tualpha update --csv-dir /e/data/tushare_data
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
常用参数:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
tualpha update --csv-dir /e/data/tushare_data --from 20260101 --to 20260821
|
|
107
|
+
tualpha update --csv-dir /e/data/tushare_data --repair-from 20250101
|
|
108
|
+
tualpha update --csv-dir /e/data/tushare_data --lookback 20
|
|
109
|
+
tualpha update --csv-dir /e/data/tushare_data --index-weight 000300.SH
|
|
110
|
+
tualpha update --csv-dir /e/data/tushare_data --bundle-root /d/tualpha-bundle
|
|
111
|
+
tualpha update --csv-dir /e/data/tushare_data --dry-run --json
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
更新流程:
|
|
115
|
+
|
|
116
|
+
1. 增量下载并保留全部现有数据目录,包括行情、资金流、每日指标、行业和财务报表;普通更新会逐数据集检测并补齐缺失的交易日分区。
|
|
117
|
+
2. 对超过单次行数上限的接口自动分页,并检测接口忽略分页造成的重复页;财务数据除刷新最近报告期外,还按公告日期增量抓取旧报告期修订并与历史版本合并。
|
|
118
|
+
3. 数据写入 `~/.tualpha/.staging`,校验字段、日期、唯一键和内容哈希。
|
|
119
|
+
4. 成功后原子替换 CSV,并增量同步 `~/.tualpha/cache/tualpha/normalized.duckdb`。
|
|
120
|
+
5. 在临时目录生成新的 Bundle,加载验证成功后替换固定的 `~/.tualpha/bundles/tualpha`。
|
|
121
|
+
6. 原子写入 `~/.tualpha/update-status.json`,记录 `running / succeeded / failed / dry_run_succeeded` 状态、CSV 路径、更新时间、更新日期和错误信息。
|
|
122
|
+
|
|
123
|
+
`index_weight` 官方接口要求逐个指数代码查询。命令默认更新本地已经跟踪过的指数;首次使用可通过多个 `--index-weight` 指定代码。
|
|
124
|
+
|
|
125
|
+
## 快速开始
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
from tualpha import order_target_percent, record, run_algorithm, symbol
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def initialize(context):
|
|
132
|
+
context.asset = symbol("510300.SH")
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def handle_data(context, data):
|
|
136
|
+
closes = data.history(context.asset, "close", 20)
|
|
137
|
+
if len(closes) < 20:
|
|
138
|
+
return
|
|
139
|
+
|
|
140
|
+
target = 0.95 if closes.iloc[-1] > closes.mean() else 0.0
|
|
141
|
+
order_target_percent(context.asset, target)
|
|
142
|
+
record(close=closes.iloc[-1], ma20=closes.mean())
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
result = run_algorithm(
|
|
146
|
+
start="2020-01-01",
|
|
147
|
+
end="2025-12-31",
|
|
148
|
+
initialize=initialize,
|
|
149
|
+
handle_data=handle_data,
|
|
150
|
+
capital_base=1_000_000,
|
|
151
|
+
# bundle_root="~/.tualpha", # 默认值;仅自定义时传入
|
|
152
|
+
adjustment="qfq", # qfq / hfq / raw
|
|
153
|
+
execution_time="open", # open / close
|
|
154
|
+
benchmark="000300.SH",
|
|
155
|
+
output_dir="outputs/demo",
|
|
156
|
+
strategy_name="沪深300 ETF 趋势策略",
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
print(result.summary())
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
运行后生成:
|
|
163
|
+
|
|
164
|
+
```text
|
|
165
|
+
outputs/demo/
|
|
166
|
+
├── report.html
|
|
167
|
+
└── daily_positions.csv
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
`daily_positions.csv` 使用 UTF-8-SIG 和长表结构。每个交易日都有一条 `CASH` 记录,持仓记录包含数量、可卖数量、成本、原始/复权收盘价、市值、权重和未实现盈亏。
|
|
171
|
+
|
|
172
|
+
## 核心 API
|
|
173
|
+
|
|
174
|
+
策略回调内可使用:
|
|
175
|
+
|
|
176
|
+
- `symbol(code)`
|
|
177
|
+
- `order(asset, amount)`
|
|
178
|
+
- `order_value(asset, value)`
|
|
179
|
+
- `order_target(asset, target)`
|
|
180
|
+
- `order_target_value(asset, target)`
|
|
181
|
+
- `order_percent(asset, percent)`
|
|
182
|
+
- `order_target_percent(asset, target)`
|
|
183
|
+
- `cancel_order(order)` / `get_open_orders()`
|
|
184
|
+
- `record(**values)`
|
|
185
|
+
- `data.current(asset, field)`
|
|
186
|
+
- `data.history(asset, field, bar_count)`
|
|
187
|
+
- `data.fundamental(asset, field, period="latest")`
|
|
188
|
+
- `data.fundamentals(asset, fields, periods=4)`
|
|
189
|
+
- `data.available_fields(namespace=None)`
|
|
190
|
+
- `data.can_trade(asset)`
|
|
191
|
+
|
|
192
|
+
所有新订单默认只在下一交易日尝试一次。未成交订单会记录 `limit_up`、`limit_down`、`suspended`、`t_plus_one`、`invalid_lot` 等原因,并在 HTML 报告中汇总。
|
|
193
|
+
|
|
194
|
+
## 日频因子、行业和 ST 数据
|
|
195
|
+
|
|
196
|
+
扩展日频字段使用 `<数据集>.<字段>` 命名,并与价格一样支持 `current()` 和 `history()`:
|
|
197
|
+
|
|
198
|
+
```python
|
|
199
|
+
pe_ttm = data.current(context.asset, "daily_basic.pe_ttm")
|
|
200
|
+
net_flow = data.history(context.asset, "moneyflow.net_mf_amount", 20)
|
|
201
|
+
industry = data.current(context.asset, "industry.l1_name")
|
|
202
|
+
is_st = data.current(context.asset, "stock_st.is_st")
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
支持的数据集:
|
|
206
|
+
|
|
207
|
+
- `daily_basic`:估值、换手率、股本、市值等;单位保持 Tushare 原始口径;
|
|
208
|
+
- `moneyflow`:大小单量和金额,量为手、金额为万元;
|
|
209
|
+
- `industry`:申万一至三级行业代码及名称;
|
|
210
|
+
- `stock_st`:ST 名称、类型及虚拟字段 `is_st`,非 ST 日返回 `0`。
|
|
211
|
+
|
|
212
|
+
可通过 `data.available_fields("daily_basic")` 查看当前 Bundle 实际包含的字段。所有扩展日线字段都是 `daily_equities.bcolz` 的物理 CTable 列,例如 `ta_daily_basic__pe_ttm`;行业和 ST 字符串采用字典编码列,并由根属性中的字段注册表透明还原。回测不会读取 CSV 或 `normalized.duckdb`。
|
|
213
|
+
|
|
214
|
+
## 无未来函数的财务查询
|
|
215
|
+
|
|
216
|
+
财务字段必须使用 `fundamental()` 或 `fundamentals()`,不能通过 `current()` 读取:
|
|
217
|
+
|
|
218
|
+
```python
|
|
219
|
+
roe = data.fundamental(context.asset, "fina_indicator.roe")
|
|
220
|
+
revenue_2023 = data.fundamental(
|
|
221
|
+
context.asset,
|
|
222
|
+
"income.revenue",
|
|
223
|
+
period="20231231",
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
reports = data.fundamentals(
|
|
227
|
+
context.asset,
|
|
228
|
+
[
|
|
229
|
+
"fina_indicator.roe",
|
|
230
|
+
"income.revenue",
|
|
231
|
+
"balancesheet.total_assets",
|
|
232
|
+
"cashflow.n_cashflow_act",
|
|
233
|
+
],
|
|
234
|
+
periods=4,
|
|
235
|
+
)
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
四张财务宽表位于 Bundle 根目录的 `finance.sqlite`。为保守避免收盘后公告造成未来函数,财务记录从公告日后的首个交易日才可见,即查询要求 `coalesce(f_ann_date, ann_date) < 当前回测日`;同一报告期按当时可见的最新公告、`update_flag` 和修订顺序选择版本。利润表和现金流量表保留 Tushare 的年初至今累计口径,不自动伪造单季度或 TTM 数据。报表默认选择 `report_type="1"` 的合并报表。
|
|
239
|
+
|
|
240
|
+
## 复权与账户处理
|
|
241
|
+
|
|
242
|
+
- 前复权历史窗口:`原始价格 × 当日复权因子 / 当前回调日复权因子`
|
|
243
|
+
- 后复权:`原始价格 × 当日复权因子`
|
|
244
|
+
|
|
245
|
+
前复权窗口以策略当前可见的最后交易日为基准,延长回测结束日期不会改写过去回调看到的数据。成交、现金、费用和涨跌停判断始终使用原始价格。
|
|
246
|
+
|
|
247
|
+
持仓跨越复权因子变化时,框架按 Tushare 的“分红再投”总收益语义调整经济持仓数量和单位成本。这是只有复权因子、没有分红明细时的近似模型,并不等同于真实现金红利到账。
|
|
248
|
+
|
|
249
|
+
## 费用默认值
|
|
250
|
+
|
|
251
|
+
- 券商佣金:成交额 `0.03%`,最低 5 元,默认视为已含交易所经手费
|
|
252
|
+
- 股票卖出印花税:2023-08-28 前 `0.1%`,之后 `0.05%`
|
|
253
|
+
- 股票过户费:2022-04-29 前 `0.002%`,之后 `0.001%`
|
|
254
|
+
- ETF:不收股票印花税和股票过户费
|
|
255
|
+
|
|
256
|
+
可传入 `ChinaFeeModel` 修改费率、最低佣金和“佣金是否含经手费”的口径。
|
|
257
|
+
|
|
258
|
+
## Agent 策略编写 Skill
|
|
259
|
+
|
|
260
|
+
项目提供 [`.agents/skills/tualpha-strategy/`](.agents/skills/tualpha-strategy/) Skill,使 Agent 能按照 TuAlpha 的 D/D+1 时序、A 股交易规则、扩展字段和财务 PIT 语义编写策略。Pi 项目配置会自动发现该目录,也可显式调用:
|
|
261
|
+
|
|
262
|
+
```text
|
|
263
|
+
/skill:tualpha-strategy
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
详见 [`.agents/skills/README.md`](.agents/skills/README.md)。
|
|
267
|
+
|
|
268
|
+
## 已知边界
|
|
269
|
+
|
|
270
|
+
- 触及涨跌停并非交易所法律意义上的绝对不能成交;框架采用“涨停不买、跌停不卖”的保守流动性假设。
|
|
271
|
+
- 日内临时停牌在日频模型中按全天不可交易处理。
|
|
272
|
+
- 不模拟涨跌停排队、盘口深度、部分成交和冲击成本。
|
|
273
|
+
- `etf_basic.csv` 不提供退市日期;退市 ETF 在缺少额外清算数据时以最后交易日作为资产结束日。
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "tualpha"
|
|
3
|
+
version = "0.5.0"
|
|
4
|
+
description = "Event-driven daily backtesting for China A-share stocks and ETFs"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
keywords = [
|
|
7
|
+
"backtest",
|
|
8
|
+
"quant",
|
|
9
|
+
"a-share",
|
|
10
|
+
"etf",
|
|
11
|
+
"tushare",
|
|
12
|
+
]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 3 - Alpha",
|
|
15
|
+
"Intended Audience :: Financial and Insurance Industry",
|
|
16
|
+
"Intended Audience :: Science/Research",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Programming Language :: Python :: 3.12",
|
|
19
|
+
"Topic :: Office/Business :: Financial :: Investment",
|
|
20
|
+
]
|
|
21
|
+
requires-python = ">=3.12"
|
|
22
|
+
dependencies = [
|
|
23
|
+
"bcolz-zipline==1.13.0",
|
|
24
|
+
"duckdb>=1.4",
|
|
25
|
+
"filelock>=3.16",
|
|
26
|
+
"numpy>=2.0",
|
|
27
|
+
"pandas>=2.2",
|
|
28
|
+
"plotly>=5.24",
|
|
29
|
+
"tushare>=1.4",
|
|
30
|
+
"zipline-reloaded==3.1.1",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[[project.authors]]
|
|
34
|
+
name = "joshuaxql"
|
|
35
|
+
email = "qiuleiustc@gmail.com"
|
|
36
|
+
|
|
37
|
+
[project.scripts]
|
|
38
|
+
tualpha = "tualpha.cli:main"
|
|
39
|
+
|
|
40
|
+
[build-system]
|
|
41
|
+
requires = ["uv_build>=0.12.5,<0.13.0"]
|
|
42
|
+
build-backend = "uv_build"
|
|
43
|
+
|
|
44
|
+
[dependency-groups]
|
|
45
|
+
dev = [
|
|
46
|
+
"pytest>=8.3",
|
|
47
|
+
"pytest-cov>=6.0",
|
|
48
|
+
"ruff>=0.9",
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
[tool.pytest.ini_options]
|
|
52
|
+
testpaths = ["tests"]
|
|
53
|
+
addopts = "-ra"
|
|
54
|
+
|
|
55
|
+
[tool.ruff]
|
|
56
|
+
target-version = "py312"
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "tualpha"
|
|
3
|
+
version = "0.5.0"
|
|
4
|
+
description = "Event-driven daily backtesting for China A-share stocks and ETFs"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
keywords = ["backtest", "quant", "a-share", "etf", "tushare"]
|
|
7
|
+
classifiers = [
|
|
8
|
+
"Development Status :: 3 - Alpha",
|
|
9
|
+
"Intended Audience :: Financial and Insurance Industry",
|
|
10
|
+
"Intended Audience :: Science/Research",
|
|
11
|
+
"Programming Language :: Python :: 3",
|
|
12
|
+
"Programming Language :: Python :: 3.12",
|
|
13
|
+
"Topic :: Office/Business :: Financial :: Investment",
|
|
14
|
+
]
|
|
15
|
+
authors = [
|
|
16
|
+
{ name = "joshuaxql", email = "qiuleiustc@gmail.com" }
|
|
17
|
+
]
|
|
18
|
+
requires-python = ">=3.12"
|
|
19
|
+
dependencies = [
|
|
20
|
+
"bcolz-zipline==1.13.0",
|
|
21
|
+
"duckdb>=1.4",
|
|
22
|
+
"filelock>=3.16",
|
|
23
|
+
"numpy>=2.0",
|
|
24
|
+
"pandas>=2.2",
|
|
25
|
+
"plotly>=5.24",
|
|
26
|
+
"tushare>=1.4",
|
|
27
|
+
"zipline-reloaded==3.1.1",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.scripts]
|
|
31
|
+
tualpha = "tualpha.cli:main"
|
|
32
|
+
|
|
33
|
+
[build-system]
|
|
34
|
+
requires = ["uv_build>=0.12.5,<0.13.0"]
|
|
35
|
+
build-backend = "uv_build"
|
|
36
|
+
|
|
37
|
+
[dependency-groups]
|
|
38
|
+
dev = [
|
|
39
|
+
"pytest>=8.3",
|
|
40
|
+
"pytest-cov>=6.0",
|
|
41
|
+
"ruff>=0.9",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
[tool.pytest.ini_options]
|
|
45
|
+
testpaths = ["tests"]
|
|
46
|
+
addopts = "-ra"
|
|
47
|
+
|
|
48
|
+
[tool.ruff]
|
|
49
|
+
target-version = "py312"
|