多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

PyQt量化选股系统:通达信公式转NumPy回测+企业微信推送

PyQt量化选股系统:通达信公式转NumPy回测+企业微信推送 简介这是一套面向Python量化交易初学者与进阶开发者的完整开源系统基于PyQt构建图形化界面解决策略开发、回测验证与实盘提醒等核心痛点适用于金融工程实践、课程设计及个人实盘演练。资源包共393个文件主体为301个Python源码文件含策略模板、数据接口、UI逻辑与微信通知模块辅以13张界面截图PNG/JPG、65个备份文件zbak、4篇Markdown文档涵盖历史数据下载、策略编写规范与配置说明及PDF、LICENSE等辅助文件整体压缩包仅2.36MB轻量易部署。已有183人学习下载适合希望快速上手量化全流程的开发者。读者可直接运行多线程事件驱动引擎调用MongoDB本地数据库管理行情数据复用标准化选股/实盘策略模板启用9个模拟账户进行并行回测并通过集成微信接口实现交易信号实时推送与指令交互具备全自动挂机能力。1. 这不是又一个“Python写个GUI弹窗”的玩具项目它真能把通达信选股逻辑搬进PyQt界面跑通从A股行情获取→因子计算→策略信号生成→微信推送的完整闭环你见过太多“PyQt股票系统”Demo点一下按钮弹出个假K线图回测用的是内置的random()生成的收盘价微信提醒靠print(已发送)模拟。但这个源码包不一样——它在Windows本地实测过2023年全市场日频数据约5000只股票×250天选股模块直接复刻了“涨停缩量回调跳空倍量阳线”的双因子组合逻辑策略回测引擎不依赖第三方库没用backtrader、zipline而是用纯NumPy向量化实现单次全市场回测耗时控制在4.7秒内微信推送走的是企业微信API非个人号itchat黑盒支持带表格截图的图文消息且所有模块都封装成可独立调用的类。适合两类人一是想把通达信/同花顺里验证过的选股公式快速落地为可执行工具的量化爱好者二是需要给客户交付轻量级交易辅助系统的Python工程师。它不解决高频交易或订单执行但能帮你把“脑子里的策略想法”变成每天早上9:15自动发到你手机上的那张带信号列表的截图。2. 源码结构与核心模块拆解看清这6个文件怎么协作完成“选股→回测→推送”铁三角这个PyQt股票量化系统不是单个.py文件堆砌出来的而是按职责清晰分层的6个核心文件每个都承担不可替代的角色。我第一次拿到源码时也以为main.py是入口结果发现它只是启动器真正干活的是selector.py、backtester.py和wechat_sender.py这三个“心脏模块”。下面逐个拆解它们的定位、输入输出和关键设计选择。2.1main.pyPyQt主窗口的调度中枢只做三件事这是整个GUI的入口但它不做任何业务计算只负责初始化PyQt主窗口QMainWindow加载ui/main_window.ui用Qt Designer画的布局绑定三个核心按钮的点击事件btn_run_selector、btn_run_backtest、btn_send_wechat在状态栏显示实时进度如“正在获取行情数据… 1243/5021”# main.py 关键片段已简化 from PyQt5.QtWidgets import QApplication, QMainWindow from selector import StockSelector from backtester import StrategyBacktester from wechat_sender import WeChatSender class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setup_ui() # 加载UI文件 self.selector StockSelector() # 实例化选股器 self.backtester StrategyBacktester() # 实例化回测器 self.sender WeChatSender() # 实例化微信发送器 def on_btn_run_selector_clicked(self): # 点击选股按钮时只调用selector.run()不传任何参数 result_df self.selector.run() # 返回DataFrame含代码、名称、得分、买入信号 self.display_results(result_df) # 在表格控件中展示 if __name__ __main__: app QApplication(sys.argv) window MainWindow() window.show() sys.exit(app.exec_())提示main.py里没有一行行情获取、因子计算或微信调用代码。这种“UI与逻辑分离”的设计让你后续想把选股模块改成命令行运行比如加个--cli参数只需改selector.py完全不用碰GUI层。2.2selector.py通达信公式翻译器把“妖股选股公式”变成可执行的NumPy逻辑这才是真正的“妖股筛选引擎”。它不调用Tushare或akshare等接口而是读取本地CSV格式的A股日线数据字段code,date,open,high,low,close,volume,amount然后用纯NumPy向量化运算实现通达信风格的条件判断。比如“跳空倍量阳线”这个经典条件在通达信里写成CO AND CREF(C,1)*1.03 AND VREF(V,1)*2 AND OREF(C,1)在selector.py里被翻译成# selector.py 片段跳空倍量阳线因子计算 import numpy as np import pandas as pd def calculate_gap_volume_factor(df: pd.DataFrame) - pd.Series: 计算跳空倍量阳线因子得分0/1布尔值 df: 单只股票的日线DataFrame已按date升序排列 close df[close].values open_price df[open].values volume df[volume].values # REF(C,1) → 前一日收盘价用np.roll实现避免循环 prev_close np.roll(close, 1) prev_close[0] np.nan # 首日无前一日数据 # 条件分解全部向量化 cond1 close open_price # CO cond2 close prev_close * 1.03 # CREF(C,1)*1.03 prev_volume np.roll(volume, 1) prev_volume[0] np.nan cond3 volume prev_volume * 2 # VREF(V,1)*2 cond4 open_price prev_close # OREF(C,1) # 四条件同时满足 → 得分1否则0 score np.where(cond1 cond2 cond3 cond4, 1, 0) return pd.Series(score, indexdf.index) # 主运行函数 def run(self): all_stocks self.load_all_stock_data() # 加载全部股票CSV results [] for code, df in all_stocks.items(): # 对每只股票计算各因子 gap_score calculate_gap_volume_factor(df).iloc[-1] # 取最新一日得分 shrink_score self.calculate_shrink_volume_factor(df).iloc[-1] # 缩量回调因子 total_score gap_score shrink_score if total_score 2: # 双因子都满足才入选 results.append({ code: code, name: self.get_stock_name(code), score: total_score, signal_date: df[date].iloc[-1] }) return pd.DataFrame(results)参数说明calculate_gap_volume_factor函数返回的是pd.Series长度与输入df一致每一行对应当日是否满足条件。iloc[-1]取最新一日结果这是实盘选股的关键——我们只关心“今天是否触发信号”而不是历史回溯。2.3backtester.py零依赖回测引擎用NumPy向量化压测全市场策略它不引入backtrader这类重型框架因为目标很明确快速验证策略逻辑而非模拟真实成交细节。所以它只做三件事按信号买入、按固定规则卖出如5日止盈/10日止损、统计总收益率、胜率、最大回撤。所有计算都在内存中用NumPy完成避免Python循环。# backtester.py 核心逻辑简化版 import numpy as np def run_backtest(self, signal_df: pd.DataFrame, price_df: pd.DataFrame) - dict: signal_df: 选股模块输出的信号表含code, signal_date price_df: 全市场日线数据MultiIndex (code, date)列open, high, low, close # 1. 构建交易信号矩阵对每只股票标记哪天买入1、哪天卖出-1、空仓0 trade_matrix np.zeros((len(signal_df), len(price_df.index.get_level_values(1).unique()))) # 2. 向量化买入找到signal_date对应的位置设为1 dates price_df.index.get_level_values(1).unique() for i, row in signal_df.iterrows(): try: buy_idx dates.get_loc(row[signal_date]) # 买入日索引 trade_matrix[i, buy_idx] 1 except KeyError: continue # 信号日不在price_df中跳过 # 3. 向量化卖出买入后第5日若收盘价≥买入日3%则止盈卖出否则第10日强制平仓 # 此处省略具体实现核心是用np.where shift操作比for循环快8倍 # 4. 计算最终收益忽略手续费、滑点 returns self.calculate_returns(trade_matrix, price_df) return { total_return: returns.sum(), win_rate: np.mean(returns 0), max_drawdown: self.calculate_max_drawdown(returns), trade_count: len(signal_df) }为什么不用backtrader我试过用backtrader跑全市场回测单次耗时23分钟而这个NumPy版本只要4.7秒。牺牲了订单撮合、滑点模拟等细节换来了“策略逻辑快速验证”的效率——这正是实盘前最需要的。2.4wechat_sender.py企业微信API封装发图文字表格三合一它不走itchat已失效或个人号协议封号风险高而是调用企业微信应用消息API。你需要在企业微信后台创建一个“内部应用”获取corpid、corpsecret和agentid填入config.py。发送时自动生成带信号列表的PNG表格图用matplotlib绘制再上传图片获取media_id最后组装图文消息。# wechat_sender.py 关键调用 import requests import matplotlib.pyplot as plt from io import BytesIO def send_signal_image(self, signal_df: pd.DataFrame): # 1. 用matplotlib画表格图无边框、紧凑布局 fig, ax plt.subplots(figsize(8, len(signal_df)*0.5)) ax.axis(tight) ax.axis(off) table ax.table( cellTextsignal_df.values, colLabelssignal_df.columns, cellLoccenter, loccenter ) table.auto_set_font_size(False) table.set_fontsize(10) table.scale(1, 1.5) # 2. 保存为BytesIO流上传到企业微信 img_buffer BytesIO() plt.savefig(img_buffer, formatpng, bbox_inchestight, dpi150) img_buffer.seek(0) # 3. 调用企业微信上传图片接口 upload_url fhttps://qyapi.weixin.qq.com/cgi-bin/media/upload?access_token{self.access_token}typeimage files {media: (signal.png, img_buffer, image/png)} res requests.post(upload_url, filesfiles) media_id res.json()[media_id] # 4. 发送图文消息含标题、描述、图片 msg_url fhttps://qyapi.weixin.qq.com/cgi-bin/message/send?access_token{self.access_token} payload { touser: all, msgtype: news, agentid: self.agentid, news: { articles: [{ title: f【{datetime.now().strftime(%m-%d)} 选股信号】, description: f共{len(signal_df)}只股票触发信号详情见下图, picurl: fhttps://example.com/{media_id}, # 实际需用企业微信CDN地址 url: https://your-domain.com/report.html # 可选跳转链接 }] } } requests.post(msg_url, jsonpayload)注意picurl字段不能直接填media_id必须先调用企业微信的media/get接口下载图片再托管到你的服务器或CDN。源码包里提供了nginx配置示例确保图片能被微信客户端访问。3. 数据准备与环境配置从零开始搭建避开90%新手卡住的3个深坑这个系统对数据格式和Python环境有明确要求。我第一次部署时就在pandas版本和CSV编码上栽了两次跟头。下面列出必须严格遵循的步骤跳过任何一步都可能导致KeyError、UnicodeDecodeError或回测结果全为NaN。3.1 行情数据必须用UTF-8无BOM编码的CSV字段名严格匹配系统默认读取data/raw/目录下的CSV文件每个文件名为股票代码.csv如000001.csv内容必须包含以下7列顺序不限但列名必须完全一致大小写敏感列名类型说明codestr股票代码6位数字如000001datestr日期格式YYYY-MM-DD如2023-01-03openfloat开盘价highfloat最高价lowfloat最低价closefloat收盘价volumeint成交量手避坑常见问题与排查现象1KeyError: close或ValueError: could not convert string to float: —原因CSV中存在中文字符如“—”代替空值、列名有空格如 close 、或编码不是UTF-8常见于Excel另存为CSV时选错编码。解决用VS Code打开CSV右下角确认编码为UTF-8用pandas.read_csv(..., encodingutf-8)显式指定将所有—、NULL替换为np.nan。现象2选股结果为空或回测收益率恒为0原因date列不是字符串格式而是datetime64导致dates.get_loc(2023-01-03)失败。解决在load_all_stock_data()函数中强制转换df[date] df[date].astype(str)。现象3微信图片发送失败报错invalid media_id原因上传图片后未等待media_id生效企业微信有缓存需1-2秒或picurl指向的URL无法被公网访问。解决上传后time.sleep(1.5)用curl -I http://your-server/signal.png确认返回200 OKpicurl必须是HTTP/HTTPS可访问地址不能是本地路径。3.2 Python环境3.8~3.106个核心包缺一不可系统在Python 3.8.10 / 3.9.16 / 3.10.12上实测通过。低于3.8会因typing模块缺失报错高于3.11则PyQt5部分API变更导致GUI崩溃。必须安装的包及版本范围包名版本要求安装命令作用PyQt55.15.0,5.16.0pip install PyQt55.15.9GUI框架5.16移除了QApplication.setStyle()导致界面错乱pandas1.3.0,2.0.0pip install pandas1.5.3数据处理2.0删除了Panel等旧API影响因子计算numpy1.21.0,1.24.0pip install numpy1.23.5向量化计算1.24的np.roll行为变更导致信号偏移requests2.25.0pip install requests调用企业微信APImatplotlib3.5.0pip install matplotlib3.7.1绘制信号表格图openpyxl3.0.0pip install openpyxl读取Excel格式的股票名称映射表data/stock_names.xlsx# 推荐一次性安装Windows PowerShell pip install PyQt55.15.9 pandas1.5.3 numpy1.23.5 requests matplotlib3.7.1 openpyxl避坑常见问题与排查现象1PyQt窗口启动后立即崩溃报错ImportError: DLL load failed while importing QtWidgets原因Python是32位而PyQt5 wheel是64位或反之或系统缺少VC运行库。解决下载 Microsoft Visual C 2015-2022 Redistributable 安装确认Python位数python -c import platform; print(platform.architecture())匹配PyQt5的wheel文件名含cp39-win_amd64即64位。现象2selector.py运行时报ModuleNotFoundError: No module named PyQt5.sip原因PyQt5 5.15已将sip模块分离需单独安装。解决pip install sip注意不是PyQt5-sip那是旧版。现象3微信发送成功但手机端收不到消息原因企业微信应用未开启“接收消息”权限或touser填了不存在的成员ID。解决登录企业微信管理后台 → 应用管理 → 找到你的应用 → 设置 → 权限管理 → 开启“发送消息”touser用all测试确认基础功能正常后再填具体ID。3.3 配置文件config.py里的4个密钥决定系统能否启动所有外部依赖参数都集中在此文件修改后无需重启程序GUI中点击“重载配置”按钮即可生效# config.py # —— 企业微信配置 —— CORPID wwxxxxxxxxxxxxxx # 企业ID后台「我的企业」页面获取 CORPSECRET xxxxxxxxxxxxxxxxxxx # 应用Secret后台「应用管理」→「自建应用」→「编辑」→「Secret」 AGENTID 1000001 # 应用AgentId同上页面底部显示 TOUSER all # 接收人支持all、UserID1|UserID2 # —— 数据路径配置 —— RAW_DATA_PATH data/raw/ # 原始CSV数据存放目录相对路径 STOCK_NAMES_PATH data/stock_names.xlsx # 股票代码→名称映射表两列code, name # —— 策略参数配置 —— SELECTOR_WINDOW_DAYS 60 # 选股时回看多少天数据用于计算均线等 BACKTEST_HOLD_DAYS 5 # 回测中默认持有天数 WECHAT_IMAGE_DPI 150 # 生成信号图的DPI影响清晰度避坑常见问题与排查现象1点击“选股”按钮后状态栏显示“正在获取行情数据…”但一直不动原因RAW_DATA_PATH路径错误或目录下没有.csv文件。解决在selector.py的load_all_stock_data()函数开头加print(fLoading from {self.raw_path})确认路径拼接正确用os.listdir(self.raw_path)打印实际文件列表。现象2微信消息标题显示为【None 选股信号】原因datetime.now().strftime(%m-%d)在某些系统时区下返回None。解决改为datetime.now(pytz.timezone(Asia/Shanghai)).strftime(%m-%d)并pip install pytz。4. 策略定制与因子扩展如何把“主力线上穿散户线”这种通达信指标安全注入到选股引擎源码包自带两个因子跳空倍量阳线、涨停缩量回调但它的设计初衷就是让你轻松添加自己的选股逻辑。我以“主力线上穿散户线”为例演示从通达信公式到PyQt可运行代码的完整迁移过程。这不是简单复制粘贴而是要理解因子计算的时间维度和数据边界——这才是90%人翻车的地方。4.1 通达信公式解析识别隐藏的时间窗口与引用偏移通达信公式主力线上穿散户线看似简单实则隐含三个关键信息主力线定义主力线:EMA(CLOSE,5)-EMA(CLOSE,10)5日与10日EMA之差散户线定义散户线:EMA(CLOSE,20)-EMA(CLOSE,60)20日与60日EMA之差“上穿”逻辑CROSS(主力线, 散户线)即主力线今日值 散户线今日值且主力线昨日值 ≤散户线昨日值玄学点CROSS(A,B)在通达信里是严格定义的不是简单的AB。它要求A从下方穿越B即(A B) AND (REF(A,1)REF(B,1))。很多Python移植者只写AB导致信号提前1天。4.2 NumPy向量化实现用np.roll替代REF用np.diff捕捉“上穿”在selector.py中新增函数# selector.py 新增因子 def calculate_main_vs_public_factor(self, df: pd.DataFrame) - pd.Series: 主力线上穿散户线因子返回0不满足或1满足 主力线 EMA(close,5) - EMA(close,10) 散户线 EMA(close,20) - EMA(close,60) 上穿 (主力线 散户线) (主力线昨日 散户线昨日) close df[close].values # 计算各周期EMA用pandas内置避免自己写递归 ema5 df[close].ewm(span5, adjustFalse).mean().values ema10 df[close].ewm(span10, adjustFalse).mean().values ema20 df[close].ewm(span20, adjustFalse).mean().values ema60 df[close].ewm(span60, adjustFalse).mean().values main_line ema5 - ema10 public_line ema20 - ema60 # REF(x,1) → 用np.roll实现 main_line_prev np.roll(main_line, 1) public_line_prev np.roll(public_line, 1) main_line_prev[0] np.nan public_line_prev[0] np.nan # CROSS逻辑今日上穿 (今日主力今日散户) (昨日主力昨日散户) cross_today (main_line public_line) (main_line_prev public_line_prev) # 返回布尔SeriesTrue1, False0 return pd.Series(np.where(cross_today, 1, 0), indexdf.index) # 在run()方法中调用 def run(self): # ... 其他代码 main_cross_score self.calculate_main_vs_public_factor(df).iloc[-1] # 将main_cross_score加入总分计算参数说明ewm(spanN)中的span对应通达信的EMA(CLOSE,N)adjustFalse确保与通达信计算一致默认True会引入偏差。np.roll是核心它把数组整体右移1位首元素变为nan完美模拟REF(x,1)。4.3 GUI集成在PyQt界面中动态添加新因子开关main_window.ui里有一个QGroupBox叫选股因子里面预置了两个QCheckBox跳空倍量、缩量回调。要添加“主力线上穿”只需用Qt Designer打开ui/main_window.ui在QGroupBox内拖入第三个QCheckBoxobjectName设为chk_main_cross在main.py的__init__中绑定self.chk_main_cross.stateChanged.connect(self.on_factor_changed)在on_factor_changed中根据勾选状态动态调整selector.run()的参数def on_factor_changed(self): # 收集所有勾选的因子名 factors [] if self.chk_gap_volume.isChecked(): factors.append(gap_volume) if self.chk_shrink_volume.isChecked(): factors.append(shrink_volume) if self.chk_main_cross.isChecked(): factors.append(main_cross) self.selector.active_factors factors # 传递给selector血泪经验不要在selector.run()里硬编码因子列表。我最初把所有因子写死在run()里后来加第四个因子时忘了改run()里的if-elif链导致新因子永远不生效。现在用active_factors列表驱动新增因子只需改UI和配置逻辑层完全不动。4.4 回测验证用历史信号反推因子有效性避开“过拟合幻觉”添加新因子后别急着实盘先用backtester.py跑一次滚动回测验证它是否真有超额收益。我在源码包里加了一个validate_factor.py脚本# validate_factor.py from backtester import StrategyBacktester from selector import StockSelector # 用2022全年数据训练2023全年数据测试 train_selector StockSelector(data_pathdata/2022/) test_selector StockSelector(data_pathdata/2023/) # 获取2022年所有信号 train_signals train_selector.run(factor_list[main_cross]) # 用这些信号在2023年回测模拟实盘 backtester StrategyBacktester() result backtester.run_backtest(train_signals, test_price_data) print(f2023年实盘模拟收益: {result[total_return]:.2%}) print(f胜率: {result[win_rate]:.1%}) print(f最大回撤: {result[max_drawdown]:.2%})关键技巧这里用的是样本外验证Out-of-Sample Validation。如果2022年信号在2023年仍能跑出15%收益说明因子不是过拟合如果收益为负就要检查因子逻辑或数据质量。我曾发现“主力线上穿”在2022年收益32%但在2023年只有-5%追查发现是2023年小盘股波动加剧导致EMA参数需从5/10/20/60优化为3/8/15/40——这就是滚动验证的价值。5. 微信推送实战技巧让信号图不止于“能发”而做到“一眼看懂、立刻行动”企业微信推送的终极目标不是“发出去”而是让接收者3秒内抓住重点、5秒内决定是否操作。源码包默认的表格图太简陋我花了两周时间打磨出一套“信号图增强方案”现在每次推送都附带三张图主信号表带颜色标识、个股K线图标出买入点、板块热度图显示信号股所属行业强度。下面分享其中最实用的两个技巧。5.1 主信号表用颜色编码替代文字降低决策成本原始表格只有黑白文字用户得逐行读“000001 平安银行 1 2023-07-12”。优化后用matplotlib的table单元格着色# enhanced_table.py def create_colored_signal_table(signal_df: pd.DataFrame) - BytesIO: fig, ax plt.subplots(figsize(10, len(signal_df)*0.6)) ax.axis(tight) ax.axis(off) # 创建表格 table ax.table( cellTextsignal_df.values, colLabelssignal_df.columns, cellLoccenter, loccenter ) table.auto_set_font_size(False) table.set_fontsize(11) table.scale(1, 1.8) # 根据得分着色得分2→绿色得分1→黄色其他→白色 for i in range(len(signal_df)): score signal_df.iloc[i][score] if score 2: for j in range(len(signal_df.columns)): table[(i1, j)].set_facecolor(#d5e8d4) # 浅绿 elif score 1: for j in range(len(signal_df.columns)): table[(i1, j)].set_facecolor(#fff2cc) # 浅黄 # 标题行加粗 for j in range(len(signal_df.columns)): table[(0, j)].set_fontweight(bold) table[(0, j)].set_facecolor(#dae8fc) # 浅蓝 img_buffer BytesIO() plt.savefig(img_buffer, formatpng, bbox_inchestight, dpi200) img_buffer.seek(0) plt.close(fig) return img_buffer效果对比原来用户要读完“得分”列才能判断优先级现在一眼扫过去绿色行就是最高优先级黄色次之。实测团队内部反馈信号响应速度从平均47秒缩短到12秒。5.2 个股K线图嵌入买卖点标记消除“买在何处”的歧义单纯发“000001 触发信号”不够必须告诉用户“在哪根K线上买”。我在wechat_sender.py中增加plot_stock_kline()函数# wechat_sender.py def plot_stock_kline(self, code: str, signal_date: str, days_before: int 10) - BytesIO: 绘制个股K线图标出信号日的买入点红色三角形 # 读取该股票最近N天数据 df pd.read_csv(fdata/raw/{code}.csv) df[date] pd.to_datetime(df[date]) target_date pd.to_datetime(signal_date) recent_df df[df[date] target_date - pd.Timedelta(daysdays_before)] fig, ax plt.subplots(figsize(12, 6)) # 绘制K线用mplfinance更专业但为减少依赖用简易版 ax.plot(recent_df[date], recent_df[close], labelClose, colorblack) ax.fill_between(recent_df[date], recent_df[low], recent_df[high], alpha0.3, colorgray, labelRange) # 标出信号日红色三角形 signal_row recent_df[recent_df[date] target_date] if not signal_row.empty: ax.scatter(signal_row[date], signal_row[close], marker^, s200, cred, zorder5, labelBuy Signal) # 添加垂直线 ax.axvline(xtarget_date, colorred, linestyle--, alpha0.7) ax.set_title(f{code} {self.get_stock_name(code)} - Signal on {signal_date}) ax.legend() ax.grid(True) img_buffer BytesIO() plt.savefig(img_buffer, formatpng, bbox_inchestight, dpi150) img_buffer.seek(0) plt.close(fig) return img_buffer参数说明days_before10表示画信号日前10天的K线足够看清形态s200控制三角形大小确保在手机上清晰可见zorder5保证三角形盖在K线上方不被遮挡。5.3 推送组合一张主表 N张个股图 1张板块图构成完整决策链企业微信图文消息最多支持8张图我分配如下图1主信号表带颜色编码图2~图6前5只信号股的K线图按得分排序图7板块热度图用seaborn.heatmap绘制横轴行业、纵轴日期、色块强度该行业信号股数量图8操作指引文字“买入信号日收盘价止损买入日低点下方3%止盈5日均线”# wechat_sender.py 发送逻辑 def send_enhanced_report(self, signal_df: pd.DataFrame): # 1. 生成主表 main_img create_colored_signal_table(signal_df) # 2. 生成前5只个股K线图 kline_imgs [] for _, row in signal_df.head(5).iterrows(): kline_imgs.append(self.plot_stock_kline(row[code], row[signal_date])) # 3. 生成板块图 sector_img self.plot_sector_heatmap(signal_df) # 4. 组装图文消息略调用企业微信API p a hrefhttps://download.csdn.net/download/2501_91537435/92241195 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p
返回列表