脚本功能介绍这是一个A股概念板块监控 邮件推送自动化脚本核心功能是找出1年内创新低的概念板块统计这些板块中近1个月涨停次数最多的股票生成美化 HTML 报告并发送到指定邮箱一、核心流程5 步text① 拉取全部概念板块 ↓ ② 筛选1年内创新低的概念 ↓ ③ 加载全市场市值快照 ↓ ④ 统计创新低概念中成分股的近1月涨停次数取每概念前3 ↓ ⑤ 生成美化 HTML 邮件发送到多个收件人二、逐项功能说明1. 概念板块全量获取通过ak.stock_board_concept_name_em()拉取全部概念板块数百个支持测试模式TEST_MODE True时只取前 10 个快速验证链路2. 1年内创新低判定对每个概念板块拉取近 400 天日K前复权判定逻辑最新收盘价 ≤ 近 250 个交易日最低收盘价输出创新低概念列表 各自最新收盘点位3. 全市场市值快照批量提速用ak.stock_zh_a_spot_em()一次性拉全市场行情构建{股票代码: 流通市值(亿)}映射避免逐只查市值接口速度大幅提升缓存复用全流程只拉一次4. 个股涨停统计对创新低概念的每只成分股拉取近 45 天日K统计最近 30 个自然日内的涨停次数主板涨跌幅 ≥ 9.5%创业板/科创板300/301/688/689≥ 19.5%同时获取最新收盘价、今日涨幅、流通市值每个概念按涨停次数降序取前 3 只5. 邮件美化与发送生成 HTML 邮件含顶部渐变标题 生成时间4 个摘要卡片创新低概念数、含涨停股概念数、上榜股票数、⭐500亿市值股数图例说明分组表格同一概念的股票合并概念单元格底部风险提示支持多收件人逐个发送失败重试指数退避收件人之间互不可见三、报告字段字段说明概念板块创新低的概念名称 板块收盘点位股票代码6 位代码股票名称名称⭐ 前缀表示流通市值 ≥ 500 亿目前股价前复权最新收盘价今日涨幅最近交易日涨跌幅红涨绿跌带/-流通市值(亿)≥500 亿的紫色加粗标记近1月涨停次数近 30 自然日涨停次数最近涨停日最近一次涨停日期四、配置项配置说明SMTP_HOST / SMTP_PORT发件邮箱 SMTPQQ/163 等FROM_ADDR发件邮箱AUTH_CODE授权码支持环境变量MAIL_AUTH_CODE覆盖TO_ADDRS收件人列表支持环境变量MAIL_TO逗号分隔覆盖TEST_MODETrue 只取前10概念快速验证MAIL_RETRY单封邮件失败重试次数BIG_CAP_THRESHOLD大市值阈值默认 500 亿五、技术特性特性实现并发拉取板块筛选 6 线程、个股统计 8 线程失败重试数据接口 3 次重试邮件 2 次重试 退避缓存优化市值快照全局缓存只拉一次多收件人逐个单独发送隐私好、互不影响美化输出HTML 内联 CSS渐变标题、卡片、分组表格、红涨绿跌、标记高亮进度可见终端逐概念打印完成进度容错单只股票/单个板块失败不影响整体停牌处理无数据显示-不参与标记六、适用场景收盘复盘每个交易日收盘后跑一次邮件推送弱势板块中的强势股超跌反弹观察创新低板块 有涨停股可能是资金试探性介入大市值关注⭐ 标记帮你在小盘异动中快速识别大市值标的定时任务配合 cron 每个交易日 15:30 自动运行七、数据口径说明项目口径复权前复权qfq创新低最新收盘 ≤ 近250交易日最低收盘涨停按代码前缀区分 9.5% / 19.5% 阈值ST 5%、北交所 30% 未单独处理今日涨幅盘中延迟快照收盘后收盘涨幅市值流通市值元转亿元八、使用方式# 安装依赖 pip3 install --upgrade akshare pandas # 测试模式快速验证 sed -i s/TEST_MODE False/TEST_MODE True/ concept_low_and_limit.py python3 concept_low_and_limit.py # 全量运行 sed -i s/TEST_MODE True/TEST_MODE False/ concept_low_and_limit.py python3 concept_low_and_limit.py # 多收件人 环境变量授权码 export MAIL_TOa163.com,bqq.com export MAIL_AUTH_CODE新授权码 python3 concept_low_and_limit.py九、局限与注意非实时日K接口有延迟定位是收盘复盘涨停判定简化ST5%、北交所30%未单独处理可能误判耗时全量约 5~15 分钟取决于概念数量和接口速度接口变动AKShare 字段可能随版本变化报错先升级邮件限制QQ 邮箱日发信量有限收件人别太多不构成投资建议创新低≠买入涨停多≠必涨十、脚本AUTH_CODE os.getenv(MAIL_AUTH_CODE, 授权码换自己的)FROM_ADDR 自己的发信箱TO_ADDRS 列表里邮箱改成自己的收件箱#!/usr/bin/env python3 # -*- coding: utf-8 -*- import akshare as ak import pandas as pd import smtplib import os import time import traceback from email.mime.text import MIMEText from email.header import Header from email.utils import formataddr from concurrent.futures import ThreadPoolExecutor, as_completed from datetime import datetime, timedelta # 配置 SMTP_HOST smtp.qq.com SMTP_PORT 465 FROM_ADDR 发信箱qq.com AUTH_CODE os.getenv(MAIL_AUTH_CODE, sreztnoovatsbada) _env_to os.getenv(MAIL_TO, ) TO_ADDRS [a.strip() for a in _env_to.split(,) if a.strip()] or [ 收件箱163.com, ] TEST_MODE False MAIL_RETRY 2 BIG_CAP_THRESHOLD 500 # 亿元 # 工具 def retry_call(func, *args, retry3, wait1, **kwargs): for i in range(retry): try: return func(*args, **kwargs) except Exception: if i retry - 1: return None time.sleep(wait) return None # 全市场市值快照一次性 _CAP_CACHE None def get_market_cap_map(): 返回 {code: 流通市值(亿)}一次性拉取失败返回空字典 global _CAP_CACHE if _CAP_CACHE is not None: return _CAP_CACHE df retry_call(ak.stock_zh_a_spot_em) if df is None or df.empty: _CAP_CACHE {} return _CAP_CACHE cap_map {} for _, r in df.iterrows(): code str(r.get(代码, )).zfill(6) cap r.get(流通市值) try: cap_map[code] round(float(cap) / 1e8, 2) if pd.notna(cap) else None except Exception: cap_map[code] None _CAP_CACHE cap_map return cap_map # 1. 全部概念 def get_all_concepts(): df retry_call(ak.stock_board_concept_name_em) if df is None: return [] names df[板块名称].tolist() if TEST_MODE: names names[:10] return names # 2. 板块历史判断1年新低 def board_hist(name): end datetime.now().strftime(%Y%m%d) start (datetime.now() - timedelta(days400)).strftime(%Y%m%d) return retry_call( ak.stock_board_concept_hist_em, symbolname, perioddaily, start_datestart, end_dateend, adjustqfq ) def is_new_low_1y(hist): if hist is None or len(hist) 30: return False, None h hist.sort_values(日期).reset_index(dropTrue) close h[收盘].astype(float) latest_close close.iloc[-1] window close.tail(250) min_close window.min() return latest_close min_close 1e-8, round(float(latest_close), 2) # 3. 板块成分股 def get_cons(board): df retry_call(ak.stock_board_concept_cons_em, symbolboard) return df if df is not None else pd.DataFrame() # 4. 个股近1月涨停次数 最新价 今日涨幅 def stock_limit_count(code): end datetime.now().strftime(%Y%m%d) start (datetime.now() - timedelta(days45)).strftime(%Y%m%d) h retry_call( ak.stock_zh_a_hist, symbolcode, perioddaily, start_datestart, end_dateend, adjustqfq ) if h is None or len(h) 0: return 0, None, None, None h h.sort_values(日期).reset_index(dropTrue) latest h.iloc[-1] latest_close round(float(latest[收盘]), 2) today_pct round(float(latest[涨跌幅]), 2) if 涨跌幅 in h.columns else None cutoff (datetime.now() - timedelta(days30)).strftime(%Y-%m-%d) h2 h[h[日期].astype(str) cutoff] if h2.empty: return 0, None, latest_close, today_pct pct h2[涨跌幅].astype(float) is_20 code.startswith((300, 301, 688, 689)) threshold 19.5 if is_20 else 9.5 limit_days h2[pct threshold] cnt len(limit_days) last_day limit_days[日期].iloc[-1] if cnt 0 else None return cnt, (str(last_day) if last_day is not None else None), latest_close, today_pct # 5. 主流程 def main(): print(① 拉取全部概念板块...) concepts get_all_concepts() print(f 共 {len(concepts)} 个概念 (测试模式 if TEST_MODE else )) print(② 筛选1年内创新低的概念...) low_concepts [] def worker_board(name): h board_hist(name) ok, close is_new_low_1y(h) return name, ok, close with ThreadPoolExecutor(max_workers6) as ex: futs {ex.submit(worker_board, n): n for n in concepts} for f in as_completed(futs): try: name, ok, close f.result() if ok: low_concepts.append((name, close)) except Exception: pass print(f 创新低概念{len(low_concepts)} 个) if not low_concepts: print( 无创新低概念退出) return print(③ 加载全市场市值快照...) cap_map get_market_cap_map() print(f 市值记录{len(cap_map)} 条) print(④ 统计这些概念成分股近1月涨停次数...) result [] for idx, (cname, cclose) in enumerate(low_concepts, 1): cons get_cons(cname) if cons.empty: continue stock_rows [] def worker_stock(row): code str(row.get(代码, )).zfill(6) name str(row.get(名称, )) cnt, last_day, price, today_pct stock_limit_count(code) cap cap_map.get(code) return {code: code, name: name, cnt: cnt, last_day: last_day, price: price, today_pct: today_pct, cap: cap} with ThreadPoolExecutor(max_workers8) as ex: futs [ex.submit(worker_stock, row) for _, row in cons.iterrows()] for f in as_completed(futs): try: stock_rows.append(f.result()) except Exception: pass stock_rows.sort(keylambda x: (x[cnt], x[last_day] or ), reverseTrue) top3 [s for s in stock_rows if s[cnt] 0][:3] for s in top3: result.append({ concept: cname, concept_close: cclose, stock_code: s[code], stock_name: s[name], price: s[price], today_pct: s[today_pct], cap: s[cap], big_cap: (s[cap] is not None and s[cap] BIG_CAP_THRESHOLD), limit_cnt: s[cnt], last_limit_day: s[last_day] or -, }) print(f [{idx}/{len(low_concepts)}] {cname} 完成) if not result: print( 无涨停股票退出) return result.sort(keylambda x: (x[concept], -x[limit_cnt])) print(⑤ 生成美化邮件并发送...) html build_html(result, len(low_concepts)) ok send_mail(【股市监控】概念创新低 近1月涨停统计, html) print( 邮件发送 (成功 if ok else 失败)) # 6. 美化 HTML def build_html(rows, low_count): now datetime.now().strftime(%Y-%m-%d %H:%M:%S) from collections import OrderedDict grouped OrderedDict() for r in rows: grouped.setdefault(r[concept], []).append(r) body_rows for concept, items in grouped.items(): first items[0] span len(items) for i, it in enumerate(items): price_disp f{it[price]:.2f} if it.get(price) is not None else - tp it.get(today_pct) if tp is None: pct_disp, pct_cls -, else: pct_disp f{tp:.2f}% pct_cls up if tp 0 else (down if tp 0 else ) cap it.get(cap) if cap is None: cap_disp, cap_cls -, else: cap_disp f{cap:.0f} cap_cls bigcap if it.get(big_cap) else name_disp it[stock_name] if it.get(big_cap): name_disp f⭐ {name_disp} if i 0: body_rows f tr td rowspan{span} classconcept{concept}brspan classsub收盘 {first[concept_close]}/span/td td{it[stock_code]}/td td classstock{name_disp}/td td classprice{price_disp}/td td class{pct_cls}{pct_disp}/td td class{cap_cls}{cap_disp}/td td classcnt{it[limit_cnt]}/td td{it[last_limit_day]}/td /tr else: body_rows f tr td{it[stock_code]}/td td classstock{name_disp}/td td classprice{price_disp}/td td class{pct_cls}{pct_disp}/td td class{cap_cls}{cap_disp}/td td classcnt{it[limit_cnt]}/td td{it[last_limit_day]}/td /tr html f !DOCTYPE html html head meta charsetutf-8 style body {{ font-family: Microsoft YaHei, Arial, sans-serif; background:#f5f7fa; margin:0; padding:20px; }} .container {{ max-width: 1120px; margin:0 auto; background:#fff; border-radius:10px; overflow:hidden; box-shadow:0 2px 10px rgba(0,0,0,.08); }} .header {{ background:linear-gradient(135deg,#1e3c72,#2a5298); color:#fff; padding:24px 30px; }} .header h1 {{ margin:0; font-size:22px; }} .header p {{ margin:6px 0 0; font-size:13px; opacity:.85; }} .summary {{ display:flex; gap:20px; padding:18px 30px; background:#f0f4ff; border-bottom:1px solid #e0e6f0; flex-wrap:wrap; }} .card {{ flex:1; min-width:150px; background:#fff; border-radius:8px; padding:14px 18px; border-left:4px solid #2a5298; }} .card .num {{ font-size:22px; font-weight:bold; color:#1e3c72; }} .card .label {{ font-size:12px; color:#666; margin-top:4px; }} table {{ width:100%; border-collapse:collapse; font-size:13px; }} thead th {{ background:#1e3c72; color:#fff; padding:10px 8px; text-align:left; font-weight:500; }} tbody td {{ padding:9px 8px; border-bottom:1px solid #eef1f6; vertical-align:top; }} tbody tr:hover {{ background:#f7f9ff; }} .concept {{ font-weight:bold; color:#1e3c72; background:#f4f7ff; }} .sub {{ font-weight:normal; color:#888; font-size:11px; }} .stock {{ color:#c0392b; font-weight:500; }} .price {{ color:#2c3e50; font-weight:500; }} .up {{ color:#c0392b; font-weight:500; }} .down {{ color:#27ae60; font-weight:500; }} .bigcap {{ color:#8e44ad; font-weight:bold; background:#f5eef8; text-align:center; }} .cnt {{ color:#d35400; font-weight:bold; text-align:center; }} .footer {{ padding:16px 30px; font-size:12px; color:#999; text-align:center; border-top:1px solid #eee; }} .warn {{ color:#c0392b; }} .legend {{ padding:10px 30px; font-size:12px; color:#666; background:#fafbfd; }} /style /head body div classcontainer div classheader h1 概念板块创新低监控报告/h1 p生成时间{now}/p /div div classsummary div classcarddiv classnum{low_count}/divdiv classlabel1年内创新低概念/div/div div classcarddiv classnum{len(set(r[concept] for r in rows))}/divdiv classlabel含涨停股的概念/div/div div classcarddiv classnum{len(rows)}/divdiv classlabel上榜强势股(前3)/div/div div classcarddiv classnum{sum(1 for r in rows if r.get(big_cap))}/divdiv classlabel⭐ 流通市值≥500亿/div/div /div div classlegend图例⭐ 紫色市值 流通市值 ≥ {BIG_CAP_THRESHOLD} 亿红色涨幅 上涨绿色 下跌/div table thead tr th stylewidth:18%概念板块/th th stylewidth:10%股票代码/th th stylewidth:16%股票名称/th th stylewidth:10%目前股价/th th stylewidth:10%今日涨幅/th th stylewidth:11%流通市值(亿)/th th stylewidth:11%近1月涨停次数/th th stylewidth:14%最近涨停日/th /tr /thead tbody {body_rows} /tbody /table div classfooter p classwarn⚠ 本报告由程序自动生成数据来自公开接口仅供参考不构成投资建议。/p p创新低 ≠ 买入信号涨停次数多 ≠ 一定上涨请结合基本面与量能判断。/p /div /div /body /html return html # 7. 发送邮件多收件人 重试 def send_mail(subject, html_content): success, failed 0, [] for addr in TO_ADDRS: sent False last_err None for attempt in range(1, MAIL_RETRY 2): try: msg MIMEText(html_content, html, utf-8) msg[From] formataddr((股市监控, FROM_ADDR)) msg[To] addr msg[Subject] Header(subject, utf-8) with smtplib.SMTP_SSL(SMTP_HOST, SMTP_PORT) as server: server.login(FROM_ADDR, AUTH_CODE) server.sendmail(FROM_ADDR, [addr], msg.as_string()) sent True break except Exception as e: last_err e if attempt MAIL_RETRY: time.sleep(2 * attempt) if sent: success 1 print(f ✅ 已发送: {addr}) else: failed.append(addr) print(f ❌ 发送失败: {addr} - {last_err}) time.sleep(0.5) print(f 发送完成成功 {success} 个失败 {len(failed)} 个) if failed: print(f 失败列表: {failed}) return success 0 if __name__ __main__: main()十一、效果截图
企业数字化 ERP 产品动态
相关推荐
C++转Java日记(01) 为什么转Java?
因为博主最近想找一段实习,但是博主的背景是只做过游戏开发,会用unity,此外就只会一点C算法竞赛的内容了,其它诸如Java和Go,那是一点不会。而现在我能够投的实习,不少都是后端开… · 2026/11/5 7:59:54
Claude Code 升级后命令行为变了?先用 changelog 和最小项目定位差异 Claude Code 升级后命令行为变了?先用 changelog 和最小项目定位差异
当 Claude Code 升级后权限提示、命令参数或 Hook 行为看起来不同,先不要在正式仓库连续试错。官方 changelog 是确认变化范围的第一入口;然后用一个没有业务密钥的最小目录验证命令和输出。
排查步骤 … · 2026/11/5 7:59:54
Aspen HYSYS 二次开发系列教程(3):VBA 环境启用与第一个宏——默认禁用不是 bug Aspen HYSYS 二次开发系列教程(3):VBA 环境启用与第一个宏——默认禁用不是 bug版本声明块
软件:aspenONE V15(基线,同第 1 篇);文档现实:Customization Guide V7.3&… · 2026/11/5 7:59:54
GitHub+Arbess实现Java项目CI/CD自动化部署实战 1. 项目背景与核心价值 最近在团队内部落地了一套基于GitHub的Java项目自动化构建部署方案,用Arbess工具链实现了从代码提交到Docker容器部署的完整流水线。这套方案特别适合中小型团队快速搭建轻量级CI/CD环境,相比传统Jenkins方案节省了60%的运维成本。… · 2026/11/5 8:34:15
最小费用最大流算法在运输网络优化中的应用 1. 题目背景与问题解析 UVa 1486 "Transportation"是算法竞赛中的一道经典网络流题目,出自ICPC世界总决赛题库。这道题考察的是最小费用最大流(Minimum Cost Maximum Flow, MCMF)算法的应用能力,属于中等偏难级别的图论… · 2026/11/5 8:34:15
mDNS安装配置与服务发布实战:.local解析、组播与故障排查 局域网里那点事,说简单也简单,说烦人也是真烦人。你手里一台常开的机器跑着几个自建服务,手机想连、笔记本想连、电视盒子偶尔也想连一下,结果每次都要去路由器后台翻 IP,或者干脆在纸上记个192.168.1.37——过两天 DH… · 2026/11/5 8:34:15
Dijkstra算法实现单源最短路径问题解析 1. 题目背景与需求分析单源最短路径(Single-Source Shortest Path,SSSP)是图论中的经典问题,也是算法竞赛中的高频考点。洛谷P3371作为该问题的模板题,主要考察对Dijkstra算法或SPFA算法的实现能力。题目给定一个包含n… · 2026/11/5 8:34:15
Z-BlogPHP免登陆发布插件:API鉴权与自动化发布实战 1. 先把场景说透:免登陆发布到底在解决什么问题做站久了的人都有一个共同的疲惫感:后台编辑器打开、标题粘进去、正文粘进去、摘要再手敲一遍、标签一个个敲、分类下拉框选一下、封面图重新上传、发布时间手动调……一篇文章发下来,手指在鼠标和键盘之间来回跳了十几趟。如果只… · 2026/11/5 8:34:15
预期功能安全SOTIF:无故障车辆为何仍会撞人? 简介:面向智能网联汽车功能安全从业者、系统与软件工程师及高校相关专业学习者,这份资料提供预期功能安全(SOTIF)领域的核心标准文本,重点解决自动驾驶与辅助驾驶系统在无故障情况下因性能局限、感知偏差和算法约束而引… · 2026/11/5 8:33:14
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 3:16:49
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 6:59:29
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 7:02:38
多智能体协作系统架构设计与实践优化 1. 多智能体协作系统的时代背景2017年Transformer架构的诞生彻底改变了人工智能的发展轨迹。当行业还沉浸在单一大模型带来的性能突破时,Anthropic的研究团队已经将目光投向了更前沿的领域——如何让多个智能体通过协作产生超越单体能力的系统智能。这就像交响乐团中… · 2026/11/5 0:00:39
大模型系统性入门实战指南:从原理到部署微调的全链路路线 提到大模型的系统性入门,我先说个结论:这事真不难,难的是资料太散、路线太多,今天这个框架明天那个工具,新人很容易在“收藏夹吃灰”和“教程看到一半放弃”之间反复横跳。市面上关于大模型的内容少说有几十万篇&#… · 2026/11/5 0:00:39
鸿蒙文件系统沙箱机制解析与开发实践 1. 鸿蒙文件系统沙箱机制深度解析作为一名从Android转向鸿蒙开发的工程师,我深刻体会到文件系统访问是迁移过程中最先遇到的"拦路虎"。在Android上运行良好的文件读写代码,在鸿蒙真机环境却频频出现文件写入后神秘消失或直接抛出权限拒绝错误的… · 2026/11/5 0:00:39
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 3:16:49
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 6:59:29
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 7:02:38