qka · diff
git:20260806.d9a46a9 to git:20260818.f0b9863
20 added, 714 removed. Audit C to A.
- # QKA 框架
+ ---
+ name: qka
+ description: 使用 QKA(快量化)框架生成 A 股量化回测策略、选股和技术指标代码。当需要基于 qka.Data、qka.Strategy、qka.Backtest、qka.Broker 等 API 编写量化代码时使用此技能。
+ ---
- ## 架构
+ # QKA 框架
- qka 框架共五个公开类,AI 生成策略代码时不会遇到第六个。
+ 简洁易用的 A 股量化回测框架,共六个公开类:
| 类 | 全限定名 | 作用 |
|-----|------|------|
| Data | `qka.Data` | 行情数据加载 + 指标预计算 |
| Strategy | `qka.Strategy` | 策略基类 — 实现 `on_bar` 做交易决策 |
| Broker | `qka.Broker` | 虚拟券商 — 执行买卖,管理资金和持仓 |
| SizingAccessor | `qka.SizingAccessor` | 仓位计算 — 四种仓位方法 |
| Backtest | `qka.Backtest` | 回测引擎 — 串联 Data 和 Strategy,注入基础设施 |
| Analysis | `qka.Analysis` | 分析模块 — 常用的分析方法 |
- ---
-
- ## Data
-
- 数据获取和指标预计算。
-
- ### 构造
-
- 创建一个数据对象,配置数据源、股票代码和预计算指标。此时数据尚未下载,调用 `get()` 时才真正获取。
-
- ```python
- from qka import Data
-
- data = Data(
- symbols=['sz.000001', 'sh.600000'],
- period='1d',
- adjust='qfq',
- benchmark=None,
- indicators=None,
- )
- ```
-
- | 参数 | 类型 | 默认值 | 说明 |
- |------|------|--------|------|
- | `symbols` | `list[str]` | `None` | A 股代码,baostock 格式 `sz.000001`、`sh.600000` |
- | `period` | `str` | `'1d'` | 数据周期,当前仅支持 `'1d'` |
- | `adjust` | `str` | `'qfq'` | 复权方式:`'qfq'` 前复权,`'hfq'` 后复权,`'bfq'` 不复权 |
- | `benchmark` | `str` | `None` | 基准指数代码,如 `'sh.000300'`。下载后在 `get()` 结果中追加 `benchmark|returns` 列 |
- | `indicators` | `dict` | `None` | 预计算指标,见下方 |
-
- `get()` 返回的 DataFrame 除了 `open/high/low/close/volume/amount` 六大基本列外,还自动内置一列:
-
- | 常驻列 | 公式 | 说明 |
- |------|------|------|
- | `returns` | `close.diff() / close.shift(1)` | 日收益率,始终存在,无需在 indicators 中声明 |
-
- ### indicators
-
- 在构造 `Data` 时传入,利用数据加载过程一次性预计算所有指标。**不能**在构造后补充。
-
- #### 内置指标
-
- 直接透传 [ta](https://github.com/bukosabino/ta) 库的全部指标函数,格式为:`{'列名': ('ta.路径.函数名', '计算列', 参数...)}`。每个条目独立产一列,第二个参数固定为计算列名(不设默认值)。
-
- ```python
- data = Data(
- symbols=['sz.000001'],
- indicators={
- 'sma_5': ('ta.trend.sma_indicator', 'close', 5),
- 'sma_20': ('ta.trend.sma_indicator', 'close', 20),
- 'ema_20': ('ta.trend.ema_indicator', 'close', 20),
- 'rsi_14': ('ta.momentum.rsi', 'close', 14),
- 'macd_line': ('ta.trend.macd', 'close', 26, 12),
- 'macd_signal': ('ta.trend.macd_signal', 'close', 26, 12, 9),
- 'macd_hist': ('ta.trend.macd_diff', 'close', 26, 12, 9),
- 'bb_upper': ('ta.volatility.bollinger_hband', 'close', 20, 2),
- 'bb_middle': ('ta.volatility.bollinger_mavg', 'close', 20),
- 'bb_lower': ('ta.volatility.bollinger_lband', 'close', 20, 2),
- 'atr_14': ('ta.volatility.average_true_range', 'high', 'low', 'close', 14),
- 'adx_14': ('ta.trend.adx', 'high', 'low', 'close', 14),
- },
- )
- ```
-
- - `spec[0]`: ta 函数完整路径
- - `spec[1]`: 计算列名(`'close'`/`'high'`/`'low'`/`'open'`/`'volume'`)
- - `spec[2:]`: 原样传给该函数
-
- 可用的 ta 函数:
-
- #### trend
-
- | 签名 | 说明 |
- |------|------|
- | `ta.trend.adx(high, low, close, window=14)` | 平均趋向指数 |
- | `ta.trend.adx_neg(high, low, close, window=14)` | ADX 负向指标 |
- | `ta.trend.adx_pos(high, low, close, window=14)` | ADX 正向指标 |
- | `ta.trend.aroon_down(high, low, window=25)` | Aroon 下轨 |
- | `ta.trend.aroon_up(high, low, window=25)` | Aroon 上轨 |
- | `ta.trend.cci(high, low, close, window=20, constant=0.015)` | 商品通道指数 |
- | `ta.trend.dpo(close, window=20)` | 去趋势价格振荡器 |
- | `ta.trend.ema_indicator(close, window=12)` | 指数移动平均 |
- | `ta.trend.ichimoku_a(high, low, window1=9, window2=26, visual=False)` | 一目均衡表 A 线(先行带) |
- | `ta.trend.ichimoku_b(high, low, window2=26, window3=52, visual=False)` | 一目均衡表 B 线(先行带) |
- | `ta.trend.ichimoku_base_line(high, low, window1=9, window2=26, visual=False)` | 一目均衡表基准线 |
- | `ta.trend.ichimoku_conversion_line(high, low, window1=9, window2=26, visual=False)` | 一目均衡表转换线 |
- | `ta.trend.kst(close, roc1=10, roc2=15, roc3=20, roc4=30, window1=10, window2=10, window3=10, window4=15)` | 确然指标(KST) |
- | `ta.trend.kst_sig(close, roc1=10, roc2=15, roc3=20, roc4=30, window1=10, window2=10, window3=10, window4=15, nsig=9)` | KST 信号线 |
- | `ta.trend.macd(close, window_slow=26, window_fast=12)` | MACD 线(DIF) |
- | `ta.trend.macd_diff(close, window_slow=26, window_fast=12, window_sign=9)` | MACD 柱(HIST) |
- | `ta.trend.macd_signal(close, window_slow=26, window_fast=12, window_sign=9)` | MACD 信号线(DEA) |
- | `ta.trend.mass_index(high, low, window_fast=9, window_slow=25)` | 质量指数 |
- | `ta.trend.psar_down(high, low, close, step=0.02, max_step=0.2)` | 抛物线 SAR(下方) |
- | `ta.trend.psar_down_indicator(high, low, close, step=0.02, max_step=0.2)` | 抛物线 SAR 下方指示 |
- | `ta.trend.psar_up(high, low, close, step=0.02, max_step=0.2)` | 抛物线 SAR(上方) |
- | `ta.trend.psar_up_indicator(high, low, close, step=0.02, max_step=0.2)` | 抛物线 SAR 上方指示 |
- | `ta.trend.sma_indicator(close, window=12)` | 简单移动平均 |
- | `ta.trend.stc(close, window_slow=50, window_fast=23, cycle=10, smooth1=3, smooth2=3)` | 沙夫趋势周期 |
- | `ta.trend.trix(close, window=15)` | 三重指数平滑平均线 |
- | `ta.trend.vortex_indicator_neg(high, low, close, window=14)` | 漩涡指标负向 |
- | `ta.trend.vortex_indicator_pos(high, low, close, window=14)` | 漩涡指标正向 |
- | `ta.trend.wma_indicator(close, window=9)` | 加权移动平均 |
-
- #### momentum
-
- | 签名 | 说明 |
- |------|------|
- | `ta.momentum.awesome_oscillator(high, low, window1=5, window2=34)` | 动量振荡器 |
- | `ta.momentum.kama(close, window=10, pow1=2, pow2=30)` | 考夫曼自适应移动平均 |
- | `ta.momentum.ppo(close, window_slow=26, window_fast=12, window_sign=9)` | 价格百分比振荡器 |
- | `ta.momentum.ppo_hist(close, window_slow=26, window_fast=12, window_sign=9)` | PPO 柱 |
- | `ta.momentum.ppo_signal(close, window_slow=26, window_fast=12, window_sign=9)` | PPO 信号线 |
- | `ta.momentum.pvo(volume, window_slow=26, window_fast=12, window_sign=9)` | 成交量百分比振荡器 |
- | `ta.momentum.pvo_hist(volume, window_slow=26, window_fast=12, window_sign=9)` | PVO 柱 |
- | `ta.momentum.pvo_signal(volume, window_slow=26, window_fast=12, window_sign=9)` | PVO 信号线 |
- | `ta.momentum.roc(close, window=12)` | 变动率 |
- | `ta.momentum.rsi(close, window=14)` | 相对强弱指数 |
- | `ta.momentum.stoch(high, low, close, window=14, smooth_window=3)` | 随机指标 %K |
- | `ta.momentum.stoch_signal(high, low, close, window=14, smooth_window=3)` | 随机指标 %D |
- | `ta.momentum.stochrsi(close, window=14, smooth1=3, smooth2=3)` | 随机 RSI |
- | `ta.momentum.stochrsi_d(close, window=14, smooth1=3, smooth2=3)` | 随机 RSI %D |
- | `ta.momentum.stochrsi_k(close, window=14, smooth1=3, smooth2=3)` | 随机 RSI %K |
- | `ta.momentum.tsi(close, window_slow=25, window_fast=13)` | 真实强度指数 |
- | `ta.momentum.ultimate_oscillator(high, low, close, window1=7, window2=14, window3=28, weight1=4.0, weight2=2.0, weight3=1.0)` | 终极振荡器 |
- | `ta.momentum.williams_r(high, low, close, lbp=14)` | 威廉指标 |
-
- #### volatility
-
- | 签名 | 说明 |
- |------|------|
- | `ta.volatility.average_true_range(high, low, close, window=14)` | 平均真实波幅 |
- | `ta.volatility.bollinger_hband(close, window=20, window_dev=2)` | 布林带上轨 |
- | `ta.volatility.bollinger_hband_indicator(close, window=20, window_dev=2)` | 布林带上轨指示 |
- | `ta.volatility.bollinger_lband(close, window=20, window_dev=2)` | 布林带下轨 |
- | `ta.volatility.bollinger_lband_indicator(close, window=20, window_dev=2)` | 布林带下轨指示 |
- | `ta.volatility.bollinger_mavg(close, window=20)` | 布林带中轨 |
- | `ta.volatility.bollinger_pband(close, window=20, window_dev=2)` | 布林带 %B |
- | `ta.volatility.bollinger_wband(close, window=20, window_dev=2)` | 布林带带宽 |
- | `ta.volatility.donchian_channel_hband(high, low, close, window=20, offset=0)` | 唐奇安通道上轨 |
- | `ta.volatility.donchian_channel_lband(high, low, close, window=20, offset=0)` | 唐奇安通道下轨 |
- | `ta.volatility.donchian_channel_mband(high, low, close, window=10, offset=0)` | 唐奇安通道中轨 |
- | `ta.volatility.donchian_channel_pband(high, low, close, window=10, offset=0)` | 唐奇安通道 %B |
- | `ta.volatility.donchian_channel_wband(high, low, close, window=10, offset=0)` | 唐奇安通道带宽 |
- | `ta.volatility.keltner_channel_hband(high, low, close, window=20, window_atr=10, original_version=True)` | 肯特纳通道上轨 |
- | `ta.volatility.keltner_channel_hband_indicator(high, low, close, window=20, window_atr=10, original_version=True)` | 肯特纳通道上轨指示 |
- | `ta.volatility.keltner_channel_lband(high, low, close, window=20, window_atr=10, original_version=True)` | 肯特纳通道下轨 |
- | `ta.volatility.keltner_channel_lband_indicator(high, low, close, window=20, window_atr=10, original_version=True)` | 肯特纳通道下轨指示 |
- | `ta.volatility.keltner_channel_mband(high, low, close, window=20, window_atr=10, original_version=True)` | 肯特纳通道中轨 |
- | `ta.volatility.keltner_channel_pband(high, low, close, window=20, window_atr=10, original_version=True)` | 肯特纳通道 %B |
- | `ta.volatility.keltner_channel_wband(high, low, close, window=20, window_atr=10, original_version=True)` | 肯特纳通道带宽 |
- | `ta.volatility.ulcer_index(close, window=14)` | 溃疡指数 |
-
- #### 自定义指标
-
- 非 ta 库的自定义因子用 callable:
-
- ```python
- indicators={
- 'ma5': lambda df: df['close'].rolling(5).mean(),
- }
- ```
-
- 函数接收单只股票的 DataFrame(含 `open/high/low/close/volume`),返回添加了新列的 DataFrame。
-
- #### qka 内置指标
-
- qka 框架内置的滚动窗口指标,格式为 `{'列名': ('qka.函数名', 窗口, ...)}`。所有函数使用 `returns` 列计算,部分需要基准数据(`benchmark|returns`)。
-
- ```python
- data = Data(
- symbols=['sz.000001', 'sh.600000'],
- benchmark='sh.000300', # alpha/beta/information_ratio 必需
- indicators={
- 'sma_20': ('ta.trend.sma_indicator', 'close', 20),
- 'beta_60': ('qka.beta', 60),
- 'alpha_60': ('qka.alpha', 60),
- 'sharpe_60':('qka.sharpe', 60),
- 'mdd_60': ('qka.max_drawdown', 60),
- 'ir_60': ('qka.information_ratio', 60),
- 'zigzag_60':('rolling_zigzag', 60, 0.3, 1600),
- },
- )
- ```
-
- | 函数 | 需要 benchmark | 说明 |
- |------|:---:|------|
- | `qka.beta` | ✅ | 滚动 β,公式 `Cov(Rp,Rm) / Var(Rm)` |
- | `qka.alpha` | ✅ | 滚动詹森 α(年化),公式 `Rp - [Rf + β·(Rm-Rf)]` |
- | `qka.sharpe` | ❌ | 滚动夏普比率(年化),公式 `(Rp-Rf) / σp` |
- | `qka.max_drawdown` | ❌ | 滚动窗口最大回撤,返回负数(如 -0.15) |
- | `qka.information_ratio` | ✅ | 滚动信息比率(年化),公式 `mean(Rp-Rm) / std(Rp-Rm)` |
- | `rolling_zigzag` | ❌ | 滚动窗口 Zigzag 趋势方向,HP 滤波+Zigzag 状态机,输出 1(上行)/-1(下行)/NaN |
-
- - `rolling_zigzag` 签名:`('rolling_zigzag', window, threshold, lamb)`,默认 `threshold=0.3` `lamb=1600`,需要 `{symbol}|close` 列
- - 无 `rf` 参数时默认 `0`,无 `periods_per_year` 时默认 `252`
- - 需要 benchmark 的函数在 `Data` 未设 `benchmark` 时抛 `ValueError`
- - 每个函数独立计算,即使 α 和 β 内部都跑 OLS 也不共享结果(与 ta 库 BBands 三函数一致)
-
- ### get()
-
- `get(lazy=False, start_date=None, end_date=None) → pd.DataFrame`
-
- 调用后触发数据下载和指标计算,返回合并后的宽表。`Backtest.run()` 内部会调用它。
-
- | 参数 | 类型 | 默认值 | 说明 |
- |------|------|--------|------|
- | `lazy` | `bool` | `False` | 返回 `dask.DataFrame` 而非 `pd.DataFrame` |
- | `start_date` | `str` | `None` | 起始日期 `'YYYY-MM-DD'`。有指标时自动向后扩展窗口确保预热数据充足 |
- | `end_date` | `str` | `None` | 截止日期 `'YYYY-MM-DD'` |
-
- ```python
- df = data.get()
- df = data.get(start_date='2024-01-01', end_date='2024-12-31')
- ```
-
- | 属性 | 说明 |
- |------|------|
- | 类型 | `pd.DataFrame`(`lazy=True` 时返回 `dask.DataFrame`) |
- | 索引 | 日期索引,**索引名为 `"date"`**。`reset_index()` 后日期列名也是 `"date"` |
- | 列名 | `{symbol}|{factor}` — 例如 `sz.000001|close`、`sz.000001|sma_5`、`sh.600000|volume` |
- | 常驻列 | 除 `open/high/low/close/volume/amount` 外,自动内置 `{symbol}|returns` |
- | 基准列 | 若构造时设了 `benchmark`,追加 `benchmark|returns`(无 `{symbol}|` 前缀) |
- | 列值 | 全部为 `float64`,指标列的早期行可能含 `NaN` |
- | 异常 | 无数据时抛出 `RuntimeError` |
-
- **返回的宽表示例:**
-
- ```python
- data = Data(symbols=['sz.000001', 'sh.600000'], indicators={
- 'sma_5': ('ta.trend.sma_indicator', 'close', 5),
- })
- df = data.get(start_date='2024-01-02', end_date='2024-01-05')
- ```
-
- 返回的 DataFrame 结构(行=日期,列=每只股票的完整字段堆叠):
-
- ```
- sz.000001|open sz.000001|close ... sz.000001|sma_5 sh.600000|open sh.600000|close ... sh.600000|sma_5
- 2024-01-02 10.0 10.2 ... NaN 15.0 15.3 ... NaN
- 2024-01-03 10.1 10.5 ... NaN 15.2 15.6 ... NaN
- 2024-01-04 10.3 10.8 ... NaN 14.9 15.1 ... NaN
- 2024-01-05 10.2 10.6 ... NaN 15.4 15.9 ... NaN
- ```
-
- - 每只股票独占一组列,列前缀 = symbol
- - `returns` 列自动存在,无需在 indicators 中声明
- - 前 4 行 SMA 为 NaN(窗口=5,不足)
- - 若设了 `benchmark='sh.000300'`,末尾多一列 `benchmark|returns`
-
- ```python
- # 列名格式:{symbol}|{factor}
- df.columns # ['sz.000001|open', 'sz.000001|close', 'sz.000001|returns',
- # 'sz.000001|sma_5', 'sh.600000|open', 'sh.600000|close',
- # 'sh.600000|returns', 'sh.600000|sma_5']
-
- # 索引名为 "date",reset_index 后转为 pd.Timestamp 列
- df = df.reset_index()
- df['date'].iloc[0] # Timestamp('2024-01-02 00:00:00')
-
- # 输出 JSON 前需转为字符串
- df['date'] = df['date'].dt.strftime('%Y-%m-%d')
- df['date'].iloc[0] # '2024-01-02'
- ```
-
- ---
-
- ## Broker
-
- 虚拟券商,由 `Backtest.run()` 在执行时创建并注入到 `strategy.broker`。用户不直接构造。
-
- ### 属性
-
- | 属性 | 类型 | 说明 |
- |------|------|------|
- | `broker.cash` | `float` | 当前可用现金 |
- | `broker.positions` | `dict` | `{symbol: {'size': int, 'avg_price': float}}` |
-
- ### buy()
-
- `buy(symbol: str, price: float, size: int) → bool`
-
- 买入,`size` 必须是 100 的整数倍(A 股 1 手 = 100 股)。
-
- ```python
- success = self.broker.buy('sz.000001', float(close['sz.000001']), 100)
- ```
-
- - 实际成交价 = `price * (1 + slippage)`(默认滑点 0.1%)
- - 自动扣佣金(万 2.5,最低 5 元)
- - 资金不足返回 `False`
- - `price <= 0` 返回 `False`(前复权可能导致早期价格为负)
-
- ### sell()
-
- `sell(symbol: str, price: float, size: int) → bool`
-
- 卖出,`size` 必须是 100 的整数倍。
-
- ```python
- success = self.broker.sell('sz.000001', float(close['sz.000001']), 100)
- ```
-
- - 自动扣佣金 + 印花税(万 5,仅卖出)
- - 持仓不足返回 `False`
-
- ---
-
- ## SizingAccessor
-
- 仓位计算器,由 `Backtest.run()` 在执行时创建并注入到 `strategy.sizing`。返回值已按手取整(100 的倍数)。
-
- | 方法 | 说明 |
- |------|------|
- | `percent(ratio, price)` | 用可用现金的 `ratio` 比例买入。`ratio` 在 0~1 之间 |
- | `fixed_amount(amount, price)` | 固定金额买入 |
- | `fixed_shares(n)` | 固定股数 |
- | `atr_risk(risk_ratio, price, atr_value, multiplier=2.0)` | ATR 风险仓位 |
-
- ```python
- price = float(close['sz.000001'])
- size = self.sizing.percent(0.1, price) # 10% 仓位,已按手取整
- if size > 0:
- self.broker.buy('sz.000001', price, size)
- ```
-
- ---
-
- ## Strategy
-
- 策略基类。所有交易策略必须继承 `qka.Strategy`,实现 `on_bar` 方法。
-
- `broker` / `sizing` / `_data` 由 `Backtest.run()` 在执行时注入,策略 `__init__` 不需要创建它们。
-
- ### 构造
-
- ```python
- from qka import Strategy
-
- class MyStrategy(Strategy):
- def __init__(self):
- super().__init__()
- # 在这里定义自己的参数
- self.lookback = 20
-
- def on_bar(self, date):
- ...
- ```
-
- | 约束 | 说明 |
- |------|------|
- | `__init__` | **必须**调用 `super().__init__()`。不需要传任何参数 |
- | `on_bar` 签名 | 只有 `self` 和 `date`。**没有** `get` 参数(旧版 API,已废弃) |
- | `self.params` | **不存在**。不要写 `self.params.get('fast', 5)`,直接用实例属性 |
-
- `Backtest.run()` 在执行时注入以下属性:
-
- | 属性 | 类型 | 说明 |
- |------|------|------|
- | `self.broker` | `Broker` | 虚拟券商,执行买卖、管理资金和持仓 |
- | `self.sizing` | `SizingAccessor` | 仓位计算,`self.sizing.percent(0.1, price)` |
- | `self._data` | `DataAccessor` | 私有,供 `self.get()` / `self.history()` 使用,勿直接访问 |
-
- ### self.get()
-
- `get(factor: str) → pd.Series`
-
- 获取当前 bar 的横截面数据。**只能在 `on_bar` 内调用**。
-
- ```python
- close = self.get('close') # pd.Series,index=股票代码,value=当前价格
- high = self.get('high')
- sma5 = self.get('sma_5') # indicators 中定义的指标列
- ```
-
- | 属性 | 说明 |
- |------|------|
- | 返回类型 | `pd.Series` |
- | index | 股票代码,如 `'sz.000001'`、`'sh.600000'` |
- | values | 当前 bar 的最新值,`float` |
- | 空值 | 无数据时返回空 `pd.Series`,不是 `None` |
-
- ```python
- # 安全访问
- if 'sz.000001' in close.index:
- price = float(close['sz.000001'])
- ```
-
- ### self.history()
-
- `history(factor: str, window: int = 20) → pd.DataFrame`
-
- 获取因子的历史窗口数据。
-
- ```python
- hist = self.history('close', 20) # 最近 20 天的收盘价
- ```
-
- | 属性 | 说明 |
- |------|------|
- | 返回类型 | `pd.DataFrame` |
- | 行 | 日期(`pd.Timestamp`),最近的在最后 |
- | 列 | 股票代码 |
- | 异常 | 因子不存在时返回空 DataFrame(有索引无列),不抛异常 |
-
- ### self.broker / self.sizing
-
- 由 `Backtest.run()` 注入(见上方注入表),通过 `self.broker.buy()` / `self.broker.sell()` 和 `self.sizing.percent()` 等方法使用。详见 [Broker](#broker) 和 [SizingAccessor](#sizingaccessor) 独立章节。
-
- ### 完整示例
-
- ```python
- from qka import Strategy
-
- class MyStrategy(Strategy):
- def __init__(self):
- super().__init__()
- self.position_pct = 0.2 # 每次买入用 20% 仓位
-
- def on_bar(self, date):
- close = self.get('close')
- sma_fast = self.get('sma_5')
- sma_slow = self.get('sma_20')
-
- for symbol in close.index:
- price = float(close[symbol])
- if symbol not in sma_fast.index or symbol not in sma_slow.index:
- continue
-
- if sma_fast[symbol] > sma_slow[symbol]:
- size = self.sizing.percent(self.position_pct, price)
- if size > 0:
- self.broker.buy(symbol, price, size)
- elif sma_fast[symbol] < sma_slow[symbol]:
- pos = self.broker.positions.get(symbol, {}).get('size', 0)
- if pos > 0:
- self.broker.sell(symbol, price, pos)
- ```
-
- ---
-
- ## Backtest
-
- 回测引擎,串联 Data 和 Strategy。在执行时注入 broker/sizing/data。
-
- ### 构造
-
- ```python
- from qka import Backtest
-
- bt = Backtest(data, strategy)
- ```
-
- | 参数 | 类型 | 说明 |
- |------|------|------|
- | `data` | `Data` | 数据对象 |
- | `strategy` | `Strategy` | 策略对象 |
-
- 构造时不执行任何操作,只绑定引用。
-
- ### run()
-
- `run(cash=100000.0, start_date=None, end_date=None, benchmark=None)`
-
- 执行回测。注入 broker/sizing/data 后遍历每个交易日,调用 `strategy.on_bar(date)`。
-
- | 参数 | 默认值 | 说明 |
- |------|--------|------|
- | `cash` | `100000.0` | 初始资金 |
- | `start_date` | `None` | 回测起始日期 `'YYYY-MM-DD'` |
- | `end_date` | `None` | 回测截止日期 |
- | `benchmark` | `None` | 基准指数代码,baostock 格式如 `'sh.000300'` |
-
- > 500 bar 以上自动分块迭代,避免一次性加载全量数据。
-
- ```python
- bt.run(cash=200000, start_date='2024-01-01')
- ```
-
- 执行后可通过 `bt.results` 和 `bt.trade_history` 获取结果,详见下方。
-
- ### bt.metrics
-
- `dict`,绩效指标,`run()` 结束时计算并缓存。
-
- | 字段 | 类型 | 说明 |
- |------|------|------|
- | `initial_cash` | `float` | 初始资金 |
- | `final_equity` | `float` | 最终资产 |
- | `total_return_pct` | `float` | 总收益率(%) |
- | `annual_return_pct` | `float` | 年化收益率(%) |
- | `annual_volatility_pct` | `float` | 年化波动率(%) |
- | `sharpe_ratio` | `float` | 夏普比率(无风险利率 3%) |
- | `max_drawdown_pct` | `float` | 最大回撤(%) |
- | `calmar_ratio` | `float` | Calmar 比率 |
- | `total_trades` | `int` | 交易总次数 |
- | `win_rate_pct` | `float` | 胜率(%) |
- | `profit_loss_ratio` | `float` | 盈亏比 |
- | `total_commission` | `float` | 总手续费 |
- | `n_days` | `int` | 回测天数 |
-
- ```python
- bt.run()
- print(bt.metrics['total_return_pct']) # 15.3
- print(bt.metrics['sharpe_ratio']) # 1.25
- ```
-
- ### bt.results
-
- `pd.DataFrame`,索引为日期。每行是当日收盘后的快照。
-
- | 列 | 类型 | 说明 |
- |------|------|------|
- | `cash` | `float` | 可用现金 |
- | `value` | `float` | 持仓总市值 |
- | `total` | `float` | 总资产(cash + value) |
- | `positions` | `dict` | 各持仓明细,`{symbol: {size, avg_price, current_price, market_value, profit_pct}}` |
- | `trades` | `list[dict]` | 截止当日的全部交易记录 |
-
- ```python
- bt.results.index # DatetimeIndex
- bt.results['total'] # 每日总资产序列
- bt.results['cash'] # 每日现金序列
- bt.results.iloc[-1] # 最终状态
- ```
-
- ### bt.trade_history
-
- `list[dict]`,逐笔交易明细,按成交顺序排列。
-
- 买入时含:
-
- | 字段 | 类型 | 说明 |
- |------|------|------|
- | `action` | `str` | `'buy'` |
- | `symbol` | `str` | 股票代码 |
- | `price` | `float` | 下单价格(成交前) |
- | `exec_price` | `float` | 实际成交价(滑点后) |
- | `size` | `int` | 成交股数 |
- | `amount` | `float` | 成交金额(exec_price × size) |
- | `commission` | `float` | 佣金 |
- | `total_cost` | `float` | 总支出(amount + commission) |
- | `timestamp` | | 交易日 |
-
- 卖出时含:
-
- | 字段 | 类型 | 说明 |
- |------|------|------|
- | `action` | `str` | `'sell'` |
- | `symbol` | `str` | 股票代码 |
- | `price` | `float` | 下单价格 |
- | `exec_price` | `float` | 实际成交价(滑点后) |
- | `size` | `int` | 成交股数 |
- | `amount` | `float` | 成交金额 |
- | `commission` | `float` | 佣金 |
- | `stamp_duty` | `float` | 印花税 |
- | `net_proceeds` | `float` | 净收入(amount - commission - stamp_duty) |
- | `timestamp` | | 交易日 |
-
- ### 完整示例
-
- ```python
- from qka import Data, Strategy, Backtest
-
- data = Data(
- symbols=['sz.000001'],
- indicators={
- 'sma_5': ('ta.trend.sma_indicator', 'close', 5),
- 'sma_20': ('ta.trend.sma_indicator', 'close', 20),
- },
- )
-
- class MaCross(Strategy):
- def __init__(self):
- super().__init__()
- self.pct = 0.2
-
- def on_bar(self, date):
- close = self.get('close')
- fast = self.get('sma_5')
- slow = self.get('sma_20')
- for sym in close.index:
- price = float(close[sym])
- if fast[sym] > slow[sym]:
- size = self.sizing.percent(self.pct, price)
- if size > 0:
- self.broker.buy(sym, price, size)
- else:
- pos = self.broker.positions.get(sym, {}).get('size', 0)
- if pos > 0:
- self.broker.sell(sym, price, pos)
-
- bt = Backtest(data, MaCross())
- bt.run(cash=200000, start_date='2024-01-01')
- print(bt.metrics['total_return_pct'])
- ```
-
- ---
-
- ## Analysis
-
- 分析模块,收纳常用的分析方法。
-
- ### 导入
-
- ```python
- from qka import Analysis
- ```
-
- 构造无参数:
-
- ```python
- analysis = Analysis()
- ```
-
- ### zigzag()
-
- `zigzag(series, threshold=0.3, min_days=90, lamb=1600) → list[Segment]`
-
- HP 滤波 + Zigzag 趋势分段。对价格序列做 Hodrick–Prescott 滤波去噪后,在趋势线上识别交替的上行/下行段。
-
- ⚠️ 包含未来函数,仅供事后分析,禁止用于回测策略。
-
- | 参数 | 类型 | 默认值 | 说明 |
- |------|------|--------|------|
- | `series` | `pd.Series` | 必填 | 价格序列,index 为日期,values 为价格 |
- | `threshold` | `float` | `0.3` | 反转阈值。峰顶回落 30% 确认顶,谷底反弹 30% 确认底 |
- | `min_days` | `int` | `90` | 最小段长(自然日)。短于此的段被前后段合并吸收 |
- | `lamb` | `int` | `1600` | HP 滤波平滑参数,日线数据标准值 |
-
- 返回值 `list[Segment]`,每个 `Segment` 是 namedtuple:
-
- | 字段 | 类型 | 说明 |
- |------|------|------|
- | `start` | `pd.Timestamp` | 段起始日期 |
- | `end` | `pd.Timestamp` | 段结束日期 |
- | `direction` | `str` | `'上行'` 或 `'下行'` |
- | `change` | `float` | 段内涨跌幅(百分比) |
-
- ### alpha_beta()
-
- `alpha_beta(returns, bench_returns, rf=0.0, periods_per_year=252) → AlphaBeta`
-
- OLS 回归,一次返回 α 和 β。
-
- | 参数 | 类型 | 默认值 | 说明 |
- |------|------|--------|------|
- | `returns` | array-like | 必填 | 资产收益率序列 |
- | `bench_returns` | array-like | 必填 | 基准收益率序列 |
- | `rf` | `float` | `0.0` | 年化无风险利率 |
- | `periods_per_year` | `int` | `252` | 年化周期数,日线=252,周线=52 |
+ ## 参考文档
- 返回值 `AlphaBeta` namedtuple:
+ 详细 API 文档按模块拆分在 `references/` 目录下,按需查阅对应文件:
- | 字段 | 类型 | 说明 |
+ | 模块 | 文件 | 内容 |
|------|------|------|
- | `alpha` | `float` | 詹森 α(已年化)。正值 = 跑赢 CAPM 预测 |
- | `beta` | `float` | 市场敏感度 β = Cov(Rp,Rm) / Var(Rm) |
-
- ```python
- # 用法
- from qka import Analysis
-
- analysis = Analysis()
- result = analysis.alpha_beta(
- df['sz.000001|returns'].dropna(),
- df['benchmark|returns'].dropna(),
- )
- print(f'α={result.alpha:.2%}, β={result.beta:.2f}')
- ```
-
- ### sharpe_ratio()
-
- `sharpe_ratio(returns, rf=0.0, periods_per_year=252) → float`
-
- 夏普比率(年化)。公式:`(mean(Rp) - rf_period) / std(Rp) × √N`。
-
- ### max_drawdown()
-
- `max_drawdown(returns) → float`
-
- 最大回撤,返回正数(0.35 = 35% 回撤)。
-
- ### information_ratio()
-
- `information_ratio(returns, bench_returns, periods_per_year=252) → float`
-
- 信息比率(年化)。公式:`mean(Rp-Rm) / std(Rp-Rm) × √N`。衡量跑赢基准的稳定性。
-
- ### 示例
-
- ```python
- from qka import Data, Analysis
-
- data = Data(symbols=['sz.000001'], benchmark='sh.000300')
- df = data.get()
-
- analysis = Analysis()
-
- # 趋势分段
- segs = analysis.zigzag(df['sz.000001|close'], threshold=0.3, min_days=90)
- for s in segs:
- print(f'{s.start.date()} ~ {s.end.date()} {s.direction} {s.change:+.1f}%')
-
- # 单次指标(标量,非滚动)
- ab = analysis.alpha_beta(
- df['sz.000001|returns'].dropna(),
- df['benchmark|returns'].dropna(),
- )
- print(f'α={ab.alpha:.2%}, β={ab.beta:.2f}')
+ | Data | `references/data.md` | 数据加载、指标预计算(ta 库全部指标 + qka 内置指标)、`get()` |
+ | Strategy | `references/strategy.md` | 策略基类、`on_bar`、`self.get()`、`self.history()` |
+ | Backtest | `references/backtest.md` | `run()`、`bt.metrics`、`bt.results`、`bt.trade_history` |
+ | Broker | `references/broker.md` | `buy()`、`sell()` |
+ | SizingAccessor | `references/sizing.md` | 四种仓位方法 |
+ | Analysis | `references/analysis.md` | `zigzag()`、`alpha_beta()`、`sharpe_ratio()`、`max_drawdown()`、`information_ratio()` |
- sharpe = analysis.sharpe_ratio(df['sz.000001|returns'])
- mdd = analysis.max_drawdown(df['sz.000001|returns'])
- ir = analysis.information_ratio(df['sz.000001|returns'], df['benchmark|returns'])
- print(f'夏普={sharpe:.2f}, 最大回撤={mdd:.1%}, IR={ir:.2f}')
- ```
+ ## 使用指南
+ - 编写**回测策略**:读 `references/strategy.md` + `references/backtest.md`,必要时读 `references/data.md`
+ - 编写**选股 / 数据加载**:读 `references/data.md`
+ - 使用**买卖 / 仓位**:读 `references/broker.md` + `references/sizing.md`
+ - 使用**事后分析**:读 `references/analysis.md`