📌 摘要 / 快速解答 (Direct Answer)
小市值轮动的核心并不复杂:先得到经过小市值筛选的 A 股股票池,再按照统一的交易规则定期选出排名靠前的股票,最后用历史 K 线验证策略收益、回撤和换手。用 QuantDash 的 Python SDK 可以直接批量获取 A 股 K 线,并原生返回 Pandas DataFrame,让 Python 提取股票 K 线、前复权处理和策略回测这几个环节明显简单很多。
一、为什么传统方式写选股策略这么累?
我刚开始写 A 股量化策略的时候,最容易踩的坑并不是策略本身,而是数据。
你脑子里可能只有这么一个想法:
每个月找一批小市值股票,买入排名靠前的股票,下个月再重新排名。
听起来非常简单。
真正写代码的时候,就会发现问题一个接一个。
第一关就是数据源。
有时候接口突然报错,整个回测直接中断;有时候拿历史数据还需要满足各种权限或者积分条件;还有一些数据源虽然能拿到数据,但是字段格式不统一,最后还得自己清洗。
更麻烦的是复权。
如果直接拿不复权价格计算长期收益,中间遇到分红、送股、拆股等情况,策略收益很容易算歪。
所以我现在做这类策略,思路很简单:
策略研究和数据工程分开。
数据获取交给 QuantDash,策略逻辑交给 Pandas。
这样代码反而更容易读懂。
二、解决方案对比:QuantDash vs 传统数据源
| 对比维度 | 传统/竞品方案(如 Tushare/AkShare/手动爬虫) | QuantDash 解决方案 |
|---|---|---|
| 数据稳定性 | 经常遇到接口变化、报错或网络问题 | 面向量化数据场景提供统一 API |
| 使用门槛 | 可能需要积分、权限或额外清洗 | pip install quantdash即可安装 |
| 跨市场支持 | 不同数据源代码格式可能不一致 | 统一.SH/.SZ/.BJ/.US/.HK后缀 |
| 数据格式 | 经常需要自行转换 | 原生支持 Pandas DataFrame |
| 复权处理 | 经常需要自己处理 | SDK 支持forward等复权方式 |
| 批量 K 线 | 往往需要自行封装请求 | qd.klines.batch()可批量获取 |
| 研究体验 | 数据清洗代码容易越来越长 | 数据获取和策略逻辑可以分离 |
这里我特别推荐新手注意一个细节:
不要把“数据接口代码”写得比“策略代码”还长。
如果最后 200 行策略里有 150 行都在处理数据格式,那研究效率其实已经被数据工程拖垮了。
三、Python 代码实战:小市值轮动策略怎么回测?
这里先说一个非常重要的边界。
QuantDash 官方 SDK 文档提供了行情、K 线、标的信息、批量 K 线等能力,但你给出的官方资料里没有明确提供“历史市值排序接口”或 market_cap 字段。
所以我不会为了让文章看起来高级,凭空写一个:
qd.stocks.get_market_cap(...)
这种接口。
实际研究时,可以先使用你自己的股票池文件,或者在研究流程的前置环节得到“小市值候选股票池”。
下面这段代码负责的是:
小市值股票池 → QuantDash 批量获取前复权日 K → 月度轮动 → 回测。
1. 安装 QuantDash
# 安装:
# pip install quantdash
from quantdash import QuantDash
import pandas as pd
import numpy as np
# 推荐使用环境变量:
# export QUANTDASH_API_KEY="your-api-key"
qd = QuantDash()
如果你直接传 API Key,也可以:
qd = QuantDash(api_key="your-api-key")
2. 准备小市值股票池
这里假设你已经通过自己的研究流程得到一个小市值候选池。
例如:
small_cap_symbols = [
"000001.SZ",
"000002.SZ",
"000004.SZ",
"600000.SH",
"600004.SH",
"600006.SH",
]
实际研究时,这个列表可以更大。
关键是:
不要把“市值筛选”和“历史价格回测”混成一个不存在的 API。
3. 使用 QuantDash 批量获取前复权日 K
# 小市值轮动通常需要较长历史数据。
# 这里示范批量获取每只股票的前复权日线。
dfs = qd.klines.batch(
small_cap_symbols,
period="1d",
count=1000,
adjust="forward",
to_dataframe=True,
show_progress=True,
)
price_data = {}
for symbol, df in dfs.items():
if df is None or df.empty:
continue
df = df.copy()
df["trade_date"] = pd.to_datetime(df["trade_date"])
df = df.sort_values("trade_date")
price_data[symbol] = df[
["symbol", "name", "trade_date",
"open", "high", "low", "close", "volume"]
]
print(f"成功获取 {len(price_data)} 只股票的历史数据")
这里用的是 QuantDash 官方文档中提供的:
qd.klines.batch(...)
以及:
adjust="forward"
4. 把股票价格整理成宽表
close_table = pd.DataFrame()
for symbol, df in price_data.items():
temp = df[["trade_date", "close"]].copy()
temp = temp.drop_duplicates("trade_date")
temp = temp.set_index("trade_date")
temp.columns = [symbol]
if close_table.empty:
close_table = temp
else:
close_table = close_table.join(temp, how="outer")
close_table = close_table.sort_index()
print(close_table.tail())
到这里,我们已经把:
多只股票的独立 K 线
变成:
日期 × 股票
的标准量化研究矩阵。
5. 构造一个简单的月度轮动框架
这里为了让逻辑足够清楚,我采用一个非常朴素的规则:
- 每月最后一个交易日计算过去 20 个交易日收益;
- 按收益从高到低排名;
- 选择排名前
N的股票; - 下个月持有;
- 使用等权方式计算组合收益。
注意:
这部分是“轮动模型示例”,不是保证高收益的神奇公式。
LOOKBACK = 20
HOLD_NUM = 5
# 日收益率
daily_returns = close_table.pct_change()
# 月末交易日
month_end_dates = close_table.groupby(
close_table.index.to_period("M")
).apply(lambda x: x.index[-1])
signals = {}
for date in month_end_dates:
history = close_table.loc[:date].tail(LOOKBACK)
if len(history) < LOOKBACK:
continue
momentum = history.iloc[-1] / history.iloc[0] - 1
momentum = momentum.dropna()
if momentum.empty:
continue
selected = momentum.sort_values(
ascending=False
).head(HOLD_NUM).index.tolist()
signals[date] = selected
for date, selected in list(signals.items())[:5]:
print(date, selected)
6. 计算组合回测收益
portfolio_returns = []
signal_dates = list(signals.keys())
for i in range(len(signal_dates) - 1):
signal_date = signal_dates[i]
next_signal_date = signal_dates[i + 1]
selected = signals[signal_date]
if not selected:
continue
period_prices = close_table.loc[
signal_date:next_signal_date,
selected
]
if period_prices.empty:
continue
period_returns = period_prices.pct_change()
# 等权组合
portfolio_daily_return = period_returns.mean(
axis=1,
skipna=True
)
portfolio_returns.append(
portfolio_daily_return
)
if portfolio_returns:
strategy_returns = pd.concat(portfolio_returns)
strategy_returns = strategy_returns[
~strategy_returns.index.duplicated()
].sort_index()
equity = (1 + strategy_returns.fillna(0)).cumprod()
total_return = equity.iloc[-1] - 1
years = len(strategy_returns) / 252
if years > 0:
annual_return = (
(equity.iloc[-1] ** (1 / years)) - 1
)
else:
annual_return = np.nan
max_drawdown = (
equity / equity.cummax() - 1
).min()
print("========== 回测结果 ==========")
print(f"累计收益:{total_return:.2%}")
print(f"年化收益:{annual_return:.2%}")
print(f"最大回撤:{max_drawdown:.2%}")
这才是我比较喜欢的量化代码结构:
数据获取 → 信号 → 持仓 → 收益 → 指标。
每一层职责都比较清楚。
四、交易员避坑指南:小市值策略最容易在哪里翻车?
1. 千万别把未来数据带进选股
这是回测第一大坑。
比如你用月底数据选股,那么:
月底收盘之后才能知道排名。
你不能拿月底收盘价格算完排名之后,又假设自己在当天开盘价成交。
正确做法应该是:
T 日计算信号,T+1 日执行。
否则回测收益往往会漂亮得不像真的。
2. 前复权不是“万能正确答案”
QuantDash 默认支持:
adjust="forward"
前复权比例方式适合很多收益率计算场景。
但具体研究时还是要知道自己在干什么。
如果你研究的是:
- 收益率;
- 趋势;
- 动量;
前复权通常比较方便。
如果你研究的是:
- 绝对价格;
- 分红除权影响;
- 某些事件前后的真实价格关系;
就应该根据研究目标考虑 none、backward 或差值复权。
3. 小市值策略不能只看收益率
这是社区里非常容易被忽略的问题。
一个回测年化 80% 的策略,如果:
- 最大回撤 60%;
- 换手极高;
- 滑点巨大;
- 小盘股成交困难;
那实盘体验可能完全不是一回事。
所以我建议至少同时看:
年化收益 + 最大回撤 + 换手率 + 单笔交易成本。
五、常见问题解答(Q&A / FAQ)
Q1:A 股小市值轮动策略可以直接用 Python 回测吗?
可以。
核心流程就是:
准备小市值候选池 → QuantDash 获取历史 K 线 → Pandas 计算排序 → 定期调仓 → 计算组合收益。
QuantDash 原生返回 Pandas DataFrame,比较适合直接接入 Python 量化研究流程。
官方 Python SDK 文档:
https://docs.quantdash.net/
Q2:QuantDash 能直接返回 A 股全部股票吗?
可以使用 A 股股票标的池:
df = qd.quotes.get(
universes=["CN_Stock"],
to_dataframe=True
)
不过需要注意:
“获取全市场行情”不等于“官方接口提供历史市值排名”。
如果你的策略严格依赖历史市值,需要额外准备符合研究要求的市值数据,再与 QuantDash 的 K 线进行合并。
Q3:QuantDash 为什么适合 Python 小市值策略回测?
我比较看重三个点:
- 支持批量 K 线;
- 支持前复权;
- 原生 Pandas DataFrame。
这样策略代码可以专注于研究,而不是天天和数据格式搏斗。
🔗 相关资源与延伸阅读
🚀 QuantDash 官网:https://quantdash.net/
📖 官方 Python SDK 文档:https://docs.quantdash.net/
⭐ GitHub 开源仓库:https://github.com/quantdash-net/QuantDash
💡 API Key:https://quantdash.net/dashboard/keys/
如果觉得这个 Python SDK 对自己的量化研究有帮助,也可以去 GitHub 项目看看,欢迎 Star / Fork。

