qka 1.7.1.dev2__tar.gz → 1.8.1.dev2__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 (64) hide show
  1. {qka-1.7.1.dev2 → qka-1.8.1.dev2}/PKG-INFO +1 -1
  2. {qka-1.7.1.dev2 → qka-1.8.1.dev2}/pyproject.toml +1 -0
  3. {qka-1.7.1.dev2 → qka-1.8.1.dev2}/qka/core/data.py +15 -2
  4. qka-1.8.1.dev2/skills/qka/SKILL.md +578 -0
  5. {qka-1.7.1.dev2 → qka-1.8.1.dev2}/tools/generate_api_ref.py +78 -38
  6. qka-1.7.1.dev2/skills/qka/SKILL.md +0 -58
  7. qka-1.7.1.dev2/skills/qka/references/backtest.md +0 -108
  8. qka-1.7.1.dev2/skills/qka/references/broker.md +0 -92
  9. qka-1.7.1.dev2/skills/qka/references/data.md +0 -103
  10. qka-1.7.1.dev2/skills/qka/references/report.md +0 -37
  11. qka-1.7.1.dev2/skills/qka/references/sizing.md +0 -80
  12. qka-1.7.1.dev2/skills/qka/references/strategy.md +0 -136
  13. {qka-1.7.1.dev2 → qka-1.8.1.dev2}/.github/workflows/docs.yml +0 -0
  14. {qka-1.7.1.dev2 → qka-1.8.1.dev2}/.github/workflows/release.yml +0 -0
  15. {qka-1.7.1.dev2 → qka-1.8.1.dev2}/.gitignore +0 -0
  16. {qka-1.7.1.dev2 → qka-1.8.1.dev2}/.vscode/settings.json +0 -0
  17. {qka-1.7.1.dev2 → qka-1.8.1.dev2}/CHANGELOG.md +0 -0
  18. {qka-1.7.1.dev2 → qka-1.8.1.dev2}/LICENSE +0 -0
  19. {qka-1.7.1.dev2 → qka-1.8.1.dev2}/README.md +0 -0
  20. {qka-1.7.1.dev2 → qka-1.8.1.dev2}/docs/advanced/performance.md +0 -0
  21. {qka-1.7.1.dev2 → qka-1.8.1.dev2}/docs/api/brokers.md +0 -0
  22. {qka-1.7.1.dev2 → qka-1.8.1.dev2}/docs/api/core.md +0 -0
  23. {qka-1.7.1.dev2 → qka-1.8.1.dev2}/docs/api/utils.md +0 -0
  24. {qka-1.7.1.dev2 → qka-1.8.1.dev2}/docs/examples/buy_and_hold.md +0 -0
  25. {qka-1.7.1.dev2 → qka-1.8.1.dev2}/docs/examples/ma_cross.md +0 -0
  26. {qka-1.7.1.dev2 → qka-1.8.1.dev2}/docs/examples/momentum.md +0 -0
  27. {qka-1.7.1.dev2 → qka-1.8.1.dev2}/docs/examples/multi_factor.md +0 -0
  28. {qka-1.7.1.dev2 → qka-1.8.1.dev2}/docs/examples/rsi_atr.md +0 -0
  29. {qka-1.7.1.dev2 → qka-1.8.1.dev2}/docs/guides/backtest.md +0 -0
  30. {qka-1.7.1.dev2 → qka-1.8.1.dev2}/docs/guides/data.md +0 -0
  31. {qka-1.7.1.dev2 → qka-1.8.1.dev2}/docs/guides/indicators.md +0 -0
  32. {qka-1.7.1.dev2 → qka-1.8.1.dev2}/docs/guides/report.md +0 -0
  33. {qka-1.7.1.dev2 → qka-1.8.1.dev2}/docs/guides/sizing.md +0 -0
  34. {qka-1.7.1.dev2 → qka-1.8.1.dev2}/docs/guides/strategy.md +0 -0
  35. {qka-1.7.1.dev2 → qka-1.8.1.dev2}/docs/guides/trading.md +0 -0
  36. {qka-1.7.1.dev2 → qka-1.8.1.dev2}/docs/index.md +0 -0
  37. {qka-1.7.1.dev2 → qka-1.8.1.dev2}/docs/user-guide/trading.md +0 -0
  38. {qka-1.7.1.dev2 → qka-1.8.1.dev2}/mkdocs.yml +0 -0
  39. {qka-1.7.1.dev2 → qka-1.8.1.dev2}/qka/__init__.py +0 -0
  40. {qka-1.7.1.dev2 → qka-1.8.1.dev2}/qka/brokers/__init__.py +0 -0
  41. {qka-1.7.1.dev2 → qka-1.8.1.dev2}/qka/brokers/client.py +0 -0
  42. {qka-1.7.1.dev2 → qka-1.8.1.dev2}/qka/brokers/server.py +0 -0
  43. {qka-1.7.1.dev2 → qka-1.8.1.dev2}/qka/brokers/trade.py +0 -0
  44. {qka-1.7.1.dev2 → qka-1.8.1.dev2}/qka/cli.py +0 -0
  45. {qka-1.7.1.dev2 → qka-1.8.1.dev2}/qka/core/__init__.py +0 -0
  46. {qka-1.7.1.dev2 → qka-1.8.1.dev2}/qka/core/accessor.py +0 -0
  47. {qka-1.7.1.dev2 → qka-1.8.1.dev2}/qka/core/backtest.py +0 -0
  48. {qka-1.7.1.dev2 → qka-1.8.1.dev2}/qka/core/broker.py +0 -0
  49. {qka-1.7.1.dev2 → qka-1.8.1.dev2}/qka/core/report.py +0 -0
  50. {qka-1.7.1.dev2 → qka-1.8.1.dev2}/qka/core/sizing.py +0 -0
  51. {qka-1.7.1.dev2 → qka-1.8.1.dev2}/qka/core/strategy.py +0 -0
  52. {qka-1.7.1.dev2 → qka-1.8.1.dev2}/qka/mcp/__init__.py +0 -0
  53. {qka-1.7.1.dev2 → qka-1.8.1.dev2}/qka/mcp/api.py +0 -0
  54. {qka-1.7.1.dev2 → qka-1.8.1.dev2}/qka/mcp/server.py +0 -0
  55. {qka-1.7.1.dev2 → qka-1.8.1.dev2}/qka/server/__init__.py +0 -0
  56. {qka-1.7.1.dev2 → qka-1.8.1.dev2}/qka/server/handlers/__init__.py +0 -0
  57. {qka-1.7.1.dev2 → qka-1.8.1.dev2}/qka/server/handlers/class_inspector_handler.py +0 -0
  58. {qka-1.7.1.dev2 → qka-1.8.1.dev2}/qka/server/handlers/code_executor_handler.py +0 -0
  59. {qka-1.7.1.dev2 → qka-1.8.1.dev2}/qka/server/ws_client.py +0 -0
  60. {qka-1.7.1.dev2 → qka-1.8.1.dev2}/qka/server/zmq_server.py +0 -0
  61. {qka-1.7.1.dev2 → qka-1.8.1.dev2}/qka/utils/__init__.py +0 -0
  62. {qka-1.7.1.dev2 → qka-1.8.1.dev2}/qka/utils/anis.py +0 -0
  63. {qka-1.7.1.dev2 → qka-1.8.1.dev2}/qka/utils/logger.py +0 -0
  64. {qka-1.7.1.dev2 → qka-1.8.1.dev2}/qka/utils/util.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qka
3
- Version: 1.7.1.dev2
3
+ Version: 1.8.1.dev2
4
4
  Summary: QKA(快量化 / Quant Kit for A-shares)- 简洁易用的 A 股量化回测框架
5
5
  Project-URL: Home, https://github.com/zsrl/qka
6
6
  Project-URL: Documentation, https://zsrl.github.io/qka
@@ -65,6 +65,7 @@ raw-options = { local_scheme = "no-local-version" }
65
65
 
66
66
  [tool.hatch.build.targets.wheel]
67
67
  packages = ["qka"]
68
+ force-include = { "skills" = "qka/skills" }
68
69
 
69
70
  [tool.semantic_release]
70
71
  version_source = "tag"
@@ -507,8 +507,21 @@ class Data():
507
507
  pd.DataFrame: 股票数据,以 date 为索引,包含 open, high, low, close, volume, amount 列
508
508
  """
509
509
  # baostock 代码格式:sz.000001 / sh.600000
510
- exchange = symbol[-2:].lower() # 'sz', 'sh', 'bj'
511
- code = symbol.split('.')[0] # '000001'
510
+ # 支持两种输入格式:
511
+ # - 000001.SZ → code=000001, exchange=sz
512
+ # - sz.000001 → code=000001, exchange=sz
513
+ parts = symbol.split('.')
514
+ if len(parts) == 2:
515
+ left, right = parts
516
+ if left.isdigit():
517
+ # 格式: 000001.SZ
518
+ code, exchange = left, right.lower()
519
+ else:
520
+ # 格式: sz.000001
521
+ code, exchange = right, left.lower()
522
+ else:
523
+ # 兜底:无后缀,直接当作代码
524
+ code, exchange = symbol, 'sh'
512
525
  bs_code = f"{exchange}.{code}"
513
526
 
514
527
  # adjustflag: 1=不复权, 2=前复权, 3=后复权
@@ -0,0 +1,578 @@
1
+ ---
2
+ name: qka
3
+ description: >
4
+ QKA 框架使用技能。当用户需要基于 QKA 框架编写 A 股量化策略、运行回测、
5
+ 处理股票数据、生成报告时使用。涵盖 QKA 全部核心 API 的使用方法。
6
+ ---
7
+
8
+ # QKA 框架技能
9
+
10
+ ## 概述
11
+
12
+ QKA 是一个 A 股量化交易回测框架。核心流程:
13
+
14
+ ```
15
+ Data(symbols) → Strategy(策略类) → Backtest(回测) → Report(报告)
16
+
17
+ indicators(预计算指标)
18
+ ```
19
+
20
+ **类名约束:** 自定义策略类必须命名为 `MyStrategy`,继承 `Strategy`
21
+
22
+ ## 能力边界
23
+
24
+ **能做的策略类型:**
25
+ - 趋势跟踪(双均线、海龟突破、MACD)
26
+ - 均值回归(RSI、Bollinger Bands)
27
+ - 多因子选股(PE/ROE/动量/波动率打分选股,周期 rebalance)
28
+ - 等权/市值加权组合
29
+ - 定投(固定间隔买入固定金额)
30
+ - 大盘 MA 择时、股债轮动
31
+
32
+ **做不了的:**
33
+ - 分钟级/高频(无分钟数据)
34
+ - 期权、期货
35
+ - 机器学习选股(无特征工程管道)
36
+ - 实盘交易
37
+ - 事件驱动(无财报/公告订阅)
38
+ - 多周期策略(仅单周期)
39
+
40
+ ## A 股交易规则
41
+
42
+ 1. 买入股数必须是 100 的整数倍(一手)
43
+ 2. 价格必须 > 0 且不是 NaN
44
+ 3. 资金不足时不买入
45
+ 4. `sizing.percent()` 和 `sizing.fixed()` 已自动按手取整
46
+
47
+ ---
48
+
49
+ <!-- AUTO: API 签名 -->
50
+
51
+ ### Data
52
+
53
+ ### `Data(**symbols** \`Optional[List[str]]\` = None, **period** \`str\` = '1d', **adjust** \`str\` = 'qfq', **source** \`str\` = 'baostock', **pool_size** \`int\` = 10, **datadir** \`Optional[Path]\` = None, **indicators** \`Optional[dict]\` = None)\`
54
+
55
+ 初始化数据对象
56
+
57
+ ### `Data.get(**lazy** \`bool\` = False) → \`lazy=False: pd.DataFrame,列名格式 {symbol}|{factor}\`\`
58
+
59
+ 获取历史数据。 并发下载所有股票数据,应用因子计算,并返回合并后的数据。
60
+
61
+ <!-- /AUTO -->
62
+
63
+ # Data 模块
64
+
65
+ 数据获取、缓存和指标预计算。
66
+
67
+ ## Data 构造函数
68
+
69
+ ```python
70
+ from qka import Data
71
+
72
+ data = Data(
73
+ symbols=['000001.SZ', '600000.SH'], # 股票代码
74
+ period='1d', # 周期:'1d'
75
+ adjust='qfq', # 复权:'qfq'/'hfq'/'bfq'
76
+ source='baostock', # 数据源:'baostock'/'akshare'/'qmt'
77
+ pool_size=10, # 并发线程数(仅 akshare)
78
+ datadir=None, # 缓存目录,默认 ./datadir
79
+ indicators=None, # 预计算指标
80
+ )
81
+ ```
82
+
83
+ - `symbols`: A 股代码 `000001.SZ`(深市)或 `600000.SH`(沪市)
84
+ - `period`: 目前仅支持 `'1d'`(日线)
85
+ - `adjust`: `'qfq'`(前复权,默认)、`'hfq'`(后复权)、`'bfq'`(不复权)
86
+ - `source`: `'baostock'`(默认,串行下载)、`'akshare'`(HTTP 并发)、`'qmt'`(QMT)
87
+ - `datadir`: 默认当前目录的 `datadir/`,缓存 parquet 文件
88
+
89
+ ## indicators 参数
90
+
91
+ 两种格式:
92
+
93
+ ### 格式一:字典(推荐)
94
+
95
+ ```python
96
+ data = Data(symbols=['000001.SZ'], indicators={
97
+ 'sma_5': ('sma', 5), # (指标名, 参数...)
98
+ 'sma_20': ('sma', 20), # 默认用 close 计算
99
+ 'rsi_14': ('rsi', 14),
100
+ 'macd': ('macd', 12, 26, 9),
101
+ 'bbands': ('bbands', 20, 2),
102
+ 'atr_14': ('atr', 14),
103
+ 'ma5_custom': lambda df: df['close'].rolling(5).mean(), # 自定义因子
104
+ 'sma_on_high': ('sma', 'high', 20), # 指定用 high 列计算
105
+ })
106
+ ```
107
+
108
+ 支持的 TA 指标名:`sma`, `ema`, `wma`, `rsi`, `macd`, `bbands`, `atr`
109
+
110
+ - `macd` 产生 3 列:`macd`, `macd_signal`, `macd_diff`
111
+ - `bbands` 产生 3 列:`bbands_upper`, `bbands_middle`, `bbands_lower`
112
+
113
+ ### 格式二:函数
114
+
115
+ ```python
116
+ data = Data(symbols=['000001.SZ'], indicators=lambda df:
117
+ df.assign(
118
+ ma5=df['close'].rolling(5).mean(),
119
+ ma20=df['close'].rolling(20).mean()
120
+ )
121
+ )
122
+ ```
123
+
124
+ 函数接收单只股票的 DataFrame,返回添加了额外列的 DataFrame。
125
+
126
+ ## 获取数据
127
+
128
+ ```python
129
+ data = Data(symbols=['000001.SZ', '600000.SH'], indicators={'sma_5': ('sma', 5)})
130
+
131
+ # 下载并返回全部数据(触发下载)
132
+ df = data.get()
133
+
134
+ # 懒加载模式(大数据用 dask,分块迭代)
135
+ ddf = data.get(lazy=True)
136
+ ```
137
+
138
+ - `get()` 返回 `pd.DataFrame`,列名格式 `{symbol}|{factor}`
139
+ - 示例列名:`000001.SZ|close`, `000001.SZ|sma_5`, `600000.SH|volume`
140
+ - `get(lazy=True)` 返回 `dask.DataFrame`,计算前操作延迟执行
141
+
142
+ ## 常见错误
143
+
144
+ ```python
145
+ # ✅ 正确:传 indicators 到 Data 构造函数
146
+ Data(symbols=['000001.SZ'], indicators={'sma_5': ('sma', 5)})
147
+
148
+ # ❌ 错误:Data() 后不能动态加指标
149
+ data = Data(['000001.SZ'])
150
+ data.indicators = {...} # 没用
151
+ ```
152
+
153
+ ---
154
+
155
+ <!-- AUTO: API 签名 -->
156
+
157
+ ### Strategy
158
+
159
+ ### `Strategy(**cash** \`float\` = 100000.0)\`
160
+
161
+ 初始化策略
162
+
163
+ ### `Strategy.get(**factor** \`str\`) → \`pd.Series,index=股票代码,values=最新值\`\`
164
+
165
+ 获取当前 bar 的横截面数据。 替代旧的 on_bar(date, get) 中的 get 参数。 仅当 on_bar 通过 self._data 注入数据后才能使用。
166
+
167
+ ### `Strategy.history(**factor** \`str\`, **window** \`int\` = 20) → \`pd.DataFrame,行=日期,列=股票代码\`\`
168
+
169
+ 获取因子的历史窗口数据。
170
+
171
+ ### `Strategy.on_bar(**date**)\`
172
+
173
+ 每个 bar 的处理逻辑,必须由子类实现。 使用 self.get(factor) / self.history(factor, window) 获取数据。 --- 用法 --- class MyStrategy(Strategy): def on_bar(self, date): # 横截面数据(当前 bar 所有股票) close = self.get('close') # 历史序列(过去 N...
174
+
175
+ <!-- /AUTO -->
176
+
177
+ # Strategy 模块
178
+
179
+ 策略编写核心。
180
+
181
+ ## 策略类结构
182
+
183
+ ```python
184
+ from qka import Strategy
185
+
186
+ class MyStrategy(Strategy):
187
+ def __init__(self, **kwargs):
188
+ super().__init__(**kwargs)
189
+ # 初始化自定义状态(可选)
190
+ # 不写 __init__ 也行,用父类默认值
191
+
192
+ def on_bar(self, date):
193
+ """每个交易日回调一次"""
194
+ # date: pd.Timestamp
195
+ # self.get() / self.history() 获取数据
196
+ # self.broker 交易
197
+ # self.sizing 计算仓位
198
+ pass
199
+ ```
200
+
201
+ **规则:**
202
+ - 类名必须是 `MyStrategy`
203
+ - `__init__` 必须用 `**kwargs` 透传,不能固定参数
204
+ - ❌ 禁止写 `on_bar(self, date, get)`——没有 `get` 参数
205
+
206
+ ## self.get(factor) -> pd.Series
207
+
208
+ 当前 bar 所有股票的横截面数据。
209
+
210
+ ```python
211
+ close = self.get('close') # 所有股票的收盘价
212
+ volume = self.get('volume') # 成交量
213
+ high = self.get('high') # 最高价
214
+
215
+ for sym in close.index: # 遍历股票
216
+ price = float(close[sym])
217
+ if price > 0:
218
+ ...
219
+ ```
220
+
221
+ - `factor`: 因子名,如 `'close'`, `'open'`, `'high'`, `'low'`, `'volume'`
222
+ - 也可以取预计算指标:`self.get('sma_5')`, `self.get('rsi_14')`
223
+ - 返回 `pd.Series`, index=股票代码
224
+ - 如果某股票当前值缺失,会从 Series 中排除
225
+
226
+ ```python
227
+ # ✅ 正确用法
228
+ close = self.get('close')
229
+ for sym in close.index:
230
+ ...
231
+
232
+ # ❌ 错误用法
233
+ self.get('close', count=20) # 没有 count 参数
234
+ get('close') # get 不是全局函数
235
+ ```
236
+
237
+ ## self.history(factor, window) -> pd.DataFrame
238
+
239
+ 因子的历史窗口数据。
240
+
241
+ ```python
242
+ hist = self.history('close', 20) # 过去 20 天收盘价
243
+ ma5 = hist.iloc[-5:].mean() # 最近 5 天均值,Series(index=股票代码)
244
+ today_close = hist.iloc[-1] # 今天收盘价
245
+ yesterday_close = hist.iloc[-2] # 昨天收盘价
246
+ series = hist[sym].dropna() # 某只股票的历史序列
247
+ ```
248
+
249
+ - 返回 `pd.DataFrame`, 行=日期(倒序), 列=股票代码
250
+ - 如果 `history` 的数据不够 `window` 天,前几行会有 NaN
251
+ - **用 `.dropna()` 清理后再计算**
252
+ - `hist.iloc[-N:]` 取最近 N 天
253
+
254
+ ## self.broker
255
+
256
+ 交易接口。详见下方 Broker 模块。
257
+
258
+ ```python
259
+ self.broker.buy('000001.SZ', price, 100) # 买入 100 股
260
+ self.broker.sell('000001.SZ', price, 100) # 卖出 100 股
261
+ ```
262
+
263
+ ## self.sizing
264
+
265
+ 仓位计算。详见下方 Sizing 模块。
266
+
267
+ ```python
268
+ # 10% 资金买入,自动按手取整
269
+ size = self.sizing.percent(0.1, price)
270
+ self.broker.buy(sym, price, size)
271
+ ```
272
+
273
+ ---
274
+
275
+ <!-- AUTO: API 签名 -->
276
+
277
+ ### Broker
278
+
279
+ ### `Broker(**initial_cash** = 100000.0, **commission_rate** = DEFAULT_COMMISSION_RATE, **stamp_duty_rate** = DEFAULT_STAMP_DUTY_RATE, **slippage** = DEFAULT_SLIPPAGE)\`
280
+
281
+ 初始化Broker
282
+
283
+ ### `Broker.on_bar(**date**, **get**)\`
284
+
285
+ Bar结束时记录当前状态。
286
+
287
+ ### `Broker.buy(**symbol** \`str\`, **price** \`float\`, **size** \`int\`) → \`bool\`\`
288
+
289
+ 买入操作 考虑滑点(买入价上移)和佣金(最低 5 元)。
290
+
291
+ ### `Broker.sell(**symbol** \`str\`, **price** \`float\`, **size** \`int\`) → \`bool\`\`
292
+
293
+ 卖出操作 考虑滑点(卖出价下移)、佣金(最低 5 元)和印花税。
294
+
295
+ ### `Broker.get(**factor** \`str\`, **timestamp** = None) → \`Any\`\`
296
+
297
+ 从trades DataFrame中获取数据
298
+
299
+ <!-- /AUTO -->
300
+
301
+ # Broker 模块
302
+
303
+ 虚拟交易经纪商,管理资金、持仓和费用。
304
+
305
+ ## 初始化
306
+
307
+ Broker 由 Strategy 自动创建,用户在策略中通过 `self.broker` 访问。
308
+
309
+ ```python
310
+ # Strategy 内部自动创建
311
+ strategy = MyStrategy(cash=100000) # 初始资金 10 万
312
+ ```
313
+
314
+ ## 交易接口
315
+
316
+ ```python
317
+ # 买入
318
+ self.broker.buy(symbol, price, size)
319
+ # symbol: 股票代码,如 '000001.SZ'
320
+ # price: 成交价(float)
321
+ # size: 股数(int,必须 100 的倍数)
322
+
323
+ # 卖出
324
+ self.broker.sell(symbol, price, size)
325
+ ```
326
+
327
+ ## 状态属性
328
+
329
+ ```python
330
+ self.broker.cash # 可用资金(float)
331
+ self.broker.positions # 持仓 dict
332
+
333
+ # 持仓格式:
334
+ # {symbol: {'size': int, 'avg_price': float, 'cost': float}}
335
+
336
+ symbol in self.broker.positions # 判断是否持仓
337
+ ```
338
+
339
+ ## 费用设置
340
+
341
+ ```python
342
+ from qka import Broker
343
+
344
+ broker = Broker(
345
+ initial_cash=100000,
346
+ commission_rate=0.00025, # 万2.5 佣金(默认)
347
+ stamp_duty_rate=0.0005, # 万5 印花税,仅卖出(默认)
348
+ slippage=0.001, # 0.1% 滑点(默认)
349
+ )
350
+ ```
351
+
352
+ - 最低佣金 5 元
353
+ - 印花税仅卖出时收取
354
+
355
+ ## 回测结果数据
356
+
357
+ 回测执行后,以下属性保存完整记录:
358
+
359
+ ### self.broker.trades — 逐日净值记录(pd.DataFrame)
360
+
361
+ ```python
362
+ equity = self.broker.trades['total'] # 净值序列(Series, index=日期)
363
+ cash = self.broker.trades['cash'] # 现金
364
+ value = self.broker.trades['value'] # 持仓市值
365
+ ```
366
+
367
+ 用于构造净值曲线:
368
+
369
+ ```python
370
+ import pandas as pd
371
+ eq = pd.Series(self.broker.trades['total'].values,
372
+ index=self.broker.trades.index)
373
+ ```
374
+
375
+ ### self.broker.trade_history — 逐笔交易记录(list[dict])
376
+
377
+ 每笔 dict 的字段:
378
+
379
+ | 字段 | 类型 | 说明 |
380
+ |---|---|---|
381
+ | `action` | str | `'buy'` 或 `'sell'` |
382
+ | `symbol` | str | 股票代码 |
383
+ | `price` | float | 市价 |
384
+ | `exec_price` | float | 滑点后成交价 |
385
+ | `size` | int | 股数 |
386
+ | `amount` | float | 成交金额 |
387
+ | `commission` | float | 佣金 |
388
+ | `timestamp` | 时间戳 | 交易日期 |
389
+
390
+ 卖出额外含 `net_proceeds`(扣除费用后净收入)。
391
+
392
+ ## 正确/错误用法
393
+
394
+ ```python
395
+ # ✅ 正确
396
+ if '000001.SZ' in self.broker.positions:
397
+ size = self.broker.positions['000001.SZ']['size']
398
+ self.broker.sell('000001.SZ', price, size)
399
+
400
+ # ❌ 错误:直接修改内部状态
401
+ self.broker.cash -= 1000
402
+ self.broker.positions['000001.SZ'] = {'size': 100, ...}
403
+ ```
404
+
405
+ ---
406
+
407
+ <!-- AUTO: API 签名 -->
408
+
409
+ ### SizingAccessor
410
+
411
+ ### `SizingAccessor(**broker**)\`
412
+ ### `SizingAccessor.fixed_shares(**n** \`int\`) → \`int\`\`
413
+
414
+ 固定股数。 如果 n 不足一手(100股),返回 0。
415
+
416
+ ### `SizingAccessor.fixed_amount(**amount** \`float\`, **price** \`float\`) → \`int\`\`
417
+
418
+ 固定金额。 计算 amount 能买多少股,向下按手取整。
419
+
420
+ ### `SizingAccessor.percent(**ratio** \`float\`, **price** \`float\`) → \`int\`\`
421
+
422
+ 资金百分比。 使用可用现金的 ratio 比例买入,按手取整。
423
+
424
+ ### `SizingAccessor.atr_risk(**risk_ratio** \`float\`, **price** \`float\`, **atr_value** \`float\`, **multiplier** \`float\` = 2.0) → \`int\`\`
425
+
426
+ ATR 风险仓位。 基于 ATR(平均真实波幅)计算仓位,确保单笔亏损不超过 risk_ratio 比例。 公式:股数 = (cash * risk_ratio) / (atr_value * multiplier)
427
+
428
+ ### `SizingAccessor.kelly(**win_rate** \`float\`, **win_loss_ratio** \`float\`, **price** \`float\`) → \`int\`\`
429
+
430
+ 凯利公式。 f* = (p * b - q) / b 其中: - p = 胜率 - b = 赔率(盈利/亏损) - q = 1 - p(败率) 当 f* ≤ 0 时返回 0(不建议下注)。
431
+
432
+ <!-- /AUTO -->
433
+
434
+ # Sizing 模块
435
+
436
+ 仓位计算工具。在策略中通过 `self.sizing` 访问。
437
+
438
+ ## 方法
439
+
440
+ ### self.sizing.percent(ratio, price) -> int
441
+
442
+ 按可用资金的百分比计算买入股数,自动向下取整到 100 的倍数。
443
+
444
+ ```python
445
+ # 用 10%(10000 元)资金买入
446
+ price = float(self.get('close')['000001.SZ'])
447
+ size = self.sizing.percent(0.1, price)
448
+ self.broker.buy('000001.SZ', price, size)
449
+ ```
450
+
451
+ - `ratio`: 资金比例,如 `0.1` = 10%, `0.5` = 50%
452
+ - `price`: 当前价格
453
+ - 返回:股数(int),已按手取整
454
+ - 如果计算出的股数 < 100,返回 0
455
+
456
+ ### self.sizing.fixed_shares(n) -> int
457
+
458
+ 买入固定股数。
459
+
460
+ ```python
461
+ size = self.sizing.fixed_shares(1000) # 买入 1000 股
462
+ self.broker.buy('000001.SZ', price, size)
463
+ ```
464
+
465
+ ### self.sizing.fixed_amount(amount, price) -> int
466
+
467
+ 买入固定金额。
468
+
469
+ ```python
470
+ size = self.sizing.fixed_amount(5000, price) # 投入 5000 元
471
+ self.broker.buy('000001.SZ', price, size)
472
+ ```
473
+
474
+ ## 注意事项
475
+
476
+ ```python
477
+ # ✅ 正确:先算仓位再买入
478
+ price = float(self.get('close')['000001.SZ'])
479
+ size = self.sizing.percent(0.1, price)
480
+ if size >= 100:
481
+ self.broker.buy(sym, price, size)
482
+
483
+ # ❌ 错误:不校验 size 直接买
484
+ self.broker.buy(sym, price, self.sizing.percent(0.1, price))
485
+ # 如果 percent 返回 0,buy 会报错
486
+ ```
487
+
488
+ ---
489
+
490
+ <!-- AUTO: API 签名 -->
491
+
492
+ ### Backtest
493
+
494
+ ### `Backtest(**data**, **strategy**)\`
495
+
496
+ 初始化回测引擎
497
+
498
+ ### `Backtest.run(**benchmark** \`Optional[str]\` = None) → \`None。回测结果保存在 self.results 中,可通过\`\`
499
+
500
+ 执行回测 遍历所有时间点,在每个时间点调用策略的on_bar方法进行交易决策, 并记录交易后的状态。 大规模回测(>500 bar)时自动使用分区迭代,分块加载数据到内存, 避免一次性加载全量数据。
501
+
502
+ ### `Backtest.summary() → \`dict\`\`
503
+
504
+ 计算并打印回测绩效指标 返回包含以下指标的字典: - 总收益率、年化收益率、年化波动率 - 夏普比率、最大回撤、Calmar比率 - 胜率、盈亏比、交易次数 - 最终资产、总手续费
505
+
506
+ <!-- /AUTO -->
507
+
508
+ # Backtest 模块
509
+
510
+ 回测引擎,管理策略生命周期和数据加载流程。
511
+
512
+ ## 用法
513
+
514
+ ```python
515
+ from qka import Data, Backtest
516
+
517
+ data = Data(symbols=['000001.SZ'], indicators={'sma_5': ('sma', 5)})
518
+ bt = Backtest(data, MyStrategy(cash=100000))
519
+ bt.run(benchmark='000300.SH') # 沪深300 为基准
520
+ ```
521
+
522
+ - `data`: Data 实例(已配置 symbol 和 indicators)
523
+ - `MyStrategy(cash=...)`: 策略实例,cash 为初始资金
524
+ - `benchmark`: 基准指数代码,如 `'000300.SH'`(沪深300)、`'000001.SH'`(上证)
525
+
526
+ ## Backtest 参数
527
+
528
+ ```python
529
+ bt = Backtest(
530
+ data, # Data 实例
531
+ strategy, # Strategy 实例
532
+ start_date='2023-01-01', # 可选,数据过滤起始
533
+ end_date='2024-12-31', # 可选,数据过滤结束
534
+ )
535
+ ```
536
+
537
+ ## 回测结果
538
+
539
+ ### summary() -> dict
540
+
541
+ ```python
542
+ metrics = bt.summary()
543
+ ```
544
+
545
+ 返回的字典包含:
546
+ - `总收益率` — 策略总收益百分比
547
+ - `年化收益率` — 年化收益率
548
+ - `最大回撤` — 最大回撤百分比(负值)
549
+ - `夏普比率` — 年化夏普比率
550
+ - `胜率` — 盈利交易占比
551
+ - `交易次数` — 总交易笔数
552
+ - `benchmark_收益` — 基准总收益百分比
553
+ - `benchmark_年化` — 基准年化收益率
554
+
555
+ ## 完整回测流程
556
+
557
+ ```python
558
+ from qka import Data, Strategy, Backtest
559
+
560
+ class MyStrategy(Strategy):
561
+ def on_bar(self, date):
562
+ close = self.get('close')
563
+ for sym in close.index:
564
+ price = float(close[sym])
565
+ if price <= 0:
566
+ continue
567
+ if sym not in self.broker.positions:
568
+ size = self.sizing.percent(0.1, price)
569
+ if size >= 100:
570
+ self.broker.buy(sym, price, size)
571
+
572
+ data = Data(symbols=['000001.SZ', '600000.SH'])
573
+ bt = Backtest(data, MyStrategy(cash=100000))
574
+ bt.run(benchmark='000300.SH')
575
+ print(bt.summary())
576
+ ```
577
+
578
+ ---