qka 1.3.1.dev5__tar.gz → 1.4.1.dev3__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.3.1.dev5 → qka-1.4.1.dev3}/PKG-INFO +2 -1
- {qka-1.3.1.dev5 → qka-1.4.1.dev3}/docs/getting-started/concepts.md +72 -8
- {qka-1.3.1.dev5 → qka-1.4.1.dev3}/docs/getting-started/first-strategy.md +68 -0
- {qka-1.3.1.dev5 → qka-1.4.1.dev3}/docs/user-guide/data.md +60 -0
- {qka-1.3.1.dev5 → qka-1.4.1.dev3}/pyproject.toml +1 -0
- {qka-1.3.1.dev5 → qka-1.4.1.dev3}/qka/core/data.py +101 -3
- {qka-1.3.1.dev5 → qka-1.4.1.dev3}/qka/core/strategy.py +6 -1
- {qka-1.3.1.dev5 → qka-1.4.1.dev3}/.github/workflows/docs.yml +0 -0
- {qka-1.3.1.dev5 → qka-1.4.1.dev3}/.github/workflows/release.yml +0 -0
- {qka-1.3.1.dev5 → qka-1.4.1.dev3}/.gitignore +0 -0
- {qka-1.3.1.dev5 → qka-1.4.1.dev3}/.vscode/settings.json +0 -0
- {qka-1.3.1.dev5 → qka-1.4.1.dev3}/CHANGELOG.md +0 -0
- {qka-1.3.1.dev5 → qka-1.4.1.dev3}/LICENSE +0 -0
- {qka-1.3.1.dev5 → qka-1.4.1.dev3}/README.md +0 -0
- {qka-1.3.1.dev5 → qka-1.4.1.dev3}/docs/advanced/performance.md +0 -0
- {qka-1.3.1.dev5 → qka-1.4.1.dev3}/docs/api/brokers.md +0 -0
- {qka-1.3.1.dev5 → qka-1.4.1.dev3}/docs/api/core.md +0 -0
- {qka-1.3.1.dev5 → qka-1.4.1.dev3}/docs/api/utils.md +0 -0
- {qka-1.3.1.dev5 → qka-1.4.1.dev3}/docs/getting-started/installation.md +0 -0
- {qka-1.3.1.dev5 → qka-1.4.1.dev3}/docs/index.md +0 -0
- {qka-1.3.1.dev5 → qka-1.4.1.dev3}/docs/user-guide/backtest.md +0 -0
- {qka-1.3.1.dev5 → qka-1.4.1.dev3}/docs/user-guide/trading.md +0 -0
- {qka-1.3.1.dev5 → qka-1.4.1.dev3}/mkdocs.yml +0 -0
- {qka-1.3.1.dev5 → qka-1.4.1.dev3}/qka/__init__.py +0 -0
- {qka-1.3.1.dev5 → qka-1.4.1.dev3}/qka/brokers/__init__.py +0 -0
- {qka-1.3.1.dev5 → qka-1.4.1.dev3}/qka/brokers/client.py +0 -0
- {qka-1.3.1.dev5 → qka-1.4.1.dev3}/qka/brokers/server.py +0 -0
- {qka-1.3.1.dev5 → qka-1.4.1.dev3}/qka/brokers/trade.py +0 -0
- {qka-1.3.1.dev5 → qka-1.4.1.dev3}/qka/cli.py +0 -0
- {qka-1.3.1.dev5 → qka-1.4.1.dev3}/qka/core/__init__.py +0 -0
- {qka-1.3.1.dev5 → qka-1.4.1.dev3}/qka/core/accessor.py +0 -0
- {qka-1.3.1.dev5 → qka-1.4.1.dev3}/qka/core/backtest.py +0 -0
- {qka-1.3.1.dev5 → qka-1.4.1.dev3}/qka/core/broker.py +0 -0
- {qka-1.3.1.dev5 → qka-1.4.1.dev3}/qka/core/report.py +0 -0
- {qka-1.3.1.dev5 → qka-1.4.1.dev3}/qka/mcp/__init__.py +0 -0
- {qka-1.3.1.dev5 → qka-1.4.1.dev3}/qka/mcp/api.py +0 -0
- {qka-1.3.1.dev5 → qka-1.4.1.dev3}/qka/mcp/server.py +0 -0
- {qka-1.3.1.dev5 → qka-1.4.1.dev3}/qka/server/__init__.py +0 -0
- {qka-1.3.1.dev5 → qka-1.4.1.dev3}/qka/server/handlers/__init__.py +0 -0
- {qka-1.3.1.dev5 → qka-1.4.1.dev3}/qka/server/handlers/class_inspector_handler.py +0 -0
- {qka-1.3.1.dev5 → qka-1.4.1.dev3}/qka/server/handlers/code_executor_handler.py +0 -0
- {qka-1.3.1.dev5 → qka-1.4.1.dev3}/qka/server/ws_client.py +0 -0
- {qka-1.3.1.dev5 → qka-1.4.1.dev3}/qka/server/zmq_server.py +0 -0
- {qka-1.3.1.dev5 → qka-1.4.1.dev3}/qka/utils/__init__.py +0 -0
- {qka-1.3.1.dev5 → qka-1.4.1.dev3}/qka/utils/anis.py +0 -0
- {qka-1.3.1.dev5 → qka-1.4.1.dev3}/qka/utils/logger.py +0 -0
- {qka-1.3.1.dev5 → qka-1.4.1.dev3}/qka/utils/util.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: qka
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.1.dev3
|
|
4
4
|
Summary: 快捷量化助手(Quick Quantitative Assistant)- 简洁易用的A股量化交易框架
|
|
5
5
|
Project-URL: Home, https://github.com/zsrl/qka
|
|
6
6
|
Project-URL: Documentation, https://zsrl.github.io/qka
|
|
@@ -46,6 +46,7 @@ Requires-Dist: mcp[cli]>=1.9.0
|
|
|
46
46
|
Requires-Dist: nbformat>=5.10.4
|
|
47
47
|
Requires-Dist: plotly>=6.1.1
|
|
48
48
|
Requires-Dist: pyarrow>=21.0.0
|
|
49
|
+
Requires-Dist: ta>=0.11.0
|
|
49
50
|
Requires-Dist: tqdm>=4.67.1
|
|
50
51
|
Requires-Dist: uvicorn>=0.34.3
|
|
51
52
|
Requires-Dist: websockets>=16.0
|
|
@@ -44,11 +44,11 @@ Data ──> Backtest.run() ──> dask 分区迭代 ──> Strategy.on_bar(da
|
|
|
44
44
|
|
|
45
45
|
---
|
|
46
46
|
|
|
47
|
-
##
|
|
47
|
+
## 核心抽象
|
|
48
48
|
|
|
49
49
|
### `DataAccessor` — 滚动窗口数据访问器
|
|
50
50
|
|
|
51
|
-
DataAccessor
|
|
51
|
+
DataAccessor 是策略访问数据的中枢。它在回测过程中维护一个滚动窗口缓存,支持三种查询方式:
|
|
52
52
|
|
|
53
53
|
```
|
|
54
54
|
push(date, 'close', {symbol: value})
|
|
@@ -58,20 +58,24 @@ DataAccessor 是策略访问数据的中枢。它在回测过程中维护一个
|
|
|
58
58
|
│ DataAccessor 内部缓存 │
|
|
59
59
|
│ │
|
|
60
60
|
│ close: { │
|
|
61
|
-
│ '000001.SZ': deque(maxlen=
|
|
62
|
-
│ '600000.SH': deque(maxlen=
|
|
61
|
+
│ '000001.SZ': deque(maxlen=750), │
|
|
62
|
+
│ '600000.SH': deque(maxlen=750), │
|
|
63
63
|
│ ... │
|
|
64
64
|
│ } │
|
|
65
65
|
│ volume: { ... } │
|
|
66
|
+
│ sma_20: { ... } ← 预计算指标 │
|
|
67
|
+
│ rsi_14: { ... } │
|
|
66
68
|
└─────────────────────────────────────┘
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
│ │
|
|
70
|
+
▼ ▼
|
|
71
|
+
self.get() self.history()
|
|
72
|
+
(横截面) (时间序列)
|
|
71
73
|
```
|
|
72
74
|
|
|
73
75
|
#### `self.get(factor)` — 横截面数据
|
|
74
76
|
|
|
77
|
+
#### `self.get(factor)` — 横截面数据
|
|
78
|
+
|
|
75
79
|
返回**当前 bar** 所有股票的某个因子值:
|
|
76
80
|
|
|
77
81
|
```python
|
|
@@ -95,6 +99,66 @@ hist = self.history('close', 20)
|
|
|
95
99
|
# ... ... ... ...
|
|
96
100
|
```
|
|
97
101
|
|
|
102
|
+
### 预计算技术指标(Data 层面)
|
|
103
|
+
|
|
104
|
+
技术指标在数据层预计算,作为额外的列存在于合并 DataFrame 中,策略中直接通过 `self.get()` 和 `self.history()` 查询。
|
|
105
|
+
|
|
106
|
+
#### 在 Data 中声明
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
data = Data(
|
|
110
|
+
symbols=['000001.SZ', '600000.SH'],
|
|
111
|
+
indicators={
|
|
112
|
+
'sma_20': ('sma', 20), # 20 日均线
|
|
113
|
+
'ema_14': ('ema', 14), # 14 日指数均线
|
|
114
|
+
'rsi_14': ('rsi', 14), # 14 日 RSI
|
|
115
|
+
'macd': ('macd', 12, 26, 9), # MACD(生成 3 列)
|
|
116
|
+
'bbands': ('bbands', 20, 2), # 布林带(生成 3 列)
|
|
117
|
+
}
|
|
118
|
+
)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
指标在数据加载时一次性预计算,每只股票会增加对应的列:
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
合并后列名格式:{symbol}_{指标名}
|
|
125
|
+
例如:000001.SZ_sma_20, 000001.SZ_macd, 000001.SZ_macd_signal
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
#### 在策略中使用
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
def on_bar(self, date):
|
|
132
|
+
# 直接取横截面(不用每 bar 现算)
|
|
133
|
+
sma_20 = self.get('sma_20')
|
|
134
|
+
price = self.get('close')
|
|
135
|
+
|
|
136
|
+
# 历史数据同样支持
|
|
137
|
+
macd_hist = self.history('macd', 10)
|
|
138
|
+
bb_upper_hist = self.history('bbands_upper', 5)
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
#### 支持的指标
|
|
142
|
+
|
|
143
|
+
| 指标名 | 参数 | 生成列 | 备注 |
|
|
144
|
+
|--------|------|--------|------|
|
|
145
|
+
| `sma` | `(length)` | `sma_20` | 简单移动平均 |
|
|
146
|
+
| `ema` | `(length)` | `ema_14` | 指数移动平均 |
|
|
147
|
+
| `wma` | `(length)` | `wma_30` | 加权移动平均 |
|
|
148
|
+
| `rsi` | `(length)` | `rsi_14` | 相对强弱指数 |
|
|
149
|
+
| `macd` | `(fast, slow, signal)` | `macd`, `macd_signal`, `macd_histogram` | 指数平滑异同 |
|
|
150
|
+
| `bbands` | `(length, std)` | `bbands_upper`, `bbands_middle`, `bbands_lower` | 布林带 |
|
|
151
|
+
| `atr` | `(length)` | `atr_14` | 平均真实波幅 |
|
|
152
|
+
|
|
153
|
+
可指定自定义因子列:`('sma', 'high', 20)` 表示对 `high` 列计算 SMA。
|
|
154
|
+
|
|
155
|
+
#### 对比:预计算 vs 动态计算
|
|
156
|
+
|
|
157
|
+
| 方式 | 性能 | 策略代码简洁度 | 历史数据支持 |
|
|
158
|
+
|------|------|---------------|-------------|
|
|
159
|
+
| **预计算(推荐)** | 一次算好,各 bar 复用 | `self.get('sma_20')` | ✅ `self.history('sma_20', 20)` |
|
|
160
|
+
| 动态计算(旧方式) | 每 bar 重复算 | `self.ta.sma('close', length=20)` | ❌ 需额外逻辑 |
|
|
161
|
+
|
|
98
162
|
---
|
|
99
163
|
|
|
100
164
|
### `Broker` — 虚拟经纪商
|
|
@@ -55,10 +55,17 @@ class BuyAndHold(Strategy):
|
|
|
55
55
|
|------|------|------|
|
|
56
56
|
| `self.get('close')` | 当前 bar 所有股票收盘价 | `pd.Series`(index=股票代码) |
|
|
57
57
|
| `self.history('close', 20)` | 过去 N 日收盘价历史 | `pd.DataFrame`(行=日期,列=股票代码) |
|
|
58
|
+
| `self.get('sma_20')` | 预计算的 20 日均线 | `pd.Series` |
|
|
59
|
+
| `self.history('sma_20', 10)` | 过去 10 日 SMA 历史 | `pd.DataFrame` |
|
|
58
60
|
|
|
59
61
|
!!! tip "不再是闭包"
|
|
60
62
|
与旧版本不同,`on_bar` 不再接收 `get` 参数。所有数据通过 `self.get()` 和 `self.history()` 访问,代码更简洁一致。
|
|
61
63
|
|
|
64
|
+
!!! tip "技术指标预计算"
|
|
65
|
+
技术指标在 `Data` 层通过 `indicators=...` 预计算,作为额外的列加载。
|
|
66
|
+
策略中直接用 `self.get('sma_20')` 获取,无需每 bar 动态计算。
|
|
67
|
+
详见 [核心概念 - 预计算技术指标](concepts.md)。
|
|
68
|
+
|
|
62
69
|
---
|
|
63
70
|
|
|
64
71
|
## 步骤 3:运行回测
|
|
@@ -192,6 +199,67 @@ class MaCross(Strategy):
|
|
|
192
199
|
self.bought = False
|
|
193
200
|
```
|
|
194
201
|
|
|
202
|
+
## 进阶 2:用预计算指标实现 RSI 策略
|
|
203
|
+
|
|
204
|
+
在 `Data` 中声明需要预计算的指标,策略中直接使用:
|
|
205
|
+
|
|
206
|
+
```python
|
|
207
|
+
from qka import Data, Strategy, Broker, Backtest
|
|
208
|
+
|
|
209
|
+
class RsiStrategy(Strategy):
|
|
210
|
+
"""RSI 超卖买入,超买卖出"""
|
|
211
|
+
def __init__(self):
|
|
212
|
+
super().__init__()
|
|
213
|
+
self.broker = Broker(initial_cash=100_000)
|
|
214
|
+
|
|
215
|
+
def on_bar(self, date):
|
|
216
|
+
close = self.get('close')
|
|
217
|
+
rsi = self.get('rsi_14')
|
|
218
|
+
if close is None or close.empty or rsi is None or rsi.empty:
|
|
219
|
+
return
|
|
220
|
+
|
|
221
|
+
for sym in close.index:
|
|
222
|
+
if sym not in rsi.index:
|
|
223
|
+
continue
|
|
224
|
+
price = float(close[sym])
|
|
225
|
+
if price <= 0:
|
|
226
|
+
continue
|
|
227
|
+
|
|
228
|
+
if rsi[sym] < 30 and sym not in self.broker.positions:
|
|
229
|
+
# RSI 低于 30,超卖,买入
|
|
230
|
+
self.broker.buy(sym, price, 100)
|
|
231
|
+
elif rsi[sym] > 70 and sym in self.broker.positions:
|
|
232
|
+
# RSI 高于 70,超买,卖出
|
|
233
|
+
pos = self.broker.positions[sym]
|
|
234
|
+
self.broker.sell(sym, price, pos['size'])
|
|
235
|
+
|
|
236
|
+
# 在 Data 中声明指标
|
|
237
|
+
data = Data(
|
|
238
|
+
symbols=['000001.SZ', '600000.SH'],
|
|
239
|
+
indicators={
|
|
240
|
+
'rsi_14': ('rsi', 14), # 预计算 14 日 RSI
|
|
241
|
+
'sma_20': ('sma', 20), # 同时预计算 20 日均线
|
|
242
|
+
},
|
|
243
|
+
)
|
|
244
|
+
bt = Backtest(data, RsiStrategy())
|
|
245
|
+
bt.run()
|
|
246
|
+
bt.summary()
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
支持的所有预计算指标:
|
|
250
|
+
|
|
251
|
+
| 指标名 | 声明方式 | 策略中获取 | 多输出列 |
|
|
252
|
+
|--------|---------|-----------|---------|
|
|
253
|
+
| SMA | `('sma', 20)` | `self.get('sma_20')` | — |
|
|
254
|
+
| EMA | `('ema', 14)` | `self.get('ema_14')` | — |
|
|
255
|
+
| RSI | `('rsi', 14)` | `self.get('rsi_14')` | — |
|
|
256
|
+
| ATR | `('atr', 14)` | `self.get('atr_14')` | — |
|
|
257
|
+
| MACD | `('macd', 12, 26, 9)` | `self.get('macd')` | 额外生成 `macd_signal`, `macd_histogram` |
|
|
258
|
+
| BBANDS | `('bbands', 20, 2)` | `self.get('bbands_upper')` | 额外生成 `bbands_middle`, `bbands_lower` |
|
|
259
|
+
|
|
260
|
+
> **预计算 vs 动态计算**:预计算在数据加载阶段一次性算好所有指标的完整序列。策略中每根 bar 拿到的既是当前值,也能查历史(`self.history('rsi_14', 10)`)。动态计算每 bar 重新算一遍,性能差且历史不可查。**始终使用预计算。**
|
|
261
|
+
```
|
|
262
|
+
|
|
195
263
|
---
|
|
196
264
|
|
|
197
265
|
## 下一步
|
|
@@ -87,6 +87,66 @@ data = Data(
|
|
|
87
87
|
|
|
88
88
|
---
|
|
89
89
|
|
|
90
|
+
## 预计算技术指标
|
|
91
|
+
|
|
92
|
+
通过 `indicators` 参数一键声明技术指标,数据加载时自动预计算:
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
from qka import Data
|
|
96
|
+
|
|
97
|
+
data = Data(
|
|
98
|
+
symbols=['000001.SZ', '600000.SH'],
|
|
99
|
+
indicators={
|
|
100
|
+
'sma_20': ('sma', 20), # 20 日均线
|
|
101
|
+
'sma_60': ('sma', 60), # 60 日均线
|
|
102
|
+
'ema_14': ('ema', 14), # 14 日指数均线
|
|
103
|
+
'rsi_14': ('rsi', 14), # 14 日 RSI
|
|
104
|
+
'macd': ('macd', 12, 26, 9), # MACD
|
|
105
|
+
'bbands': ('bbands', 20, 2), # 布林带
|
|
106
|
+
'atr_14': ('atr', 14), # 平均真实波幅
|
|
107
|
+
}
|
|
108
|
+
)
|
|
109
|
+
lazy = data.get(lazy=True)
|
|
110
|
+
# 合并后的列:... 000001.SZ_sma_20, 000001.SZ_rsi_14, 000001.SZ_macd, ...
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
指标列跟 `close`、`volume` 一样,在策略中通过 `self.get()` 和 `self.history()` 访问:
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
# 策略中
|
|
117
|
+
sma_20 = self.get('sma_20') # 横截面
|
|
118
|
+
macd_hist = self.history('macd', 10) # 历史序列
|
|
119
|
+
bb_upper = self.get('bbands_upper') # 布林带上轨
|
|
120
|
+
rsi_14 = self.get('rsi_14') # 当前 RSI
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### 与 `factor` 的区别
|
|
124
|
+
|
|
125
|
+
| | `factor` | `indicators` |
|
|
126
|
+
|--|----------|-------------|
|
|
127
|
+
| 适用场景 | 任意自定义计算 | 标准技术指标(SMA、MACD 等) |
|
|
128
|
+
| 写法 | Python 函数 | 声明式字典 |
|
|
129
|
+
| 多输出 | 手动添加多列 | 自动展开(MACD 生成 3 列) |
|
|
130
|
+
| 底层 | 任意 pandas 逻辑 | 基于 `ta` 库 |
|
|
131
|
+
|
|
132
|
+
二者可以同时使用,`indicators` 在 `factor` 之后执行。
|
|
133
|
+
|
|
134
|
+
### 支持的指标
|
|
135
|
+
|
|
136
|
+
| 指标名 | 参数 | 说明 | 生成列 |
|
|
137
|
+
|--------|------|------|--------|
|
|
138
|
+
| `sma` | `(length)` | 简单移动平均 | `sma_20` |
|
|
139
|
+
| `ema` | `(length)` | 指数移动平均 | `ema_14` |
|
|
140
|
+
| `wma` | `(length)` | 加权移动平均 | `wma_30` |
|
|
141
|
+
| `rsi` | `(length)` | 相对强弱指数 | `rsi_14` |
|
|
142
|
+
| `macd` | `(fast, slow, signal)` | 指数平滑异同 | `macd`, `macd_signal`, `macd_histogram` |
|
|
143
|
+
| `bbands` | `(length, std)` | 布林带 | `bbands_upper`, `bbands_middle`, `bbands_lower` |
|
|
144
|
+
| `atr` | `(length)` | 平均真实波幅 | `atr_14` |
|
|
145
|
+
|
|
146
|
+
可指定自定义因子列:`('sma', 'high', 20)` 表示对 `high` 列计算 20 日均线。
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
90
150
|
## 数据格式
|
|
91
151
|
|
|
92
152
|
`Data.get()` 返回的 DataFrame 列名格式为 `{symbol}_{factor}`:
|
|
@@ -20,13 +20,15 @@ class Data():
|
|
|
20
20
|
"""
|
|
21
21
|
数据管理类
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
负责股票数据的获取、缓存和管理,支持多数据源、并发下载、自定义因子计算
|
|
24
|
+
以及预计算技术指标。
|
|
24
25
|
|
|
25
26
|
Attributes:
|
|
26
27
|
symbols (List[str]): 股票代码列表
|
|
27
28
|
period (str): 数据周期,如 '1d'、'1m' 等
|
|
28
29
|
adjust (str): 复权方式,如 'qfq'、'hfq'、'bfq'
|
|
29
30
|
factor (Callable): 因子计算函数
|
|
31
|
+
indicators (dict): 预计算技术指标,col_name → (ind_type, *args)
|
|
30
32
|
source (str): 数据源,如 'baostock'(默认)、'akshare'、'qmt'
|
|
31
33
|
pool_size (int): 并发下载线程数
|
|
32
34
|
datadir (Path): 数据缓存目录
|
|
@@ -41,7 +43,8 @@ class Data():
|
|
|
41
43
|
factor: Callable[[pd.DataFrame], pd.DataFrame] = lambda df: df,
|
|
42
44
|
source: str = 'baostock',
|
|
43
45
|
pool_size: int = 10,
|
|
44
|
-
datadir: Optional[Path] = None
|
|
46
|
+
datadir: Optional[Path] = None,
|
|
47
|
+
indicators: Optional[dict] = None,
|
|
45
48
|
):
|
|
46
49
|
"""
|
|
47
50
|
初始化数据对象
|
|
@@ -53,12 +56,19 @@ class Data():
|
|
|
53
56
|
factor: 因子计算函数,接收 DataFrame 返回 DataFrame,用于扩展自定义因子
|
|
54
57
|
source: 数据来源,'baostock'(默认)、'akshare'、'qmt'
|
|
55
58
|
pool_size: 并发下载线程数
|
|
56
|
-
datadir:
|
|
59
|
+
datadir: 缓存目录路径
|
|
60
|
+
indicators: 预计算技术指标,格式为 {列名: (指标名, *参数)}
|
|
61
|
+
示例:{'sma_20': ('sma', 20), 'rsi_14': ('rsi', 14),
|
|
62
|
+
'macd': ('macd', 12, 26, 9)}
|
|
63
|
+
指标名支持:sma、ema、wma、rsi、macd、bbands、atr
|
|
64
|
+
factor 默认为 'close',多因子指标(如 bbands、atr)
|
|
65
|
+
使用标准列名 high/low/close
|
|
57
66
|
"""
|
|
58
67
|
self.symbols = symbols or []
|
|
59
68
|
self.period = period
|
|
60
69
|
self.adjust = adjust
|
|
61
70
|
self.factor = factor
|
|
71
|
+
self.indicators = indicators or {}
|
|
62
72
|
self.source = source
|
|
63
73
|
self.pool_size = pool_size
|
|
64
74
|
|
|
@@ -181,6 +191,7 @@ class Data():
|
|
|
181
191
|
continue
|
|
182
192
|
ddf = dd.read_parquet(str(parquet_path))
|
|
183
193
|
ddf = self.factor(ddf)
|
|
194
|
+
ddf = self._apply_indicators(ddf)
|
|
184
195
|
column_mapping = {col: f'{symbol}_{col}' for col in ddf.columns}
|
|
185
196
|
dfs.append(ddf.rename(columns=column_mapping))
|
|
186
197
|
|
|
@@ -204,6 +215,7 @@ class Data():
|
|
|
204
215
|
continue
|
|
205
216
|
df = dd.read_parquet(str(parquet_path))
|
|
206
217
|
df = self.factor(df)
|
|
218
|
+
df = self._apply_indicators(df)
|
|
207
219
|
column_mapping = {col: f'{symbol}_{col}' for col in df.columns}
|
|
208
220
|
dfs.append(df.rename(columns=column_mapping))
|
|
209
221
|
|
|
@@ -217,6 +229,92 @@ class Data():
|
|
|
217
229
|
self._cached = ddf.compute()
|
|
218
230
|
return self._cached
|
|
219
231
|
|
|
232
|
+
def _apply_indicators(self, df: 'pd.DataFrame'):
|
|
233
|
+
"""
|
|
234
|
+
对单只股票的数据计算预定义的技术指标。
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
df: 单只股票的 DataFrame
|
|
238
|
+
|
|
239
|
+
Returns:
|
|
240
|
+
DataFrame: 包含原始列和指标列
|
|
241
|
+
"""
|
|
242
|
+
if not self.indicators:
|
|
243
|
+
return df
|
|
244
|
+
|
|
245
|
+
if isinstance(df, dd.DataFrame):
|
|
246
|
+
return df.map_partitions(
|
|
247
|
+
lambda partition: self._compute_indicator_cols(partition.copy())
|
|
248
|
+
)
|
|
249
|
+
return self._compute_indicator_cols(df.copy())
|
|
250
|
+
|
|
251
|
+
def _compute_indicator_cols(self, df):
|
|
252
|
+
"""在 pandas DataFrame 上计算技术指标列(单只股票)。"""
|
|
253
|
+
import ta as _ta_lib
|
|
254
|
+
|
|
255
|
+
for col_name, spec in self.indicators.items():
|
|
256
|
+
# 统一解析:spec = (indicator_name, *args)
|
|
257
|
+
if isinstance(spec, (list, tuple)):
|
|
258
|
+
ind_type = spec[0]
|
|
259
|
+
args = list(spec[1:])
|
|
260
|
+
else:
|
|
261
|
+
raise TypeError(f"指标规格必须为元组,got {type(spec)}")
|
|
262
|
+
|
|
263
|
+
# 如果第一个参数是字符串,视为列名;否则默认为 'close'
|
|
264
|
+
factor = 'close'
|
|
265
|
+
rest = args
|
|
266
|
+
if args and isinstance(args[0], str):
|
|
267
|
+
factor = args[0]
|
|
268
|
+
rest = args[1:]
|
|
269
|
+
|
|
270
|
+
if ind_type == 'sma':
|
|
271
|
+
window = int(rest[0]) if rest else 20
|
|
272
|
+
df[col_name] = _ta_lib.trend.sma_indicator(df[factor], window=window)
|
|
273
|
+
|
|
274
|
+
elif ind_type == 'ema':
|
|
275
|
+
window = int(rest[0]) if rest else 30
|
|
276
|
+
df[col_name] = _ta_lib.trend.ema_indicator(df[factor], window=window)
|
|
277
|
+
|
|
278
|
+
elif ind_type == 'wma':
|
|
279
|
+
window = int(rest[0]) if rest else 30
|
|
280
|
+
df[col_name] = _ta_lib.trend.wma_indicator(df[factor], window=window)
|
|
281
|
+
|
|
282
|
+
elif ind_type == 'rsi':
|
|
283
|
+
window = int(rest[0]) if rest else 14
|
|
284
|
+
df[col_name] = _ta_lib.momentum.rsi(df[factor], window=window)
|
|
285
|
+
|
|
286
|
+
elif ind_type == 'macd':
|
|
287
|
+
fast = int(rest[0]) if len(rest) >= 1 else 12
|
|
288
|
+
slow = int(rest[1]) if len(rest) >= 2 else 26
|
|
289
|
+
signal = int(rest[2]) if len(rest) >= 3 else 9
|
|
290
|
+
_macd = _ta_lib.trend.MACD(
|
|
291
|
+
df[factor], window_slow=slow, window_fast=fast, window_sign=signal,
|
|
292
|
+
)
|
|
293
|
+
df[col_name] = _macd.macd()
|
|
294
|
+
df[f'{col_name}_signal'] = _macd.macd_signal()
|
|
295
|
+
df[f'{col_name}_histogram'] = _macd.macd_diff()
|
|
296
|
+
|
|
297
|
+
elif ind_type == 'bbands':
|
|
298
|
+
window = int(rest[0]) if rest else 20
|
|
299
|
+
std = int(rest[1]) if len(rest) >= 2 else 2
|
|
300
|
+
_bb = _ta_lib.volatility.BollingerBands(
|
|
301
|
+
df[factor], window=window, window_dev=std,
|
|
302
|
+
)
|
|
303
|
+
df[f'{col_name}_upper'] = _bb.bollinger_hband()
|
|
304
|
+
df[f'{col_name}_middle'] = _bb.bollinger_mavg()
|
|
305
|
+
df[f'{col_name}_lower'] = _bb.bollinger_lband()
|
|
306
|
+
|
|
307
|
+
elif ind_type == 'atr':
|
|
308
|
+
window = int(rest[0]) if rest else 14
|
|
309
|
+
df[col_name] = _ta_lib.volatility.average_true_range(
|
|
310
|
+
df['high'], df['low'], df['close'], window=window,
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
else:
|
|
314
|
+
logger.warning(f"未知指标类型: {ind_type},跳过 {col_name}")
|
|
315
|
+
|
|
316
|
+
return df
|
|
317
|
+
|
|
220
318
|
def _get_from_akshare(self, symbol: str) -> pd.DataFrame:
|
|
221
319
|
"""
|
|
222
320
|
从 akshare 获取单个股票的数据。
|
|
@@ -28,7 +28,7 @@ class Strategy(ABC):
|
|
|
28
28
|
cash: 初始资金,默认 10 万元
|
|
29
29
|
"""
|
|
30
30
|
self.broker = Broker(initial_cash=cash)
|
|
31
|
-
self._data = DataAccessor(max_window=
|
|
31
|
+
self._data = DataAccessor(max_window=750)
|
|
32
32
|
|
|
33
33
|
def get(self, factor: str):
|
|
34
34
|
"""
|
|
@@ -75,6 +75,11 @@ class Strategy(ABC):
|
|
|
75
75
|
# 历史序列(过去 N 天)
|
|
76
76
|
hist = self.history('close', 20)
|
|
77
77
|
|
|
78
|
+
# 技术指标(基于 ta 库)
|
|
79
|
+
sma20 = self.ta.sma('close', length=20)
|
|
80
|
+
rsi14 = self.ta.rsi('close', length=14)
|
|
81
|
+
macd = self.ta.macd('close', fast=12, slow=26, signal=9)
|
|
82
|
+
|
|
78
83
|
# 交易操作
|
|
79
84
|
if not close.empty and '000001.SZ' in close.index:
|
|
80
85
|
self.broker.buy('000001.SZ', float(close['000001.SZ']), 100)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|