qka 2.2.3.dev3__tar.gz → 2.2.4.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.
- {qka-2.2.3.dev3 → qka-2.2.4.dev2}/PKG-INFO +1 -1
- {qka-2.2.3.dev3 → qka-2.2.4.dev2}/qka/core/backtest.py +15 -2
- {qka-2.2.3.dev3 → qka-2.2.4.dev2}/qka/core/broker.py +34 -7
- {qka-2.2.3.dev3 → qka-2.2.4.dev2}/qka/core/sizing.py +24 -4
- {qka-2.2.3.dev3 → qka-2.2.4.dev2}/.github/workflows/release.yml +0 -0
- {qka-2.2.3.dev3 → qka-2.2.4.dev2}/.gitignore +0 -0
- {qka-2.2.3.dev3 → qka-2.2.4.dev2}/.vscode/settings.json +0 -0
- {qka-2.2.3.dev3 → qka-2.2.4.dev2}/CHANGELOG.md +0 -0
- {qka-2.2.3.dev3 → qka-2.2.4.dev2}/LICENSE +0 -0
- {qka-2.2.3.dev3 → qka-2.2.4.dev2}/README.md +0 -0
- {qka-2.2.3.dev3 → qka-2.2.4.dev2}/pyproject.toml +0 -0
- {qka-2.2.3.dev3 → qka-2.2.4.dev2}/qka/__init__.py +0 -0
- {qka-2.2.3.dev3 → qka-2.2.4.dev2}/qka/cli.py +0 -0
- {qka-2.2.3.dev3 → qka-2.2.4.dev2}/qka/core/__init__.py +0 -0
- {qka-2.2.3.dev3 → qka-2.2.4.dev2}/qka/core/accessor.py +0 -0
- {qka-2.2.3.dev3 → qka-2.2.4.dev2}/qka/core/analysis.py +0 -0
- {qka-2.2.3.dev3 → qka-2.2.4.dev2}/qka/core/data.py +0 -0
- {qka-2.2.3.dev3 → qka-2.2.4.dev2}/qka/core/indicator.py +0 -0
- {qka-2.2.3.dev3 → qka-2.2.4.dev2}/qka/core/report.py +0 -0
- {qka-2.2.3.dev3 → qka-2.2.4.dev2}/qka/core/strategy.py +0 -0
- {qka-2.2.3.dev3 → qka-2.2.4.dev2}/qka/utils/__init__.py +0 -0
- {qka-2.2.3.dev3 → qka-2.2.4.dev2}/qka/utils/logger.py +0 -0
- {qka-2.2.3.dev3 → qka-2.2.4.dev2}/skills/qka/SKILL.md +0 -0
- {qka-2.2.3.dev3 → qka-2.2.4.dev2}/skills/qka/references/analysis.md +0 -0
- {qka-2.2.3.dev3 → qka-2.2.4.dev2}/skills/qka/references/backtest.md +0 -0
- {qka-2.2.3.dev3 → qka-2.2.4.dev2}/skills/qka/references/broker.md +0 -0
- {qka-2.2.3.dev3 → qka-2.2.4.dev2}/skills/qka/references/data.md +0 -0
- {qka-2.2.3.dev3 → qka-2.2.4.dev2}/skills/qka/references/sizing.md +0 -0
- {qka-2.2.3.dev3 → qka-2.2.4.dev2}/skills/qka/references/strategy.md +0 -0
- {qka-2.2.3.dev3 → qka-2.2.4.dev2}/tools/generate_api_ref.py +0 -0
|
@@ -79,7 +79,10 @@ class Backtest:
|
|
|
79
79
|
|
|
80
80
|
def run(self, cash: float = 100000.0,
|
|
81
81
|
start_date: str = None, end_date: str = None,
|
|
82
|
-
benchmark: Optional[str] = None
|
|
82
|
+
benchmark: Optional[str] = None,
|
|
83
|
+
commission_rate: float = None,
|
|
84
|
+
stamp_duty_rate: float = None,
|
|
85
|
+
slippage: float = None):
|
|
83
86
|
"""
|
|
84
87
|
执行回测
|
|
85
88
|
|
|
@@ -95,13 +98,23 @@ class Backtest:
|
|
|
95
98
|
end_date: 回测截止日期 YYYY-MM-DD,None 表示数据最晚日期
|
|
96
99
|
benchmark: 基准代码,如 '000300.SH'(沪深300)。
|
|
97
100
|
如果提供,会下载基准数据用于对比。
|
|
101
|
+
commission_rate: 佣金费率,None 表示用 Broker 默认(万 2.5,最低 5 元)
|
|
102
|
+
stamp_duty_rate: 印花税率(仅卖出),None 表示用 Broker 默认(万 5)
|
|
103
|
+
slippage: 滑点比率,None 表示用 Broker 默认(0.1%)
|
|
98
104
|
|
|
99
105
|
Returns:
|
|
100
106
|
None。回测结果保存在 self.results、self.metrics、self.trade_history 中。
|
|
101
107
|
"""
|
|
102
108
|
# 注入基础设施
|
|
103
109
|
self.initial_cash = cash
|
|
104
|
-
|
|
110
|
+
broker_kwargs = {'initial_cash': cash}
|
|
111
|
+
if commission_rate is not None:
|
|
112
|
+
broker_kwargs['commission_rate'] = commission_rate
|
|
113
|
+
if stamp_duty_rate is not None:
|
|
114
|
+
broker_kwargs['stamp_duty_rate'] = stamp_duty_rate
|
|
115
|
+
if slippage is not None:
|
|
116
|
+
broker_kwargs['slippage'] = slippage
|
|
117
|
+
self.strategy.broker = Broker(**broker_kwargs)
|
|
105
118
|
self.strategy.sizing = SizingAccessor(self.strategy.broker)
|
|
106
119
|
self.strategy._data = DataAccessor()
|
|
107
120
|
|
|
@@ -99,6 +99,39 @@ class Broker:
|
|
|
99
99
|
'trades': list(self.trade_history),
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
def _buy_cost(self, price: float, size: int):
|
|
103
|
+
"""
|
|
104
|
+
计算买入成本明细(滑点后执行价、成交额、佣金、总成本)。
|
|
105
|
+
|
|
106
|
+
作为买入费用的唯一来源,供 buy() 与仓位计算 estimate_buy_cost() 复用,
|
|
107
|
+
保证"预留费用"与"实际扣费"口径一致。
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
tuple: (exec_price, amount, commission, total_cost)
|
|
111
|
+
"""
|
|
112
|
+
exec_price = price * (1 + self.slippage)
|
|
113
|
+
amount = exec_price * size
|
|
114
|
+
if self.commission_rate > 0:
|
|
115
|
+
commission = max(amount * self.commission_rate, MIN_COMMISSION)
|
|
116
|
+
else:
|
|
117
|
+
commission = 0.0
|
|
118
|
+
return exec_price, amount, commission, amount + commission
|
|
119
|
+
|
|
120
|
+
def estimate_buy_cost(self, price: float, size: int) -> float:
|
|
121
|
+
"""
|
|
122
|
+
预估买入总成本(含滑点与佣金)。
|
|
123
|
+
|
|
124
|
+
供仓位计算预留费用使用,口径与 buy() 完全一致。
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
price (float): 市价
|
|
128
|
+
size (int): 拟买入数量
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
float: 总成本(成交额 + 佣金)
|
|
132
|
+
"""
|
|
133
|
+
return self._buy_cost(price, size)[3]
|
|
134
|
+
|
|
102
135
|
def buy(self, symbol: str, price: float, size: int) -> bool:
|
|
103
136
|
"""
|
|
104
137
|
买入操作
|
|
@@ -121,13 +154,7 @@ class Broker:
|
|
|
121
154
|
logger.warning(f"价格 {price:.2f} 不合法(前复权可能导致早期价格为负),跳过买入 {symbol}")
|
|
122
155
|
return False
|
|
123
156
|
|
|
124
|
-
exec_price
|
|
125
|
-
amount = exec_price * size
|
|
126
|
-
if self.commission_rate > 0:
|
|
127
|
-
commission = max(amount * self.commission_rate, MIN_COMMISSION)
|
|
128
|
-
else:
|
|
129
|
-
commission = 0.0
|
|
130
|
-
total_cost = amount + commission
|
|
157
|
+
exec_price, amount, commission, total_cost = self._buy_cost(price, size)
|
|
131
158
|
|
|
132
159
|
if self.cash < total_cost:
|
|
133
160
|
logger.debug(f"资金不足!需要 {total_cost:.2f}(佣金 {commission:.2f}),当前可用 {self.cash:.2f}")
|
|
@@ -63,10 +63,14 @@ class SizingAccessor:
|
|
|
63
63
|
"""
|
|
64
64
|
固定金额。
|
|
65
65
|
|
|
66
|
-
计算 amount
|
|
66
|
+
计算 amount 能买多少股(预留滑点与佣金,保证买入总成本不超过 amount),
|
|
67
|
+
向下按手取整。
|
|
68
|
+
|
|
69
|
+
注意:amount 为「总成本」上限(成交额 + 佣金),非纯「成交额」;
|
|
70
|
+
与 percent() 同口径,避免满仓时因费用超支被 Broker 拒单。
|
|
67
71
|
|
|
68
72
|
Args:
|
|
69
|
-
amount:
|
|
73
|
+
amount: 投入金额上限
|
|
70
74
|
price: 每股价格
|
|
71
75
|
|
|
72
76
|
Returns:
|
|
@@ -74,7 +78,13 @@ class SizingAccessor:
|
|
|
74
78
|
"""
|
|
75
79
|
self._validate_positive(amount, 'amount')
|
|
76
80
|
self._validate_positive(price, 'price')
|
|
77
|
-
|
|
81
|
+
# 以含滑点的成交价估算可买股数,再逐手下调,
|
|
82
|
+
# 确保「成交额 + 佣金」不超过 amount(与 Broker.buy 口径一致)。
|
|
83
|
+
exec_price = price * (1 + self._broker.slippage)
|
|
84
|
+
size = self._round_lot(amount / exec_price)
|
|
85
|
+
while size > 0 and self._broker.estimate_buy_cost(price, size) > amount:
|
|
86
|
+
size -= self.MIN_LOT
|
|
87
|
+
return size
|
|
78
88
|
|
|
79
89
|
def percent(self, ratio: float, price: float) -> int:
|
|
80
90
|
"""
|
|
@@ -82,6 +92,9 @@ class SizingAccessor:
|
|
|
82
92
|
|
|
83
93
|
使用可用现金的 ratio 比例买入,按手取整。
|
|
84
94
|
|
|
95
|
+
已预留滑点与佣金:返回的股数保证买入总成本(含费用)不超过
|
|
96
|
+
cash * ratio,避免满仓时因费用超支被 Broker 拒单。
|
|
97
|
+
|
|
85
98
|
Args:
|
|
86
99
|
ratio: 0~1 之间的比例,如 0.1 表示 10%
|
|
87
100
|
price: 每股价格
|
|
@@ -93,7 +106,14 @@ class SizingAccessor:
|
|
|
93
106
|
self._validate_positive(price, 'price')
|
|
94
107
|
if self._broker.cash <= 0:
|
|
95
108
|
return 0
|
|
96
|
-
|
|
109
|
+
budget = self._broker.cash * ratio
|
|
110
|
+
# 以含滑点的成交价估算可买股数,再逐手下调,
|
|
111
|
+
# 确保「成交额 + 佣金」不超过预算(与 Broker.buy 口径一致)。
|
|
112
|
+
exec_price = price * (1 + self._broker.slippage)
|
|
113
|
+
size = self._round_lot(budget / exec_price)
|
|
114
|
+
while size > 0 and self._broker.estimate_buy_cost(price, size) > budget:
|
|
115
|
+
size -= self.MIN_LOT
|
|
116
|
+
return size
|
|
97
117
|
|
|
98
118
|
def atr_risk(self, risk_ratio: float, price: float, atr_value: float,
|
|
99
119
|
multiplier: float = 2.0) -> int:
|
|
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
|