qka 1.8.1.dev2__tar.gz → 1.9.1.dev4__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.
- {qka-1.8.1.dev2 → qka-1.9.1.dev4}/PKG-INFO +60 -66
- qka-1.9.1.dev4/README.md +145 -0
- {qka-1.8.1.dev2 → qka-1.9.1.dev4}/pyproject.toml +1 -18
- {qka-1.8.1.dev2 → qka-1.9.1.dev4}/qka/__init__.py +2 -7
- qka-1.9.1.dev4/qka/cli.py +21 -0
- {qka-1.8.1.dev2 → qka-1.9.1.dev4}/qka/core/backtest.py +44 -51
- {qka-1.8.1.dev2 → qka-1.9.1.dev4}/qka/core/data.py +151 -206
- qka-1.9.1.dev4/qka/core/strategy.py +71 -0
- {qka-1.8.1.dev2 → qka-1.9.1.dev4}/qka/utils/__init__.py +12 -14
- qka-1.9.1.dev4/skills/qka/SKILL.md +527 -0
- qka-1.8.1.dev2/.github/workflows/docs.yml +0 -75
- qka-1.8.1.dev2/README.md +0 -136
- qka-1.8.1.dev2/docs/advanced/performance.md +0 -203
- qka-1.8.1.dev2/docs/api/brokers.md +0 -4
- qka-1.8.1.dev2/docs/api/core.md +0 -58
- qka-1.8.1.dev2/docs/api/utils.md +0 -9
- qka-1.8.1.dev2/docs/examples/buy_and_hold.md +0 -77
- qka-1.8.1.dev2/docs/examples/ma_cross.md +0 -132
- qka-1.8.1.dev2/docs/examples/momentum.md +0 -81
- qka-1.8.1.dev2/docs/examples/multi_factor.md +0 -111
- qka-1.8.1.dev2/docs/examples/rsi_atr.md +0 -59
- qka-1.8.1.dev2/docs/guides/backtest.md +0 -48
- qka-1.8.1.dev2/docs/guides/data.md +0 -82
- qka-1.8.1.dev2/docs/guides/indicators.md +0 -78
- qka-1.8.1.dev2/docs/guides/report.md +0 -74
- qka-1.8.1.dev2/docs/guides/sizing.md +0 -83
- qka-1.8.1.dev2/docs/guides/strategy.md +0 -112
- qka-1.8.1.dev2/docs/guides/trading.md +0 -86
- qka-1.8.1.dev2/docs/index.md +0 -151
- qka-1.8.1.dev2/docs/user-guide/trading.md +0 -52
- qka-1.8.1.dev2/mkdocs.yml +0 -142
- qka-1.8.1.dev2/qka/brokers/__init__.py +0 -15
- qka-1.8.1.dev2/qka/brokers/client.py +0 -72
- qka-1.8.1.dev2/qka/brokers/server.py +0 -189
- qka-1.8.1.dev2/qka/brokers/trade.py +0 -176
- qka-1.8.1.dev2/qka/cli.py +0 -31
- qka-1.8.1.dev2/qka/core/strategy.py +0 -92
- qka-1.8.1.dev2/qka/mcp/__init__.py +0 -14
- qka-1.8.1.dev2/qka/mcp/api.py +0 -156
- qka-1.8.1.dev2/qka/mcp/server.py +0 -125
- qka-1.8.1.dev2/qka/server/__init__.py +0 -8
- qka-1.8.1.dev2/qka/server/handlers/__init__.py +0 -20
- qka-1.8.1.dev2/qka/server/handlers/class_inspector_handler.py +0 -68
- qka-1.8.1.dev2/qka/server/handlers/code_executor_handler.py +0 -91
- qka-1.8.1.dev2/qka/server/ws_client.py +0 -275
- qka-1.8.1.dev2/qka/server/zmq_server.py +0 -195
- qka-1.8.1.dev2/qka/utils/anis.py +0 -6
- qka-1.8.1.dev2/qka/utils/util.py +0 -50
- qka-1.8.1.dev2/skills/qka/SKILL.md +0 -578
- {qka-1.8.1.dev2 → qka-1.9.1.dev4}/.github/workflows/release.yml +0 -0
- {qka-1.8.1.dev2 → qka-1.9.1.dev4}/.gitignore +0 -0
- {qka-1.8.1.dev2 → qka-1.9.1.dev4}/.vscode/settings.json +0 -0
- {qka-1.8.1.dev2 → qka-1.9.1.dev4}/CHANGELOG.md +0 -0
- {qka-1.8.1.dev2 → qka-1.9.1.dev4}/LICENSE +0 -0
- {qka-1.8.1.dev2 → qka-1.9.1.dev4}/qka/core/__init__.py +0 -0
- {qka-1.8.1.dev2 → qka-1.9.1.dev4}/qka/core/accessor.py +0 -0
- {qka-1.8.1.dev2 → qka-1.9.1.dev4}/qka/core/broker.py +0 -0
- {qka-1.8.1.dev2 → qka-1.9.1.dev4}/qka/core/report.py +0 -0
- {qka-1.8.1.dev2 → qka-1.9.1.dev4}/qka/core/sizing.py +0 -0
- {qka-1.8.1.dev2 → qka-1.9.1.dev4}/qka/utils/logger.py +0 -0
- {qka-1.8.1.dev2 → qka-1.9.1.dev4}/tools/generate_api_ref.py +0 -0
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: qka
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.9.1.dev4
|
|
4
4
|
Summary: QKA(快量化 / Quant Kit for A-shares)- 简洁易用的 A 股量化回测框架
|
|
5
5
|
Project-URL: Home, https://github.com/zsrl/qka
|
|
6
|
-
Project-URL: Documentation, https://zsrl.github.io/qka
|
|
7
6
|
Project-URL: Repository, https://github.com/zsrl/qka
|
|
8
7
|
Project-URL: Issues, https://github.com/zsrl/qka/issues
|
|
9
8
|
Author-email: myc <mayuanchi1029@gmail.com>
|
|
@@ -35,28 +34,14 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
35
34
|
Classifier: Programming Language :: Python :: 3.11
|
|
36
35
|
Classifier: Programming Language :: Python :: 3.12
|
|
37
36
|
Requires-Python: >=3.10
|
|
38
|
-
Requires-Dist:
|
|
39
|
-
Requires-Dist: baostock>=0.8.9
|
|
37
|
+
Requires-Dist: baostock>=0.9.1
|
|
40
38
|
Requires-Dist: dask[dataframe]>=2025.7.0
|
|
41
|
-
Requires-Dist: fastapi>=0.115.13
|
|
42
|
-
Requires-Dist: flask>=3.1.1
|
|
43
|
-
Requires-Dist: ipykernel>=6.29.5
|
|
44
|
-
Requires-Dist: litellm>=1.81.14
|
|
45
|
-
Requires-Dist: mcp[cli]>=1.9.0
|
|
46
|
-
Requires-Dist: nbformat>=5.10.4
|
|
47
39
|
Requires-Dist: plotly>=6.1.1
|
|
48
40
|
Requires-Dist: pyarrow>=21.0.0
|
|
49
41
|
Requires-Dist: ta>=0.11.0
|
|
50
42
|
Requires-Dist: tqdm>=4.67.1
|
|
51
|
-
Requires-Dist: uvicorn>=0.34.3
|
|
52
|
-
Requires-Dist: websockets>=16.0
|
|
53
|
-
Requires-Dist: xtquant>=241014.1.2
|
|
54
43
|
Provides-Extra: dev
|
|
55
44
|
Requires-Dist: hatch-vcs>=0.3.0; extra == 'dev'
|
|
56
|
-
Requires-Dist: mkdocs-material>=9.6.14; extra == 'dev'
|
|
57
|
-
Requires-Dist: mkdocs>=1.6.1; extra == 'dev'
|
|
58
|
-
Requires-Dist: mkdocstrings-python>=1.16.12; extra == 'dev'
|
|
59
|
-
Requires-Dist: mkdocstrings>=0.29.1; extra == 'dev'
|
|
60
45
|
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
|
|
61
46
|
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
62
47
|
Requires-Dist: python-semantic-release>=9.0.0; extra == 'dev'
|
|
@@ -66,9 +51,6 @@ Description-Content-Type: text/markdown
|
|
|
66
51
|
## Quant Kit for A-shares
|
|
67
52
|
|
|
68
53
|
<p align="center">
|
|
69
|
-
<a href="https://qka.quantai.chat" target="_blank">
|
|
70
|
-
<img src="https://img.shields.io/badge/文档站-qka.quantai.chat-blue?style=flat" alt="文档站">
|
|
71
|
-
</a>
|
|
72
54
|
<a href="https://pypi.org/project/qka/">
|
|
73
55
|
<img src="https://img.shields.io/pypi/v/qka?color=blue" alt="PyPI">
|
|
74
56
|
</a>
|
|
@@ -85,20 +67,40 @@ Description-Content-Type: text/markdown
|
|
|
85
67
|
```python
|
|
86
68
|
from qka import Data, Strategy, Backtest
|
|
87
69
|
|
|
88
|
-
|
|
70
|
+
data = Data(
|
|
71
|
+
symbols=['sz.000001'],
|
|
72
|
+
indicators={
|
|
73
|
+
'sma_5': ('ta.trend.sma_indicator', 'close', 5),
|
|
74
|
+
'sma_20': ('ta.trend.sma_indicator', 'close', 20),
|
|
75
|
+
},
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
class MaCross(Strategy):
|
|
79
|
+
def __init__(self):
|
|
80
|
+
super().__init__()
|
|
81
|
+
self.pct = 0.2
|
|
82
|
+
|
|
89
83
|
def on_bar(self, date):
|
|
90
84
|
close = self.get('close')
|
|
85
|
+
fast = self.get('sma_5')
|
|
86
|
+
slow = self.get('sma_20')
|
|
91
87
|
for sym in close.index:
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
88
|
+
price = float(close[sym])
|
|
89
|
+
if price <= 0:
|
|
90
|
+
continue
|
|
91
|
+
if fast[sym] > slow[sym]:
|
|
92
|
+
size = self.sizing.percent(self.pct, price)
|
|
93
|
+
if size > 0:
|
|
94
|
+
self.broker.buy(sym, price, size)
|
|
95
|
+
else:
|
|
96
|
+
pos = self.broker.positions.get(sym, {}).get('size', 0)
|
|
97
|
+
if pos > 0:
|
|
98
|
+
self.broker.sell(sym, price, pos)
|
|
99
|
+
|
|
100
|
+
strategy = MaCross()
|
|
101
|
+
bt = Backtest(data, strategy)
|
|
102
|
+
bt.run(cash=200000, start_date='2024-01-01')
|
|
103
|
+
print(bt.metrics['total_return_pct'])
|
|
102
104
|
```
|
|
103
105
|
|
|
104
106
|
---
|
|
@@ -119,10 +121,13 @@ pip install qka
|
|
|
119
121
|
from qka import Data
|
|
120
122
|
|
|
121
123
|
data = Data(
|
|
122
|
-
symbols=['000001
|
|
123
|
-
indicators={
|
|
124
|
+
symbols=['sz.000001', 'sh.600000'],
|
|
125
|
+
indicators={
|
|
126
|
+
'sma_5': ('ta.trend.sma_indicator', 'close', 5),
|
|
127
|
+
'rsi_14': ('ta.momentum.rsi', 'close', 14),
|
|
128
|
+
},
|
|
124
129
|
)
|
|
125
|
-
df = data.get() #
|
|
130
|
+
df = data.get() # 返回宽表 DataFrame,列名 {symbol}|{factor}
|
|
126
131
|
```
|
|
127
132
|
|
|
128
133
|
### 策略
|
|
@@ -131,14 +136,15 @@ df = data.get() # 触发下载,返回宽表 DataFrame
|
|
|
131
136
|
from qka import Strategy
|
|
132
137
|
|
|
133
138
|
class MyStrategy(Strategy):
|
|
134
|
-
def __init__(self
|
|
135
|
-
super().__init__(
|
|
136
|
-
#
|
|
139
|
+
def __init__(self):
|
|
140
|
+
super().__init__()
|
|
141
|
+
self.lookback = 20 # 自定义参数
|
|
137
142
|
|
|
138
143
|
def on_bar(self, date):
|
|
139
|
-
close = self.get('close')
|
|
140
|
-
hist = self.history('close', 20)
|
|
141
|
-
#
|
|
144
|
+
close = self.get('close') # 当前横截面
|
|
145
|
+
hist = self.history('close', 20) # 历史窗口
|
|
146
|
+
# 交易逻辑:self.broker.buy / self.broker.sell
|
|
147
|
+
# 仓位计算:self.sizing.percent / self.sizing.fixed_shares
|
|
142
148
|
```
|
|
143
149
|
|
|
144
150
|
### 回测
|
|
@@ -146,37 +152,26 @@ class MyStrategy(Strategy):
|
|
|
146
152
|
```python
|
|
147
153
|
from qka import Backtest
|
|
148
154
|
|
|
149
|
-
|
|
150
|
-
bt
|
|
151
|
-
|
|
152
|
-
bt.
|
|
155
|
+
strategy = MyStrategy()
|
|
156
|
+
bt = Backtest(data, strategy)
|
|
157
|
+
bt.run(cash=200000, start_date='2024-01-01', benchmark='sh.000300')
|
|
158
|
+
print(bt.metrics['total_return_pct']) # 总收益率
|
|
159
|
+
print(bt.metrics['sharpe_ratio']) # 夏普比率
|
|
153
160
|
```
|
|
154
161
|
|
|
155
|
-
### 更多示例
|
|
156
|
-
|
|
157
|
-
| 策略 | 说明 |
|
|
158
|
-
|------|------|
|
|
159
|
-
| [买入持有与定投](https://qka.quantai.chat/examples/buy_and_hold/) | 买入不动 + 每月定投 |
|
|
160
|
-
| [均线交叉](https://qka.quantai.chat/examples/ma_cross/) | 5日线上穿/下穿20日线 |
|
|
161
|
-
| [RSI + ATR 风控](https://qka.quantai.chat/examples/rsi_atr/) | RSI 超卖买入,ATR 止损 |
|
|
162
|
-
| [动量排序选股](https://qka.quantai.chat/examples/momentum/) | 月度动量排序,Top 5 等权 |
|
|
163
|
-
| [多因子打分](https://qka.quantai.chat/examples/multi_factor/) | PE/ROE/动量/波动率打分选股 |
|
|
164
|
-
|
|
165
162
|
## 核心能力
|
|
166
163
|
|
|
167
|
-
- **多数据源** — baostock(默认)、akshare、QMT
|
|
168
|
-
- **预计算指标** —
|
|
164
|
+
- **多数据源** — baostock(默认)、akshare、QMT
|
|
165
|
+
- **预计算指标** — ta 库全部 60+ 指标,`('ta.trend.sma_indicator', 'close', 5)` 格式直接透传
|
|
169
166
|
- **事件驱动回测** — 按日推进,`self.get()` 横截面 + `self.history()` 窗口序列
|
|
170
|
-
- **仓位管理** — `
|
|
171
|
-
- **交易模拟** — 佣金万2.5、印花税万5
|
|
172
|
-
-
|
|
173
|
-
- **基准对比** —
|
|
167
|
+
- **仓位管理** — `sizing.percent()` / `sizing.fixed_amount()` / `sizing.fixed_shares()` / `sizing.atr_risk()`
|
|
168
|
+
- **交易模拟** — 佣金万 2.5、印花税万 5(仅卖出)、滑点 0.1%,最低佣金 5 元
|
|
169
|
+
- **绩效指标** — 总收益率、年化、夏普比率、最大回撤、Calmar、胜率、盈亏比等 13 项
|
|
170
|
+
- **基准对比** — 支持沪深 300(或指定指数)对比
|
|
174
171
|
|
|
175
172
|
## 文档
|
|
176
173
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
👉 **[qka.quantai.chat](https://qka.quantai.chat)**
|
|
174
|
+
框架 API 完整文档见 [skills/qka/SKILL.md](skills/qka/SKILL.md)——所有类的方法签名、参数、返回值和约束都在里面。
|
|
180
175
|
|
|
181
176
|
## 下一步规划
|
|
182
177
|
|
|
@@ -191,10 +186,9 @@ bt.report() # 生成 HTML 报告
|
|
|
191
186
|
## 致谢
|
|
192
187
|
|
|
193
188
|
- [baostock](http://baostock.com) — 免费 A 股数据
|
|
194
|
-
- [
|
|
195
|
-
- [Plotly](https://plotly.com/python/) — 交互式图表
|
|
196
|
-
- [xtquant](https://github.com/ShiMiaoYS/xtquant) — QMT 接口
|
|
189
|
+
- [ta](https://github.com/bukosabino/ta) — 技术指标库
|
|
197
190
|
|
|
198
191
|
---
|
|
199
192
|
|
|
200
193
|
> ⚠️ 量化交易存在风险,请充分了解后再使用本框架。
|
|
194
|
+
充分了解后再使用本框架。
|
qka-1.9.1.dev4/README.md
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# QKA — 快量化
|
|
2
|
+
## Quant Kit for A-shares
|
|
3
|
+
|
|
4
|
+
<p align="center">
|
|
5
|
+
<a href="https://pypi.org/project/qka/">
|
|
6
|
+
<img src="https://img.shields.io/pypi/v/qka?color=blue" alt="PyPI">
|
|
7
|
+
</a>
|
|
8
|
+
<a href="https://github.com/zsrl/qka">
|
|
9
|
+
<img src="https://img.shields.io/badge/python-3.10+-blue" alt="Python">
|
|
10
|
+
</a>
|
|
11
|
+
<a href="LICENSE">
|
|
12
|
+
<img src="https://img.shields.io/badge/license-MIT-green" alt="License">
|
|
13
|
+
</a>
|
|
14
|
+
</p>
|
|
15
|
+
|
|
16
|
+
**QKA(快量化 / Quant Kit for A-shares)** — 简洁易用的 A 股量化回测框架。
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
from qka import Data, Strategy, Backtest
|
|
20
|
+
|
|
21
|
+
data = Data(
|
|
22
|
+
symbols=['sz.000001'],
|
|
23
|
+
indicators={
|
|
24
|
+
'sma_5': ('ta.trend.sma_indicator', 'close', 5),
|
|
25
|
+
'sma_20': ('ta.trend.sma_indicator', 'close', 20),
|
|
26
|
+
},
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
class MaCross(Strategy):
|
|
30
|
+
def __init__(self):
|
|
31
|
+
super().__init__()
|
|
32
|
+
self.pct = 0.2
|
|
33
|
+
|
|
34
|
+
def on_bar(self, date):
|
|
35
|
+
close = self.get('close')
|
|
36
|
+
fast = self.get('sma_5')
|
|
37
|
+
slow = self.get('sma_20')
|
|
38
|
+
for sym in close.index:
|
|
39
|
+
price = float(close[sym])
|
|
40
|
+
if price <= 0:
|
|
41
|
+
continue
|
|
42
|
+
if fast[sym] > slow[sym]:
|
|
43
|
+
size = self.sizing.percent(self.pct, price)
|
|
44
|
+
if size > 0:
|
|
45
|
+
self.broker.buy(sym, price, size)
|
|
46
|
+
else:
|
|
47
|
+
pos = self.broker.positions.get(sym, {}).get('size', 0)
|
|
48
|
+
if pos > 0:
|
|
49
|
+
self.broker.sell(sym, price, pos)
|
|
50
|
+
|
|
51
|
+
strategy = MaCross()
|
|
52
|
+
bt = Backtest(data, strategy)
|
|
53
|
+
bt.run(cash=200000, start_date='2024-01-01')
|
|
54
|
+
print(bt.metrics['total_return_pct'])
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## 安装
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
pip install qka
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
需要 Python 3.10+。
|
|
66
|
+
|
|
67
|
+
## 快速上手
|
|
68
|
+
|
|
69
|
+
### 数据
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
from qka import Data
|
|
73
|
+
|
|
74
|
+
data = Data(
|
|
75
|
+
symbols=['sz.000001', 'sh.600000'],
|
|
76
|
+
indicators={
|
|
77
|
+
'sma_5': ('ta.trend.sma_indicator', 'close', 5),
|
|
78
|
+
'rsi_14': ('ta.momentum.rsi', 'close', 14),
|
|
79
|
+
},
|
|
80
|
+
)
|
|
81
|
+
df = data.get() # 返回宽表 DataFrame,列名 {symbol}|{factor}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### 策略
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
from qka import Strategy
|
|
88
|
+
|
|
89
|
+
class MyStrategy(Strategy):
|
|
90
|
+
def __init__(self):
|
|
91
|
+
super().__init__()
|
|
92
|
+
self.lookback = 20 # 自定义参数
|
|
93
|
+
|
|
94
|
+
def on_bar(self, date):
|
|
95
|
+
close = self.get('close') # 当前横截面
|
|
96
|
+
hist = self.history('close', 20) # 历史窗口
|
|
97
|
+
# 交易逻辑:self.broker.buy / self.broker.sell
|
|
98
|
+
# 仓位计算:self.sizing.percent / self.sizing.fixed_shares
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### 回测
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
from qka import Backtest
|
|
105
|
+
|
|
106
|
+
strategy = MyStrategy()
|
|
107
|
+
bt = Backtest(data, strategy)
|
|
108
|
+
bt.run(cash=200000, start_date='2024-01-01', benchmark='sh.000300')
|
|
109
|
+
print(bt.metrics['total_return_pct']) # 总收益率
|
|
110
|
+
print(bt.metrics['sharpe_ratio']) # 夏普比率
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## 核心能力
|
|
114
|
+
|
|
115
|
+
- **多数据源** — baostock(默认)、akshare、QMT
|
|
116
|
+
- **预计算指标** — ta 库全部 60+ 指标,`('ta.trend.sma_indicator', 'close', 5)` 格式直接透传
|
|
117
|
+
- **事件驱动回测** — 按日推进,`self.get()` 横截面 + `self.history()` 窗口序列
|
|
118
|
+
- **仓位管理** — `sizing.percent()` / `sizing.fixed_amount()` / `sizing.fixed_shares()` / `sizing.atr_risk()`
|
|
119
|
+
- **交易模拟** — 佣金万 2.5、印花税万 5(仅卖出)、滑点 0.1%,最低佣金 5 元
|
|
120
|
+
- **绩效指标** — 总收益率、年化、夏普比率、最大回撤、Calmar、胜率、盈亏比等 13 项
|
|
121
|
+
- **基准对比** — 支持沪深 300(或指定指数)对比
|
|
122
|
+
|
|
123
|
+
## 文档
|
|
124
|
+
|
|
125
|
+
框架 API 完整文档见 [skills/qka/SKILL.md](skills/qka/SKILL.md)——所有类的方法签名、参数、返回值和约束都在里面。
|
|
126
|
+
|
|
127
|
+
## 下一步规划
|
|
128
|
+
|
|
129
|
+
- [ ] 分钟级数据支持
|
|
130
|
+
- [ ] 自适应参数优化
|
|
131
|
+
- [ ] 实盘交易(QMT 接口)
|
|
132
|
+
|
|
133
|
+
## 许可证
|
|
134
|
+
|
|
135
|
+
[MIT](LICENSE)
|
|
136
|
+
|
|
137
|
+
## 致谢
|
|
138
|
+
|
|
139
|
+
- [baostock](http://baostock.com) — 免费 A 股数据
|
|
140
|
+
- [ta](https://github.com/bukosabino/ta) — 技术指标库
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
> ⚠️ 量化交易存在风险,请充分了解后再使用本框架。
|
|
145
|
+
充分了解后再使用本框架。
|
|
@@ -18,30 +18,16 @@ classifiers = [
|
|
|
18
18
|
]
|
|
19
19
|
requires-python = ">=3.10"
|
|
20
20
|
dependencies = [
|
|
21
|
-
"akshare>=1.16.93",
|
|
22
|
-
"fastapi>=0.115.13",
|
|
23
|
-
"flask>=3.1.1",
|
|
24
|
-
"mcp[cli]>=1.9.0",
|
|
25
|
-
"nbformat>=5.10.4",
|
|
26
21
|
"plotly>=6.1.1",
|
|
27
|
-
"uvicorn>=0.34.3",
|
|
28
|
-
"xtquant>=241014.1.2",
|
|
29
|
-
"ipykernel>=6.29.5",
|
|
30
22
|
"pyarrow>=21.0.0",
|
|
31
23
|
"tqdm>=4.67.1",
|
|
32
24
|
"dask[dataframe]>=2025.7.0",
|
|
33
|
-
"baostock>=0.8.9",
|
|
34
|
-
"litellm>=1.81.14",
|
|
35
|
-
"websockets>=16.0",
|
|
36
25
|
"ta>=0.11.0",
|
|
26
|
+
"baostock>=0.9.1",
|
|
37
27
|
]
|
|
38
28
|
|
|
39
29
|
[project.optional-dependencies]
|
|
40
30
|
dev = [
|
|
41
|
-
"mkdocs>=1.6.1",
|
|
42
|
-
"mkdocs-material>=9.6.14",
|
|
43
|
-
"mkdocstrings>=0.29.1",
|
|
44
|
-
"mkdocstrings-python>=1.16.12",
|
|
45
31
|
"python-semantic-release>=9.0.0",
|
|
46
32
|
"pytest>=7.0.0",
|
|
47
33
|
"pytest-cov>=4.0.0",
|
|
@@ -50,14 +36,11 @@ dev = [
|
|
|
50
36
|
|
|
51
37
|
[project.urls]
|
|
52
38
|
Home = "https://github.com/zsrl/qka"
|
|
53
|
-
Documentation = "https://zsrl.github.io/qka"
|
|
54
39
|
Repository = "https://github.com/zsrl/qka"
|
|
55
40
|
Issues = "https://github.com/zsrl/qka/issues"
|
|
56
41
|
|
|
57
42
|
[project.scripts]
|
|
58
|
-
qka-zmq-server = "qka.server.zmq_server:main"
|
|
59
43
|
qka = "qka.cli:main"
|
|
60
|
-
qka-ws-client = "qka.server.ws_client:main"
|
|
61
44
|
|
|
62
45
|
[tool.hatch.version]
|
|
63
46
|
source = "vcs"
|
|
@@ -20,16 +20,11 @@ from qka.core.broker import Broker
|
|
|
20
20
|
from qka.core.sizing import SizingAccessor
|
|
21
21
|
|
|
22
22
|
# 子模块导入
|
|
23
|
-
from qka import core, utils
|
|
24
|
-
|
|
25
|
-
# 交易相关(有依赖的模块暂时不导入,避免导入错误)
|
|
26
|
-
# from qka.brokers.trade import create_trader
|
|
27
|
-
# from qka.brokers.client import QMTClient
|
|
28
|
-
# from qka.brokers.server import QMTServer
|
|
23
|
+
from qka import core, utils
|
|
29
24
|
|
|
30
25
|
__all__ = [
|
|
31
26
|
# 核心功能
|
|
32
27
|
'Data', 'Backtest', 'Strategy', 'Broker', 'DataAccessor', 'SizingAccessor',
|
|
33
28
|
# 子模块
|
|
34
|
-
'core', 'utils'
|
|
29
|
+
'core', 'utils'
|
|
35
30
|
]
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""
|
|
2
|
+
QKA命令行接口
|
|
3
|
+
"""
|
|
4
|
+
import argparse
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def main():
|
|
9
|
+
parser = argparse.ArgumentParser(description="QKA量化交易框架命令行工具")
|
|
10
|
+
parser.add_argument("--version", action="store_true", help="显示版本信息")
|
|
11
|
+
|
|
12
|
+
args = parser.parse_args()
|
|
13
|
+
if args.version:
|
|
14
|
+
from qka import __version__
|
|
15
|
+
print(f"qka {__version__}")
|
|
16
|
+
else:
|
|
17
|
+
parser.print_help()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
if __name__ == "__main__":
|
|
21
|
+
main()
|
|
@@ -9,6 +9,9 @@ import numpy as np
|
|
|
9
9
|
import dask.dataframe as dd
|
|
10
10
|
from typing import Optional, Union
|
|
11
11
|
from collections import defaultdict
|
|
12
|
+
from qka.core.broker import Broker
|
|
13
|
+
from qka.core.accessor import DataAccessor
|
|
14
|
+
from qka.core.sizing import SizingAccessor
|
|
12
15
|
|
|
13
16
|
|
|
14
17
|
class Backtest:
|
|
@@ -36,9 +39,20 @@ class Backtest:
|
|
|
36
39
|
self.data = data
|
|
37
40
|
self.strategy = strategy
|
|
38
41
|
self.results = None
|
|
39
|
-
self.initial_cash =
|
|
42
|
+
self.initial_cash = None # 由 run() 设置
|
|
43
|
+
self.metrics = None
|
|
40
44
|
self._benchmark_data = None
|
|
41
45
|
|
|
46
|
+
@property
|
|
47
|
+
def trade_history(self):
|
|
48
|
+
"""逐笔交易明细(list[dict])"""
|
|
49
|
+
return self.strategy.broker.trade_history if hasattr(self.strategy, 'broker') else []
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def trades(self):
|
|
53
|
+
"""每日资金/市值/持仓快照(pd.DataFrame)"""
|
|
54
|
+
return self.strategy.broker.trades if hasattr(self.strategy, 'broker') else pd.DataFrame()
|
|
55
|
+
|
|
42
56
|
@staticmethod
|
|
43
57
|
def _parse_row(row):
|
|
44
58
|
"""
|
|
@@ -63,7 +77,9 @@ class Backtest:
|
|
|
63
77
|
by_factor[factor][symbol] = val
|
|
64
78
|
return dict(by_factor)
|
|
65
79
|
|
|
66
|
-
def run(self,
|
|
80
|
+
def run(self, cash: float = 100000.0,
|
|
81
|
+
start_date: str = None, end_date: str = None,
|
|
82
|
+
benchmark: Optional[str] = None):
|
|
67
83
|
"""
|
|
68
84
|
执行回测
|
|
69
85
|
|
|
@@ -74,15 +90,23 @@ class Backtest:
|
|
|
74
90
|
避免一次性加载全量数据。
|
|
75
91
|
|
|
76
92
|
Args:
|
|
77
|
-
|
|
78
|
-
|
|
93
|
+
cash: 初始资金,默认 10 万元
|
|
94
|
+
start_date: 回测起始日期 YYYY-MM-DD,None 表示数据最早日期
|
|
95
|
+
end_date: 回测截止日期 YYYY-MM-DD,None 表示数据最晚日期
|
|
96
|
+
benchmark: 基准代码,如 '000300.SH'(沪深300)。
|
|
97
|
+
如果提供,会下载基准数据用于对比。
|
|
79
98
|
|
|
80
99
|
Returns:
|
|
81
|
-
None。回测结果保存在 self.results
|
|
82
|
-
self.summary() 查看绩效指标,self.report() 生成报告。
|
|
100
|
+
None。回测结果保存在 self.results、self.metrics、self.trade_history 中。
|
|
83
101
|
"""
|
|
84
|
-
#
|
|
85
|
-
|
|
102
|
+
# 注入基础设施
|
|
103
|
+
self.initial_cash = cash
|
|
104
|
+
self.strategy.broker = Broker(initial_cash=cash)
|
|
105
|
+
self.strategy.sizing = SizingAccessor(self.strategy.broker)
|
|
106
|
+
self.strategy._data = DataAccessor()
|
|
107
|
+
|
|
108
|
+
# 获取数据
|
|
109
|
+
raw = self.data.get(lazy=True, start_date=start_date, end_date=end_date)
|
|
86
110
|
|
|
87
111
|
# 加载基准数据
|
|
88
112
|
if benchmark:
|
|
@@ -137,6 +161,7 @@ class Backtest:
|
|
|
137
161
|
|
|
138
162
|
# 保存回测结果
|
|
139
163
|
self.results = self.strategy.broker.trades
|
|
164
|
+
self.metrics = self._compute_metrics()
|
|
140
165
|
|
|
141
166
|
def _load_benchmark(self, benchmark_code: str):
|
|
142
167
|
"""
|
|
@@ -146,38 +171,27 @@ class Backtest:
|
|
|
146
171
|
benchmark_code: 基准代码,如 '000300.SH'
|
|
147
172
|
"""
|
|
148
173
|
try:
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
bm_df =
|
|
174
|
+
from qka.core.data import Data
|
|
175
|
+
bm_data = Data(symbols=[benchmark_code], source='baostock')
|
|
176
|
+
bm_df = bm_data.get(lazy=False)
|
|
152
177
|
if bm_df is not None and not bm_df.empty:
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
178
|
+
# 提取 close 列(可能是多股票 MultiIndex,取第一只)
|
|
179
|
+
close_col = [c for c in bm_df.columns if 'close' in str(c).lower()]
|
|
180
|
+
if close_col:
|
|
181
|
+
self._benchmark_data = bm_df[close_col[0]]
|
|
182
|
+
else:
|
|
183
|
+
self._benchmark_data = bm_df.iloc[:, 0]
|
|
157
184
|
print(f"基准数据加载成功: {benchmark_code},{len(bm_df)} 个交易日")
|
|
158
185
|
except Exception as e:
|
|
159
186
|
print(f"基准数据加载失败: {e}")
|
|
160
187
|
|
|
161
|
-
def
|
|
162
|
-
"""
|
|
163
|
-
计算并打印回测绩效指标
|
|
164
|
-
|
|
165
|
-
返回包含以下指标的字典:
|
|
166
|
-
- 总收益率、年化收益率、年化波动率
|
|
167
|
-
- 夏普比率、最大回撤、Calmar比率
|
|
168
|
-
- 胜率、盈亏比、交易次数
|
|
169
|
-
- 最终资产、总手续费
|
|
170
|
-
|
|
171
|
-
Returns:
|
|
172
|
-
dict: 绩效指标字典
|
|
173
|
-
"""
|
|
188
|
+
def _compute_metrics(self) -> dict:
|
|
189
|
+
"""计算绩效指标。无数据时返回空 dict。"""
|
|
174
190
|
if self.results is None or self.results.empty:
|
|
175
|
-
print("请先运行回测 (backtest.run())")
|
|
176
191
|
return {}
|
|
177
192
|
|
|
178
193
|
totals = self.results['total']
|
|
179
194
|
if len(totals) < 2:
|
|
180
|
-
print("回测数据不足(至少需要2个交易周期)")
|
|
181
195
|
return {}
|
|
182
196
|
|
|
183
197
|
# 基本数据
|
|
@@ -192,7 +206,6 @@ class Backtest:
|
|
|
192
206
|
# 日收益率序列
|
|
193
207
|
daily_returns = totals.pct_change().dropna()
|
|
194
208
|
if len(daily_returns) == 0:
|
|
195
|
-
print("没有足够的收益率数据")
|
|
196
209
|
return {}
|
|
197
210
|
|
|
198
211
|
# 年化收益率
|
|
@@ -257,30 +270,10 @@ class Backtest:
|
|
|
257
270
|
else:
|
|
258
271
|
win_rate = 0
|
|
259
272
|
profit_loss_ratio = 0
|
|
260
|
-
trade_pnl = []
|
|
261
273
|
|
|
262
274
|
# 总手续费
|
|
263
275
|
total_commission = self.strategy.broker.total_commission
|
|
264
276
|
|
|
265
|
-
# 打印报告
|
|
266
|
-
print("=" * 55)
|
|
267
|
-
print(" 回测绩效报告")
|
|
268
|
-
print("=" * 55)
|
|
269
|
-
print(f" 初始资金: RMB {initial:>10,.2f}")
|
|
270
|
-
print(f" 最终资产: RMB {final:>10,.2f}")
|
|
271
|
-
print(f" 总收益率: {total_return:>+8.2f}%")
|
|
272
|
-
print(f" 年化收益率: {annual_return * 100:>+8.2f}%")
|
|
273
|
-
print(f" 年化波动率: {annual_vol * 100:>8.2f}%")
|
|
274
|
-
print(f" 夏普比率: {sharpe:>8.2f}")
|
|
275
|
-
print(f" 最大回撤: {max_drawdown:>8.2f}%")
|
|
276
|
-
print(f" Calmar比率: {calmar:>8.2f}")
|
|
277
|
-
print(f" 交易次数: {n_trades:>8}")
|
|
278
|
-
print(f" 胜率: {win_rate:>8.2f}%")
|
|
279
|
-
print(f" 盈亏比: {profit_loss_ratio:>8.2f}")
|
|
280
|
-
print(f" 总手续费: RMB {total_commission:>10,.2f}")
|
|
281
|
-
print(f" 回测天数: {n_days:>8} 天")
|
|
282
|
-
print("=" * 55)
|
|
283
|
-
|
|
284
277
|
return {
|
|
285
278
|
'initial_cash': initial,
|
|
286
279
|
'final_equity': final,
|