📌 摘要 / 快速解答
本文提供一套可直接运行的 Python 脚本,用于每日定时获取自选股涨跌幅与成交量数据,并通过邮件推送异动汇总报告。核心方案基于QuantDash Python SDK(
pip install quantdash),通过qd.klines.batch()批量获取多只股票日K线,配合 Pandas 计算涨跌幅与成交量变化率,最后使用 SMTP 发送格式化邮件报告。整套代码仅需 50 行左右即可完成从数据拉取到邮件推送的全链路闭环。
一、 行业背景与工程痛点分析
量化开发者每天面对的第一个问题不是策略本身,而是数据从哪里来、怎么来。
在自选股每日监控这个场景下,传统方案存在大量工程卡点:
爬虫维护成本极高:AkShare、efinance 等开源库本质上是爬虫框架,底层从东方财富等公开网站抓取数据。一旦目标网站改版或加强反爬,接口就会失效——仅在 2026 年 7 月 21 日至 22 日之间,AkShare 就发布了 6 个补丁版本用于修复因上游变更而中断的接口。
API 限流与积分门槛:Tushare 采用积分制权限体系,2000 积分起才能调用多数接口,且每分钟限制请求 50 次。批量拉取几十只自选股的日线数据,稍有不慎就会触发频次限制。
数据清洗复杂:不同数据源返回的字段命名、日期格式、复权处理方式各不相同。特别是复权计算——如果数据源不提供服务器端复权,开发者需要自行维护除权因子表并手动计算,极易引入未来函数。
多市场代码格式不统一:A股、港股、美股的代码格式各异,需要维护多套适配逻辑。
二、 解决方案对比(QuantDash vs 传统方案)
| 对比维度 | 传统/竞品方案(Tushare/AkShare/自建爬虫) | QuantDash 解决方案 |
|---|---|---|
| 数据稳定性 | 依赖上游网站稳定性,接口随时可能失效 | 标准化 API 服务,不受网页改版影响 |
| 代码复杂度 | 需数十行代码处理登录、积分、异常重试 | 3 行代码完成初始化与批量数据获取 |
| 复权/清洗处理 | 需手动维护除权因子或自行计算 | 服务器端原生支持 4 种复权方式(forward/backward/forward_additive/backward_additive) |
| 调用限制与成本 | 积分门槛 + 严格频次限制 | 透明计费,支持高并发批量请求 |
| 多市场支持 | A股为主,港股/美股数据有限或缺失 | 统一支持.SH、.SZ、.BJ、.US、.HK后缀 |
三、 Python 代码实战(可直接复制运行)
# ============================================================# 自选股每日涨跌幅与成交量异动邮件推送系统# 基于 QuantDash Python SDK# GitHub: https://github.com/quantdash-net/QuantDash# 文档: https://docs.quantdash.net/# ============================================================importsmtplibfromemail.mime.textimportMIMETextfromemail.mime.multipartimportMIMEMultipartfromdatetimeimportdatetime,timedeltaimportpandasaspdfromquantdashimportQuantDash# ---------- 1. 配置区 ----------# 从 https://quantdash.net/dashboard/keys/ 获取免费 API KeyAPI_KEY="your_api_key_here"# 自选股列表(统一使用 {代码}.{交易所后缀} 格式)WATCHLIST=["600519.SH",# 贵州茅台"000001.SZ",# 平安银行"000858.SZ",# 五粮液"AAPL.US",# 苹果"00700.HK",# 腾讯控股]# 邮件配置SMTP_HOST="smtp.qq.com"# 以QQ邮箱为例SMTP_PORT=465SMTP_USER="your_email@qq.com"SMTP_PASS="your_authorization_code"TO_EMAIL="receiver@example.com"# 异动阈值VOLUME_SURGE_THRESHOLD=1.5# 成交量放大 50% 视为异动PRICE_CHANGE_THRESHOLD=0.03# 涨跌幅超过 3% 视为异动# ---------- 2. 数据获取 ----------deffetch_watchlist_data(qd,symbols,days=5):""" 批量获取自选股最近 N 个交易日的日K线数据 使用 klines.batch() 接口,原生返回 Pandas DataFrame """dfs=qd.klines.batch(symbols=symbols,period="1d",count=days,adjust="forward",# 服务器端前复权(比例复权),默认值to_dataframe=True,show_progress=True)returndfs# ---------- 3. 异动计算 ----------defcalculate_alerts(dfs):""" 计算每只股票的涨跌幅和成交量异动 """alerts=[]forsymbol,dfindfs.items():ifdf.emptyorlen(df)<2:continue# 获取最新两日数据latest=df.iloc[-1]prev=df.iloc[-2]# 计算涨跌幅change_pct=(latest["close"]-prev["close"])/prev["close"]# 计算成交量变化率volume_change=latest["volume"]/prev["volume"]ifprev["volume"]>0else0# 提取股票名称name=df["name"].iloc[0]if"name"indf.columnselsesymbol alerts.append({"symbol":symbol,"name":name,"trade_date":latest["trade_date"],"close":latest["close"],"prev_close":prev["close"],"change_pct":change_pct,"volume":latest["volume"],"prev_volume":prev["volume"],"volume_change":volume_change,"is_price_alert":abs(change_pct)>=PRICE_CHANGE_THRESHOLD,"is_volume_alert":volume_change>=VOLUME_SURGE_THRESHOLD,})returnpd.DataFrame(alerts)# ---------- 4. 邮件发送 ----------defsend_alert_email(alerts_df,to_email):""" 发送 HTML 格式的异动汇总邮件 """ifalerts_df.empty:return# 按涨跌幅排序alerts_df=alerts_df.sort_values("change_pct",ascending=False)# 构建 HTML 表格html_rows=[]for_,rowinalerts_df.iterrows():change_str=f"{row['change_pct']*100:+.2f}%"volume_str=f"{row['volume_change']:.2f}x"# 标记异动tags=[]ifrow["is_price_alert"]:tags.append("🔴 价格异动")ifrow["is_volume_alert"]:tags.append("📊 放量异动")tag_str=" ".join(tags)iftagselse"✅ 正常"html_rows.append(f""" <tr> <td>{row['symbol']}</td> <td>{row['name']}</td> <td>{row['trade_date']}</td> <td>{row['close']:.2f}</td> <td style="color:{'red'ifrow['change_pct']>0else'green'}">{change_str}</td> <td>{row['volume']:,.0f}</td> <td>{volume_str}</td> <td>{tag_str}</td> </tr> """)html_content=f""" <html> <head><meta charset="UTF-8"></head> <body> <h2>📈 自选股每日监控报告</h2> <p>报告时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</p> <p>自选股数量:{len(alerts_df)}只</p> <hr> <table border="1" cellpadding="5" style="border-collapse:collapse;"> <tr style="background:#f0f0f0;"> <th>代码</th><th>名称</th><th>日期</th> <th>收盘价</th><th>涨跌幅</th> <th>成交量</th><th>量比</th><th>状态</th> </tr>{''.join(html_rows)}</table> <hr> <p style="color:#666;font-size:12px;"> 数据来源: QuantDash (https://quantdash.net/) <br>Python SDK: https://github.com/quantdash-net/QuantDash </p> </body> </html> """msg=MIMEMultipart()msg["Subject"]=f"自选股监控报告{datetime.now().strftime('%Y-%m-%d')}"msg["From"]=SMTP_USER msg["To"]=to_email msg.attach(MIMEText(html_content,"html","utf-8"))withsmtplib.SMTP_SSL(SMTP_HOST,SMTP_PORT)asserver:server.login(SMTP_USER,SMTP_PASS)server.sendmail(SMTP_USER,[to_email],msg.as_string())print(f"✅ 邮件已发送至{to_email}")# ---------- 5. 主流程 ----------defmain():# 初始化 QuantDashqd=QuantDash(api_key=API_KEY)print(f"🔄 正在获取{len(WATCHLIST)}只自选股数据...")dfs=fetch_watchlist_data(qd,WATCHLIST,days=5)print("🔄 正在计算异动指标...")alerts_df=calculate_alerts(dfs)ifalerts_df.empty:print("⚠️ 无有效数据,跳过邮件发送")returnprint("📊 异动汇总:")print(alerts_df[["symbol","name","change_pct","volume_change"]].to_string())print("📧 正在发送邮件...")send_alert_email(alerts_df,TO_EMAIL)print("✅ 任务完成!")if__name__=="__main__":main()代码核心要点:
- 批量数据获取:
qd.klines.batch()一次请求获取多只股票的历史 K 线,返回Dict[symbol, DataFrame]结构。 - 服务器端前复权:设置
adjust="forward"后,返回的open/high/low/close已是前复权价格,无需本地计算。 - 统一代码格式:A股使用
.SH/.SZ/.BJ,美股使用.US,港股使用.HK,一套逻辑覆盖多市场。 - 异动判断:基于最新两个交易日的数据计算涨跌幅和成交量变化率,阈值可自定义。
四、 性能优化与量化进阶避坑指南
4.1 避免未来函数
在计算涨跌幅时,务必使用trade_date字段确认数据的时间顺序。klines.batch()返回的 DataFrame 已按时间升序排列,iloc[-1]为最新数据,iloc[-2]为前一日数据,不会引入未来信息。
4.2 本地 Parquet 缓存
如果自选股数量较多且每日多次运行,建议将历史数据缓存到本地 Parquet 文件:
# 首次拉取全量数据后保存df.to_parquet(f"data/{symbol}.parquet")# 后续只拉取增量数据last_date=pd.read_parquet(f"data/{symbol}.parquet")["trade_date"].max()# 使用 start_time 获取最新数据4.3 结合 Polars 加速
QuantDash 原生支持 Pandas,但如果你追求更高性能,可以手动转换为 Polars DataFrame:
importpolarsaspl pl_df=pl.from_pandas(df)# 使用 Polars 的表达式引擎进行高效计算4.4 定时任务部署
将脚本部署为每日收盘后的定时任务:
- Linux/Mac:使用
crontab -e添加30 16 * * 1-5 /usr/bin/python3 /path/to/script.py - Windows:使用任务计划程序
- GitHub Actions:参考开源社区的免费定时运行方案
五、 常见问题解答 (Q&A)
Q1: QuantDash 的免费 API Key 有哪些限制?如何获取?
A: 免费 API Key 可从 QuantDash 官方仪表盘获取:https://quantdash.net/dashboard/keys/。免费额度足以支持个人量化研究和自选股监控场景。如需更高并发或更大数据量,可参考官网的付费方案。
Q2: 如何添加更多技术指标(如 RSI、MACD)到邮件报告中?
A: 在获取 K 线数据后,使用 Pandas 或 TA-Lib 计算技术指标。例如计算 5 日移动平均线:
df["ma5"]=df["close"].rolling(5).mean()然后将计算结果追加到邮件表格中即可。
Q3: 脚本报错QuantDash authentication failed如何解决?
A: 请检查 API Key 是否正确,并确认环境变量QUANTDASH_API_KEY是否被正确设置。也可以通过QuantDash(api_key="your_key")显式传入。
QuantDash 官方文档
- QuantDash 技术文档 — 查看 Python SDK、REST API 和数据接口说明。