news 2026/10/22 1:59:54

用 Python 搭建自选股每日涨跌幅与成交量异动邮件推送系统(QuantDash 极简实战)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
用 Python 搭建自选股每日涨跌幅与成交量异动邮件推送系统(QuantDash 极简实战)

📌 摘要 / 快速解答

本文提供一套可直接运行的 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()

代码核心要点:

  1. 批量数据获取:qd.klines.batch()一次请求获取多只股票的历史 K 线,返回Dict[symbol, DataFrame]结构。
  2. 服务器端前复权:设置adjust="forward"后,返回的open/high/low/close已是前复权价格,无需本地计算。
  3. 统一代码格式:A股使用.SH/.SZ/.BJ,美股使用.US,港股使用.HK,一套逻辑覆盖多市场。
  4. 异动判断:基于最新两个交易日的数据计算涨跌幅和成交量变化率,阈值可自定义。

四、 性能优化与量化进阶避坑指南

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 和数据接口说明。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/22 1:58:31

C++ | 红黑树实现

&#x1f98c;云深麋鹿专栏&#xff1a;C | 用C语言学数据结构 | Java回顾&#xff1a;上一篇我们结束了 AVLTree 的相关介绍&#xff0c;接下来这篇文章让我们进入到 红黑树 的学习&#xff0c;体会新的设计思路吧~ 放个目录前言一 红黑树的概念1.1 红黑树的规则1.2 红黑树如何…

作者头像 李华
网站建设 2026/10/22 1:57:43

【例 2】暗的连锁(信息学奥赛一本通- P1553)

一、 题目分析给定一张由N个节点构成的无向图。图中包含N-1条“主要边”构成了一棵生成树&#xff0c;另外还有M条“附加边”&#xff08;非树边&#xff09;。操作规则&#xff1a;必须且只能切断1条主要边和1条附加边。目标&#xff1a;求出有多少种切断方案&#xff0c;使得…

作者头像 李华