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.
Files changed (47) hide show
  1. {qka-1.3.1.dev5 → qka-1.4.1.dev3}/PKG-INFO +2 -1
  2. {qka-1.3.1.dev5 → qka-1.4.1.dev3}/docs/getting-started/concepts.md +72 -8
  3. {qka-1.3.1.dev5 → qka-1.4.1.dev3}/docs/getting-started/first-strategy.md +68 -0
  4. {qka-1.3.1.dev5 → qka-1.4.1.dev3}/docs/user-guide/data.md +60 -0
  5. {qka-1.3.1.dev5 → qka-1.4.1.dev3}/pyproject.toml +1 -0
  6. {qka-1.3.1.dev5 → qka-1.4.1.dev3}/qka/core/data.py +101 -3
  7. {qka-1.3.1.dev5 → qka-1.4.1.dev3}/qka/core/strategy.py +6 -1
  8. {qka-1.3.1.dev5 → qka-1.4.1.dev3}/.github/workflows/docs.yml +0 -0
  9. {qka-1.3.1.dev5 → qka-1.4.1.dev3}/.github/workflows/release.yml +0 -0
  10. {qka-1.3.1.dev5 → qka-1.4.1.dev3}/.gitignore +0 -0
  11. {qka-1.3.1.dev5 → qka-1.4.1.dev3}/.vscode/settings.json +0 -0
  12. {qka-1.3.1.dev5 → qka-1.4.1.dev3}/CHANGELOG.md +0 -0
  13. {qka-1.3.1.dev5 → qka-1.4.1.dev3}/LICENSE +0 -0
  14. {qka-1.3.1.dev5 → qka-1.4.1.dev3}/README.md +0 -0
  15. {qka-1.3.1.dev5 → qka-1.4.1.dev3}/docs/advanced/performance.md +0 -0
  16. {qka-1.3.1.dev5 → qka-1.4.1.dev3}/docs/api/brokers.md +0 -0
  17. {qka-1.3.1.dev5 → qka-1.4.1.dev3}/docs/api/core.md +0 -0
  18. {qka-1.3.1.dev5 → qka-1.4.1.dev3}/docs/api/utils.md +0 -0
  19. {qka-1.3.1.dev5 → qka-1.4.1.dev3}/docs/getting-started/installation.md +0 -0
  20. {qka-1.3.1.dev5 → qka-1.4.1.dev3}/docs/index.md +0 -0
  21. {qka-1.3.1.dev5 → qka-1.4.1.dev3}/docs/user-guide/backtest.md +0 -0
  22. {qka-1.3.1.dev5 → qka-1.4.1.dev3}/docs/user-guide/trading.md +0 -0
  23. {qka-1.3.1.dev5 → qka-1.4.1.dev3}/mkdocs.yml +0 -0
  24. {qka-1.3.1.dev5 → qka-1.4.1.dev3}/qka/__init__.py +0 -0
  25. {qka-1.3.1.dev5 → qka-1.4.1.dev3}/qka/brokers/__init__.py +0 -0
  26. {qka-1.3.1.dev5 → qka-1.4.1.dev3}/qka/brokers/client.py +0 -0
  27. {qka-1.3.1.dev5 → qka-1.4.1.dev3}/qka/brokers/server.py +0 -0
  28. {qka-1.3.1.dev5 → qka-1.4.1.dev3}/qka/brokers/trade.py +0 -0
  29. {qka-1.3.1.dev5 → qka-1.4.1.dev3}/qka/cli.py +0 -0
  30. {qka-1.3.1.dev5 → qka-1.4.1.dev3}/qka/core/__init__.py +0 -0
  31. {qka-1.3.1.dev5 → qka-1.4.1.dev3}/qka/core/accessor.py +0 -0
  32. {qka-1.3.1.dev5 → qka-1.4.1.dev3}/qka/core/backtest.py +0 -0
  33. {qka-1.3.1.dev5 → qka-1.4.1.dev3}/qka/core/broker.py +0 -0
  34. {qka-1.3.1.dev5 → qka-1.4.1.dev3}/qka/core/report.py +0 -0
  35. {qka-1.3.1.dev5 → qka-1.4.1.dev3}/qka/mcp/__init__.py +0 -0
  36. {qka-1.3.1.dev5 → qka-1.4.1.dev3}/qka/mcp/api.py +0 -0
  37. {qka-1.3.1.dev5 → qka-1.4.1.dev3}/qka/mcp/server.py +0 -0
  38. {qka-1.3.1.dev5 → qka-1.4.1.dev3}/qka/server/__init__.py +0 -0
  39. {qka-1.3.1.dev5 → qka-1.4.1.dev3}/qka/server/handlers/__init__.py +0 -0
  40. {qka-1.3.1.dev5 → qka-1.4.1.dev3}/qka/server/handlers/class_inspector_handler.py +0 -0
  41. {qka-1.3.1.dev5 → qka-1.4.1.dev3}/qka/server/handlers/code_executor_handler.py +0 -0
  42. {qka-1.3.1.dev5 → qka-1.4.1.dev3}/qka/server/ws_client.py +0 -0
  43. {qka-1.3.1.dev5 → qka-1.4.1.dev3}/qka/server/zmq_server.py +0 -0
  44. {qka-1.3.1.dev5 → qka-1.4.1.dev3}/qka/utils/__init__.py +0 -0
  45. {qka-1.3.1.dev5 → qka-1.4.1.dev3}/qka/utils/anis.py +0 -0
  46. {qka-1.3.1.dev5 → qka-1.4.1.dev3}/qka/utils/logger.py +0 -0
  47. {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.1.dev5
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=250), │
62
- │ '600000.SH': deque(maxlen=250), │
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
- self.get() self.history()
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}`:
@@ -33,6 +33,7 @@ dependencies = [
33
33
  "baostock>=0.8.9",
34
34
  "litellm>=1.81.14",
35
35
  "websockets>=16.0",
36
+ "ta>=0.11.0",
36
37
  ]
37
38
 
38
39
  [project.optional-dependencies]
@@ -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: 缓存根目录,默认为当前工作目录下的 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=250)
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