qka 1.6.1.dev4__tar.gz → 1.6.2.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 (55) hide show
  1. {qka-1.6.1.dev4 → qka-1.6.2.dev2}/.gitignore +3 -3
  2. {qka-1.6.1.dev4 → qka-1.6.2.dev2}/PKG-INFO +1 -1
  3. qka-1.6.2.dev2/docs/examples/buy_and_hold.md +79 -0
  4. qka-1.6.2.dev2/docs/examples/ma_cross.md +134 -0
  5. qka-1.6.2.dev2/docs/examples/momentum.md +87 -0
  6. qka-1.6.2.dev2/docs/examples/multi_factor.md +106 -0
  7. qka-1.6.2.dev2/docs/examples/rsi_atr.md +60 -0
  8. {qka-1.6.1.dev4 → qka-1.6.2.dev2}/.github/workflows/docs.yml +0 -0
  9. {qka-1.6.1.dev4 → qka-1.6.2.dev2}/.github/workflows/release.yml +0 -0
  10. {qka-1.6.1.dev4 → qka-1.6.2.dev2}/.vscode/settings.json +0 -0
  11. {qka-1.6.1.dev4 → qka-1.6.2.dev2}/CHANGELOG.md +0 -0
  12. {qka-1.6.1.dev4 → qka-1.6.2.dev2}/LICENSE +0 -0
  13. {qka-1.6.1.dev4 → qka-1.6.2.dev2}/README.md +0 -0
  14. {qka-1.6.1.dev4 → qka-1.6.2.dev2}/docs/advanced/performance.md +0 -0
  15. {qka-1.6.1.dev4 → qka-1.6.2.dev2}/docs/api/brokers.md +0 -0
  16. {qka-1.6.1.dev4 → qka-1.6.2.dev2}/docs/api/core.md +0 -0
  17. {qka-1.6.1.dev4 → qka-1.6.2.dev2}/docs/api/utils.md +0 -0
  18. {qka-1.6.1.dev4 → qka-1.6.2.dev2}/docs/concepts/backtest.md +0 -0
  19. {qka-1.6.1.dev4 → qka-1.6.2.dev2}/docs/concepts/broker.md +0 -0
  20. {qka-1.6.1.dev4 → qka-1.6.2.dev2}/docs/concepts/data.md +0 -0
  21. {qka-1.6.1.dev4 → qka-1.6.2.dev2}/docs/concepts/indicators.md +0 -0
  22. {qka-1.6.1.dev4 → qka-1.6.2.dev2}/docs/concepts/sizing.md +0 -0
  23. {qka-1.6.1.dev4 → qka-1.6.2.dev2}/docs/concepts/strategy.md +0 -0
  24. {qka-1.6.1.dev4 → qka-1.6.2.dev2}/docs/getting-started/quickstart.md +0 -0
  25. {qka-1.6.1.dev4 → qka-1.6.2.dev2}/docs/index.md +0 -0
  26. {qka-1.6.1.dev4 → qka-1.6.2.dev2}/docs/user-guide/trading.md +0 -0
  27. {qka-1.6.1.dev4 → qka-1.6.2.dev2}/mkdocs.yml +0 -0
  28. {qka-1.6.1.dev4 → qka-1.6.2.dev2}/pyproject.toml +0 -0
  29. {qka-1.6.1.dev4 → qka-1.6.2.dev2}/qka/__init__.py +0 -0
  30. {qka-1.6.1.dev4 → qka-1.6.2.dev2}/qka/brokers/__init__.py +0 -0
  31. {qka-1.6.1.dev4 → qka-1.6.2.dev2}/qka/brokers/client.py +0 -0
  32. {qka-1.6.1.dev4 → qka-1.6.2.dev2}/qka/brokers/server.py +0 -0
  33. {qka-1.6.1.dev4 → qka-1.6.2.dev2}/qka/brokers/trade.py +0 -0
  34. {qka-1.6.1.dev4 → qka-1.6.2.dev2}/qka/cli.py +0 -0
  35. {qka-1.6.1.dev4 → qka-1.6.2.dev2}/qka/core/__init__.py +0 -0
  36. {qka-1.6.1.dev4 → qka-1.6.2.dev2}/qka/core/accessor.py +0 -0
  37. {qka-1.6.1.dev4 → qka-1.6.2.dev2}/qka/core/backtest.py +0 -0
  38. {qka-1.6.1.dev4 → qka-1.6.2.dev2}/qka/core/broker.py +0 -0
  39. {qka-1.6.1.dev4 → qka-1.6.2.dev2}/qka/core/data.py +0 -0
  40. {qka-1.6.1.dev4 → qka-1.6.2.dev2}/qka/core/report.py +0 -0
  41. {qka-1.6.1.dev4 → qka-1.6.2.dev2}/qka/core/sizing.py +0 -0
  42. {qka-1.6.1.dev4 → qka-1.6.2.dev2}/qka/core/strategy.py +0 -0
  43. {qka-1.6.1.dev4 → qka-1.6.2.dev2}/qka/mcp/__init__.py +0 -0
  44. {qka-1.6.1.dev4 → qka-1.6.2.dev2}/qka/mcp/api.py +0 -0
  45. {qka-1.6.1.dev4 → qka-1.6.2.dev2}/qka/mcp/server.py +0 -0
  46. {qka-1.6.1.dev4 → qka-1.6.2.dev2}/qka/server/__init__.py +0 -0
  47. {qka-1.6.1.dev4 → qka-1.6.2.dev2}/qka/server/handlers/__init__.py +0 -0
  48. {qka-1.6.1.dev4 → qka-1.6.2.dev2}/qka/server/handlers/class_inspector_handler.py +0 -0
  49. {qka-1.6.1.dev4 → qka-1.6.2.dev2}/qka/server/handlers/code_executor_handler.py +0 -0
  50. {qka-1.6.1.dev4 → qka-1.6.2.dev2}/qka/server/ws_client.py +0 -0
  51. {qka-1.6.1.dev4 → qka-1.6.2.dev2}/qka/server/zmq_server.py +0 -0
  52. {qka-1.6.1.dev4 → qka-1.6.2.dev2}/qka/utils/__init__.py +0 -0
  53. {qka-1.6.1.dev4 → qka-1.6.2.dev2}/qka/utils/anis.py +0 -0
  54. {qka-1.6.1.dev4 → qka-1.6.2.dev2}/qka/utils/logger.py +0 -0
  55. {qka-1.6.1.dev4 → qka-1.6.2.dev2}/qka/utils/util.py +0 -0
@@ -177,9 +177,9 @@ _debug_*.py
177
177
  _temp_*.txt
178
178
  _momentum_output.txt
179
179
 
180
- # 示例和测试(不入库)
181
- examples/
182
- tests/
180
+ # 示例和测试(不入库,仓库根目录下的)
181
+ /examples/
182
+ /tests/
183
183
 
184
184
  # 回测报告
185
185
  reports/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qka
3
- Version: 1.6.1.dev4
3
+ Version: 1.6.2.dev2
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
@@ -0,0 +1,79 @@
1
+ # 买入持有与定投
2
+
3
+ 最简单的策略:买入,然后不动。
4
+
5
+ ---
6
+
7
+ ## 买入持有
8
+
9
+ ```python
10
+ """买入平安银行,一直持有到回测结束"""
11
+ from qka import Data, Strategy, Broker, Backtest
12
+
13
+
14
+ class BuyAndHold(Strategy):
15
+ def __init__(self):
16
+ super().__init__()
17
+ self.broker = Broker(initial_cash=100_000)
18
+ self.bought = False
19
+
20
+ def on_bar(self, date):
21
+ close = self.get('close')
22
+ if close is None or close.empty:
23
+ return
24
+ if not self.bought and '000001.SZ' in close.index:
25
+ price = float(close['000001.SZ'])
26
+ if price > 0:
27
+ size = self.sizing.percent(0.5, price)
28
+ if size > 0:
29
+ self.broker.buy('000001.SZ', price, size)
30
+ self.bought = True
31
+
32
+
33
+ if __name__ == '__main__':
34
+ data = Data(symbols=['000001.SZ'])
35
+ bt = Backtest(data, BuyAndHold())
36
+ bt.run(benchmark='000300.SH')
37
+ bt.summary()
38
+ bt.report(title='买入持有 - 平安银行')
39
+ ```
40
+
41
+ ---
42
+
43
+ ## 定投
44
+
45
+ 每月固定时间买入固定金额:
46
+
47
+ ```python
48
+ """每月 1 号买入 5000 块"""
49
+ from qka import Data, Strategy, Broker, Backtest
50
+
51
+
52
+ class MonthlyDCA(Strategy):
53
+ def __init__(self):
54
+ super().__init__()
55
+ self.broker = Broker(initial_cash=100_000)
56
+
57
+ def on_bar(self, date):
58
+ # 每月 1 号买入
59
+ if date.day != 1:
60
+ return
61
+
62
+ close = self.get('close')
63
+ if close is None or close.empty:
64
+ return
65
+ if '000001.SZ' in close.index:
66
+ price = float(close['000001.SZ'])
67
+ if price > 0:
68
+ size = self.sizing.fixed_amount(5000, price)
69
+ if size > 0:
70
+ self.broker.buy('000001.SZ', price, size)
71
+
72
+
73
+ if __name__ == '__main__':
74
+ data = Data(symbols=['000001.SZ'])
75
+ bt = Backtest(data, MonthlyDCA())
76
+ bt.run(benchmark='000300.SH')
77
+ bt.summary()
78
+ bt.report(title='定投 - 每月 1 号 5000 元')
79
+ ```
@@ -0,0 +1,134 @@
1
+ # 均线交叉
2
+
3
+ 5 日均线上穿 20 日均线买入,下穿卖出。经典的趋势跟踪策略。
4
+
5
+ ---
6
+
7
+ ## 单股票
8
+
9
+ ```python
10
+ """5 日线上穿 20 日线买入,下穿卖出"""
11
+ from qka import Data, Strategy, Broker, Backtest
12
+
13
+
14
+ class MaCross(Strategy):
15
+ def __init__(self):
16
+ super().__init__()
17
+ self.broker = Broker(initial_cash=100_000)
18
+
19
+ def on_bar(self, date):
20
+ close = self.get('close')
21
+ if close is None or close.empty:
22
+ return
23
+ if '000001.SZ' not in close.index:
24
+ return
25
+
26
+ # 取历史均线
27
+ ma5 = self.history('sma_5', 3)
28
+ ma20 = self.history('sma_20', 3)
29
+
30
+ if len(ma5) < 2 or len(ma20) < 2:
31
+ return
32
+
33
+ price = float(close['000001.SZ'])
34
+ if price <= 0:
35
+ return
36
+
37
+ # 今日 5 日线 > 20 日线,且昨天 5 日线 <= 20 日线(上穿)
38
+ if (ma5['000001.SZ'].iloc[-1] > ma20['000001.SZ'].iloc[-1] and
39
+ ma5['000001.SZ'].iloc[-2] <= ma20['000001.SZ'].iloc[-2]):
40
+
41
+ if '000001.SZ' not in self.broker.positions:
42
+ size = self.sizing.percent(0.5, price)
43
+ if size > 0:
44
+ self.broker.buy('000001.SZ', price, size)
45
+
46
+ # 下穿卖出
47
+ elif (ma5['000001.SZ'].iloc[-1] < ma20['000001.SZ'].iloc[-1] and
48
+ ma5['000001.SZ'].iloc[-2] >= ma20['000001.SZ'].iloc[-2]):
49
+
50
+ if '000001.SZ' in self.broker.positions:
51
+ pos = self.broker.positions['000001.SZ']
52
+ self.broker.sell('000001.SZ', price, pos['size'])
53
+
54
+
55
+ if __name__ == '__main__':
56
+ data = Data(
57
+ symbols=['000001.SZ'],
58
+ indicators={
59
+ 'sma_5': ('sma', 5),
60
+ 'sma_20': ('sma', 20),
61
+ },
62
+ )
63
+ bt = Backtest(data, MaCross())
64
+ bt.run(benchmark='000300.SH')
65
+ bt.summary()
66
+ bt.report(title='均线交叉 - 平安银行')
67
+ ```
68
+
69
+ ---
70
+
71
+ ## 多股票
72
+
73
+ 每只股票独立判断,谁出信号买谁:
74
+
75
+ ```python
76
+ """多股票均线交叉,每只股票判断自己的信号"""
77
+ from qka import Data, Strategy, Broker, Backtest
78
+
79
+
80
+ class MultiMaCross(Strategy):
81
+ def __init__(self):
82
+ super().__init__()
83
+ self.broker = Broker(initial_cash=1_000_000)
84
+
85
+ def on_bar(self, date):
86
+ close = self.get('close')
87
+ if close is None or close.empty:
88
+ return
89
+
90
+ # 取历史均线(多股票的 DataFrame)
91
+ ma5 = self.history('sma_5', 3)
92
+ ma20 = self.history('sma_20', 3)
93
+
94
+ if len(ma5) < 2 or len(ma20) < 2:
95
+ return
96
+
97
+ for sym in close.index:
98
+ price = float(close[sym])
99
+ if price <= 0:
100
+ continue
101
+ if sym not in ma5.columns or sym not in ma20.columns:
102
+ continue
103
+
104
+ # 上穿买入
105
+ if (ma5[sym].iloc[-1] > ma20[sym].iloc[-1] and
106
+ ma5[sym].iloc[-2] <= ma20[sym].iloc[-2]):
107
+
108
+ if sym not in self.broker.positions:
109
+ size = self.sizing.percent(0.1, price)
110
+ if size > 0:
111
+ self.broker.buy(sym, price, size)
112
+
113
+ # 下穿卖出
114
+ elif (ma5[sym].iloc[-1] < ma20[sym].iloc[-1] and
115
+ ma5[sym].iloc[-2] >= ma20[sym].iloc[-2]):
116
+
117
+ if sym in self.broker.positions:
118
+ pos = self.broker.positions[sym]
119
+ self.broker.sell(sym, price, pos['size'])
120
+
121
+
122
+ if __name__ == '__main__':
123
+ data = Data(
124
+ symbols=['000001.SZ', '600000.SH', '000002.SZ'],
125
+ indicators={
126
+ 'sma_5': ('sma', 5),
127
+ 'sma_20': ('sma', 20),
128
+ },
129
+ )
130
+ bt = Backtest(data, MultiMaCross())
131
+ bt.run(benchmark='000300.SH')
132
+ bt.summary()
133
+ bt.report(title='多股票均线交叉')
134
+ ```
@@ -0,0 +1,87 @@
1
+ # 动量排序选股
2
+
3
+ 每月末按过去 N 日收益率排序,选前几名买入。最经典的量化策略之一。
4
+
5
+ ---
6
+
7
+ ```python
8
+ """每月末按动量排序,选前 20% 的股票买入"""
9
+ from qka import Data, Strategy, Broker, Backtest
10
+ import numpy as np
11
+
12
+
13
+ class Momentum(Strategy):
14
+ def __init__(self):
15
+ super().__init__()
16
+ self.broker = Broker(initial_cash=1_000_000)
17
+ self.monthly_rebalance = False
18
+ self.holdings = []
19
+
20
+ def on_bar(self, date):
21
+ # 月末调仓
22
+ if date.day < 28:
23
+ return
24
+
25
+ # 每月只调一次(本周最后一个交易日)
26
+ if self.monthly_rebalance:
27
+ return
28
+ self.monthly_rebalance = True
29
+
30
+ # 下个月的第一天重置标记
31
+ if date.day >= 28:
32
+ pass # 保持标记,等跨月再重置
33
+
34
+ close = self.get('close')
35
+ if close is None or close.empty or len(close) < 5:
36
+ return
37
+
38
+ # 过去 60 个交易日的动量
39
+ hist = self.history('close', 60)
40
+ if hist is None or hist.empty:
41
+ return
42
+
43
+ # 算每只股票的收益率
44
+ ret = {}
45
+ for sym in hist.columns:
46
+ prices = hist[sym].dropna()
47
+ if len(prices) >= 40: # 至少 40 个有效数据
48
+ ret[sym] = prices.iloc[-1] / prices.iloc[0] - 1
49
+
50
+ if not ret:
51
+ return
52
+
53
+ # 排序,选前 20%
54
+ sorted_syms = sorted(ret, key=ret.get, reverse=True)
55
+ top_n = max(1, len(sorted_syms) // 5)
56
+ buy_list = sorted_syms[:top_n]
57
+
58
+ # 卖出不在列表里的
59
+ for sym in list(self.broker.positions.keys()):
60
+ if sym not in buy_list:
61
+ pos = self.broker.positions[sym]
62
+ price = float(close.get(sym, 0))
63
+ if price > 0:
64
+ self.broker.sell(sym, price, pos['size'])
65
+
66
+ # 买入列表里的新标的
67
+ cash_per_sym = self.broker.cash / len(buy_list)
68
+ for sym in buy_list:
69
+ if sym in self.broker.positions:
70
+ continue
71
+ price = float(close.get(sym, 0))
72
+ if price > 0:
73
+ size = self.sizing.fixed_amount(cash_per_sym, price)
74
+ if size > 0:
75
+ self.broker.buy(sym, price, size)
76
+
77
+
78
+ if __name__ == '__main__':
79
+ data = Data(
80
+ symbols=['000001.SZ', '600000.SH', '000002.SZ',
81
+ '600036.SH', '601166.SH'],
82
+ )
83
+ bt = Backtest(data, Momentum())
84
+ bt.run(benchmark='000300.SH')
85
+ bt.summary()
86
+ bt.report(title='动量排序选股')
87
+ ```
@@ -0,0 +1,106 @@
1
+ # 多因子打分
2
+
3
+ 用多个因子综合打分,选分最高的买。多因子策略的骨架。
4
+
5
+ ---
6
+
7
+ ```python
8
+ """PE + 动量 + 波动率三因子打分选股"""
9
+ from qka import Data, Strategy, Broker, Backtest
10
+
11
+
12
+ class MultiFactor(Strategy):
13
+ def __init__(self):
14
+ super().__init__()
15
+ self.broker = Broker(initial_cash=1_000_000)
16
+
17
+ def on_bar(self, date):
18
+ close = self.get('close')
19
+ if close is None or close.empty:
20
+ return
21
+
22
+ # 每月末调仓
23
+ if date.day < 28:
24
+ return
25
+
26
+ rsi = self.get('rsi_14')
27
+ hist = self.history('rsi_14', 20)
28
+ if hist is None or hist.empty:
29
+ return
30
+
31
+ scores = {}
32
+ for sym in close.index:
33
+ price = float(close[sym])
34
+ if price <= 0:
35
+ continue
36
+ if sym not in rsi.index or sym not in hist.columns:
37
+ continue
38
+
39
+ rsi_val = float(rsi[sym])
40
+ hist_series = hist[sym].dropna()
41
+
42
+ # 因子 1:RSI 适中(30-70 之间最好,太高中性,太低扣分)
43
+ score1 = 1.0 if 30 < rsi_val < 70 else 0.0
44
+ if rsi_val < 30:
45
+ score1 = -0.5
46
+
47
+ # 因子 2:RSI 趋势(最近 20 天 RSI 上升趋势)
48
+ if len(hist_series) >= 10:
49
+ recent = hist_series.iloc[-5:].mean()
50
+ earlier = hist_series.iloc[-10:-5].mean()
51
+ score2 = (recent - earlier) / earlier
52
+ score2 = max(-1, min(1, score2)) # 限制到 [-1, 1]
53
+ else:
54
+ score2 = 0
55
+
56
+ # 因子 3:波动率(低波动加分)
57
+ if len(hist_series) >= 10:
58
+ vol = hist_series.std()
59
+ score3 = max(-1, min(1, -vol / 20)) # 波动越低分越高
60
+ else:
61
+ score3 = 0
62
+
63
+ # 综合打分
64
+ scores[sym] = 0.4 * score1 + 0.4 * score2 + 0.2 * score3
65
+
66
+ if not scores:
67
+ return
68
+
69
+ # 选前 3 名
70
+ sorted_syms = sorted(scores, key=scores.get, reverse=True)
71
+ buy_list = sorted_syms[:3]
72
+
73
+ # 卖出不在名单里的
74
+ for sym in list(self.broker.positions.keys()):
75
+ if sym not in buy_list:
76
+ pos = self.broker.positions[sym]
77
+ price = float(close[sym])
78
+ if price > 0:
79
+ self.broker.sell(sym, price, pos['size'])
80
+
81
+ # 买入名单里的新标的
82
+ cash_per_sym = self.broker.cash / len(buy_list)
83
+ for sym in buy_list:
84
+ if sym in self.broker.positions:
85
+ continue
86
+ price = float(close[sym])
87
+ if price > 0:
88
+ size = self.sizing.fixed_amount(cash_per_sym, price)
89
+ if size > 0:
90
+ self.broker.buy(sym, price, size)
91
+
92
+
93
+ if __name__ == '__main__':
94
+ data = Data(
95
+ symbols=['000001.SZ', '600000.SH', '000002.SZ',
96
+ '600036.SH', '601166.SH', '600519.SH',
97
+ '000858.SZ', '002415.SZ', '300750.SZ'],
98
+ indicators={
99
+ 'rsi_14': ('rsi', 14),
100
+ },
101
+ )
102
+ bt = Backtest(data, MultiFactor())
103
+ bt.run(benchmark='000300.SH')
104
+ bt.summary()
105
+ bt.report(title='多因子打分选股')
106
+ ```
@@ -0,0 +1,60 @@
1
+ # RSI + ATR 风控
2
+
3
+ RSI 判断超买超卖,ATR 控制仓位大小。比固定股数更贴近实战。
4
+
5
+ ---
6
+
7
+ ```python
8
+ """RSI 超卖买入 + ATR 仓位控制"""
9
+ from qka import Data, Strategy, Broker, Backtest
10
+
11
+
12
+ class RsiAtrStrategy(Strategy):
13
+ def __init__(self):
14
+ super().__init__()
15
+ self.broker = Broker(initial_cash=1_000_000)
16
+
17
+ def on_bar(self, date):
18
+ close = self.get('close')
19
+ if close is None or close.empty:
20
+ return
21
+
22
+ rsi = self.get('rsi_14')
23
+ atr = self.get('atr_14')
24
+
25
+ for sym in close.index:
26
+ price = float(close[sym])
27
+ if price <= 0:
28
+ continue
29
+ if sym not in rsi.index or sym not in atr.index:
30
+ continue
31
+
32
+ rsi_val = float(rsi[sym])
33
+ atr_val = float(atr[sym])
34
+
35
+ # 超卖(RSI < 30)且没有持仓 → 买入
36
+ if rsi_val < 30 and sym not in self.broker.positions:
37
+ # ATR 控制仓位:每笔风险不超总资金 2%
38
+ size = self.sizing.atr_risk(0.02, price, atr_val)
39
+ if size > 0:
40
+ self.broker.buy(sym, price, size)
41
+
42
+ # 超买(RSI > 70)且有持仓 → 卖出
43
+ elif rsi_val > 70 and sym in self.broker.positions:
44
+ pos = self.broker.positions[sym]
45
+ self.broker.sell(sym, price, pos['size'])
46
+
47
+
48
+ if __name__ == '__main__':
49
+ data = Data(
50
+ symbols=['000001.SZ', '600000.SH', '000002.SZ'],
51
+ indicators={
52
+ 'rsi_14': ('rsi', 14),
53
+ 'atr_14': ('atr', 14),
54
+ },
55
+ )
56
+ bt = Backtest(data, RsiAtrStrategy())
57
+ bt.run(benchmark='000300.SH')
58
+ bt.summary()
59
+ bt.report(title='RSI + ATR 风控')
60
+ ```
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