
1. 从盯盘焦虑到自动推送这套系统到底在解决什么做A股的人大概都有过这种体验早上九点半开盘手里几只票一边上班一边偷偷刷行情涨了怕回撤跌了怕深套一天下来正事没干几件心态倒是被反复揉搓。更麻烦的是很多信号是盘中稍纵即逝的等你中午吃饭时打开软件该发生的都发生完了。我身边不少朋友的做法是设个价格提醒但券商的提醒功能往往很粗糙——只能设固定价位没法做组合条件更没法把多个指标串起来判断。这套基于LLM的企微量化推送系统核心要解决的就是这件事把人盯盘变成系统盯盘有事才叫你。它的工作链路很清晰——用Python定时拉取行情数据跑一遍你自定义的量化策略当策略触发条件时把结果交给LLM做一层人话翻译和上下文补充最后通过企业微信的机器人接口推送到你手机上。整个过程不需要你盯着屏幕也不需要你懂多少机器学习会写一点Python、能配几个参数就能跑起来。为什么是企微而不是别的推送渠道这是我在实际搭建中反复权衡后的选择。企微群机器人有官方开放的Webhook接口配置简单到只需要一个URL不用申请复杂的开发者账号也不用处理各种鉴权跳转。而且企微消息在手机上的提醒很及时支持Markdown格式能把涨跌幅、触发原因、相关新闻排得清清楚楚。相比之下短信要钱、邮件容易进垃圾箱、其他即时通讯工具的机器人接口要么门槛高要么不稳定。对于个人散户来说企微机器人是性价比最高的方案。那LLM在这里面扮演什么角色很多人第一反应是让AI预测涨跌这恰恰是最大的误区。LLM在这套系统里的定位是信息加工器不是预测器。它的价值在于把冷冰冰的MACD金叉、成交量放大1.8倍翻译成这只票今天量价配合不错短期动能转强但要注意上方20日线压力把策略触发的技术信号和当天的公告、行业新闻做关联给你一个更完整的决策背景。说白了它帮你省掉的是看到信号后还要自己去查资料、理解含义的那几分钟而不是替你做买卖决定。这套系统适合谁我认为有三类人值得动手一是有点Python基础、想把自己的交易想法系统化验证的散户二是工作忙、没法全天盯盘但不想完全放弃主动管理的上班族三是想学习量化LLM应用落地、拿真实场景练手的开发者。如果你完全不懂编程那需要先补一下Python基础但门槛没有想象中高后面我会把关键代码和配置都拆开讲。需要提前说清楚的是这套系统是辅助决策工具不是自动交易系统。它不会替你下单也不会保证你赚钱。它的作用是让你在关键时点收到有信息量的提醒把精力从持续盯盘转移到关键时刻做判断。这个定位想清楚了后面的搭建和使用才不会跑偏。2. 拆解系统四大模块数据、策略、LLM、推送各干什么活在动手写代码之前先把整个系统的骨架理清楚。我把它拆成四个相对独立的模块每个模块只干一件事模块之间通过约定好的数据格式衔接。这样设计的好处是任何一块出问题都好排查想换数据源或者换推送渠道也不用推倒重来。2.1 行情数据模块免费源怎么选、怎么取才稳数据是整个系统的地基。A股行情数据获取有几个常见途径一是各类财经数据接口二是券商提供的行情API三是开源社区维护的数据包。对于个人散户的日频或分钟频需求我推荐用开源的Python财经数据库比如akshare这类社区维护的项目它封装了多个公开数据源调用简单覆盖A股、指数、基金等品种。选数据源时我最看重三点稳定性、字段完整性、调用频率限制。稳定性指的是接口别三天两头挂字段完整性指的是除了最新价还要能拿到开高低收、成交量、成交额这些做策略必需的字段频率限制则决定了你能多久拉一次数据。实测下来日线数据每天收盘后拉一次完全够用如果你做的是日内策略那需要分钟级数据对接口的实时性要求就更高。这里有个容易踩的坑不要在主策略循环里直接调数据接口。我一开始图省事每次策略判断都实时去拉数据结果一是慢二是容易触发频率限制被封。正确做法是加一层本地缓存——每天定时把关注的股票数据拉到本地可以用SQLite或CSV策略运行时读本地文件。这样既快又稳还能回测历史数据。import akshare as ak import pandas as pd from datetime import datetime def fetch_daily_data(stock_list, save_pathdata/daily): 拉取关注股票的日线数据并落地到本地 for code in stock_list: try: df ak.stock_zh_a_hist( symbolcode, perioddaily, start_date20240101, end_datedatetime.now().strftime(%Y%m%d), adjustqfq # 前复权做策略必须用复权数据 ) df.to_csv(f{save_path}/{code}.csv, indexFalse) print(f{code} 数据已更新共 {len(df)} 条) except Exception as e: print(f{code} 拉取失败: {e})注意adjustqfq这个参数前复权处理是必须的。如果不复权遇到除权除息日价格会出现跳空缺口你的均线、MACD全都会失真策略信号也就没意义了。这是很多新手第一次做量化时最容易忽略的细节。2.2 策略模块把交易想法翻译成可执行的判断逻辑策略模块是整个系统的大脑它决定了什么时候该推送。对散户来说不需要一上来就搞复杂的多因子模型从几个经典的技术指标组合开始就够用。我常用的组合是均线系统判断趋势方向成交量判断资金参与度MACD或RSI判断动能强弱。策略的写法有个原则条件要明确、可量化、无歧义。感觉要涨不是策略5日均线上穿20日均线且当日成交量大于5日均量1.5倍才是策略。每个条件都要能用代码里的布尔表达式写出来这样回测和实盘才能一致。def check_strategy(df): 一个简单的均线量能策略示例 df df.copy() df[ma5] df[收盘].rolling(5).mean() df[ma20] df[收盘].rolling(20).mean() df[vol_ma5] df[成交量].rolling(5).mean() latest df.iloc[-1] prev df.iloc[-2] # 金叉昨天ma5在ma20下方今天上穿 golden_cross (prev[ma5] prev[ma20]) and (latest[ma5] latest[ma20]) # 量能放大 vol_surge latest[成交量] latest[vol_ma5] * 1.5 if golden_cross and vol_surge: return { trigger: True, reason: f5日均线上穿20日均线成交量放大至5日均量的{latest[成交量]/latest[vol_ma5]:.2f}倍, price: latest[收盘], change_pct: (latest[收盘]/prev[收盘]-1)*100 } return {trigger: False}这段代码里有个细节值得说判断金叉时我用的是昨天在下方、今天在上方而不是简单地比较今天ma5和ma20的大小。因为后者只能告诉你当前处于多头排列无法捕捉刚刚发生交叉这个时点。推送系统的价值就在于抓拐点所以交叉判断必须用前后两天的状态对比。2.3 LLM加工模块让机器说人话而不是替你算命这是整套系统里最容易被误解的模块。我再强调一遍LLM不预测涨跌它做的是信息翻译和上下文补充。策略模块输出的是结构化的信号数据LLM的任务是把这个信号变成一段有逻辑、有背景、读起来像人话的推送文案。具体怎么做我采用的是模板LLM润色的混合方案。先有一个基础模板把关键数据填进去然后让LLM基于这个模板和额外的上下文比如当天该股票的相关新闻标题生成一段更自然的描述。这样既保证了数据准确又让文案不那么机械。调用LLM时提示词的设计很关键。我的经验是给角色、给约束、给示例。告诉它你是一个冷静的A股复盘助手不要用夸张词汇不要给出买卖建议只做客观描述和风险提示然后给一两个输入输出示例效果会稳定很多。def generate_push_text(signal, news_titles, llm_client): prompt f你是一个冷静客观的A股复盘助手。请根据以下信息生成一段简短的推送文案。 要求 1. 不超过150字 2. 不使用必涨稳赚等夸张词汇 3. 不给出明确买卖建议 4. 客观描述信号并提示风险 信号数据{signal} 相关新闻{news_titles} 请输出推送文案 response llm_client.chat(prompt) return response关于LLM的选型本地部署和云端API各有取舍。本地跑开源模型比如通过Ollama部署的好处是数据不出本地、没有调用成本缺点是对机器配置有要求生成速度也慢一些。云端API的好处是快、效果好缺点是要花钱、数据要发出去。我的建议是如果只是做信号翻译这种轻量任务本地小模型完全够用如果要做复杂的新闻关联分析云端大模型效果更好。千万不要把账户信息、持仓明细这类敏感数据发给任何外部服务这是底线。2.4 企微推送模块一个Webhook搞定消息触达企微群机器人的接入简单到令人感动。在企微群里点右上角添加群机器人拿到一个Webhook地址然后往这个地址POST一个JSON就能发消息。支持文本、Markdown、图片等多种格式Markdown格式最适合做行情推送能把关键数据加粗、排版整齐。import requests def send_wecom_message(webhook_url, content): 通过企微群机器人推送Markdown消息 payload { msgtype: markdown, markdown: {content: content} } resp requests.post(webhook_url, jsonpayload, timeout10) result resp.json() if result.get(errcode) ! 0: print(f推送失败: {result}) return result这里有个实操经验Webhook地址要放在环境变量或配置文件里不要硬编码在代码中。一是方便换群二是避免把地址提交到代码仓库造成泄露。企微机器人有频率限制每分钟最多发20条正常策略推送一天也就几条到几十条完全够用但如果你监控的股票特别多要注意合并推送别一条信号发一次。四个模块串起来的流程就是定时任务触发 → 数据模块拉取最新行情 → 策略模块逐票判断 → 触发的信号交给LLM加工 → 推送模块发到企微。每个模块都可以单独测试这样调试起来不会一团乱麻。3. 从零跑通的完整搭建流程与关键配置前面讲的是为什么这么设计这一节讲具体怎么搭。我按实际搭建顺序走一遍每一步都说明操作意图和容易出问题的地方。假设你已经装好了Python环境如果还没装去Python官网下载3.10以上版本安装时记得勾选Add to PATH。3.1 环境准备与依赖安装先建一个独立的虚拟环境这是好习惯。不同项目的依赖版本经常打架虚拟环境能隔离干净。python -m venv venv # Windows venv\Scripts\activate # macOS/Linux source venv/bin/activate pip install akshare pandas requests schedule如果你打算用本地LLM还需要装Ollama并拉一个模型如果用云端API装对应的SDK就行。依赖不要一次装太多用到什么装什么装多了容易出兼容问题。国内下载pip包慢的话可以配置镜像源加速这个网上教程很多不展开。3.2 配置文件的设计把会变的东西抽出来我强烈建议把股票列表、策略参数、Webhook地址、LLM配置这些会变的东西全部抽到一个配置文件里代码只读配置不写死。这样你想加一只票、调一个参数改配置文件就行不用动代码。# config.py CONFIG { stock_list: [600519, 000858, 300750], # 关注股票代码 strategy: { ma_short: 5, ma_long: 20, vol_ratio: 1.5 }, wecom_webhook: 从环境变量读取, llm: { provider: local, # local 或 cloud model: qwen2.5, api_base: http://localhost:11434 }, schedule_time: 15:30 # 每天收盘后运行 }Webhook这种敏感信息用os.environ.get(WECOM_WEBHOOK)从环境变量读别写在配置文件里。这是安全习惯也是防止误提交的基本操作。3.3 主流程串联与定时调度主流程就是一个函数把四个模块按顺序调一遍。定时调度用schedule库或者系统的crontab都行。日频策略的话每天收盘后比如15:30跑一次最合适这时候当天数据已经定型不会出现盘中数据跳动导致信号反复的问题。import schedule import time from config import CONFIG from data_module import fetch_daily_data from strategy_module import check_strategy from llm_module import generate_push_text from push_module import send_wecom_message def daily_job(): print(开始执行每日任务...) fetch_daily_data(CONFIG[stock_list]) for code in CONFIG[stock_list]: df pd.read_csv(fdata/daily/{code}.csv) signal check_strategy(df) if signal[trigger]: text generate_push_text(signal, [], llm_client) send_wecom_message(CONFIG[wecom_webhook], text) print(f{code} 信号已推送) schedule.every().day.at(CONFIG[schedule_time]).do(daily_job) while True: schedule.run_pending() time.sleep(60)这个循环会一直挂着适合放在一台常开的机器上。如果你不想一直开着电脑可以把它部署到云服务器上或者用系统的定时任务在固定时间唤起脚本执行一次就退出。后者更省资源适合个人使用。3.4 第一次跑通的验证方法第一次跑千万别直接上真实推送容易翻车。我的验证顺序是先单独测数据拉取确认CSV文件生成且数据正确再单独测策略函数拿历史数据手动验证信号是否合理然后测LLM生成看文案是否通顺、有没有胡说最后测推送先发一条测试消息到企微群确认能收到。四个模块都单独验证过再串起来跑。提示第一次跑策略时建议先用历史数据回测一下看看过去几个月这套策略触发了多少次、触发后的走势如何。如果触发太频繁比如每天好几只票都触发说明条件太松需要收紧如果几个月都不触发一次说明太严需要放宽。这个调参过程是必须的。4. 实测中踩过的坑与稳定性优化系统能跑起来只是第一步能稳定跑下去才是关键。这一节我把自己实际运行中遇到的问题和解决办法整理出来都是真金白银换来的经验。4.1 数据接口的坑复权、停牌、字段缺失第一个大坑是复权。前面提过但值得再强调。我有一次忘了加复权参数结果某只票除权当天价格暴跌了30%策略直接触发了一个假信号推送到手机上吓我一跳。后来所有数据拉取都强制加前复权再没出过这个问题。第二个坑是停牌。停牌期间数据接口可能返回空值或者干脆没有当天数据如果你的策略代码没做空值判断直接iloc[-1]取最新行可能取到的是停牌前的旧数据导致重复触发。解决办法是在策略函数开头加一个判断如果最新数据的日期不是今天直接跳过。def is_trading_today(df): 判断最新数据是否为今天避免停牌或数据未更新导致误判 latest_date pd.to_datetime(df.iloc[-1][日期]).date() return latest_date datetime.now().date()第三个坑是字段名不统一。不同数据源返回的列名可能不一样有的叫收盘有的叫close有的叫成交量有的叫volume。写代码时最好做一层字段映射把列名统一成自己习惯的后面策略代码就不用管数据源差异了。4.2 LLM输出的不确定性怎么让它稳定说人话LLM最大的问题是输出不稳定。同样的输入今天生成一段很专业的描述明天可能就给你来一句该股票即将大涨这种违规内容。我的应对策略有三层第一层是提示词约束明确禁止夸张词汇和买卖建议这个前面讲过。第二层是输出后处理用关键词过滤把必涨稳赚满仓这类词拦掉如果命中就重新生成或者退回模板文案。第三层是降级机制如果LLM调用失败或者超时直接用预设的模板文案推送保证系统不会因为LLM挂了就整个瘫痪。FORBIDDEN_WORDS [必涨, 稳赚, 满仓, 梭哈, 翻倍] def safe_generate(signal, llm_client, fallback_template): try: text generate_push_text(signal, [], llm_client) if any(w in text for w in FORBIDDEN_WORDS): return fallback_template.format(**signal) return text except Exception as e: print(fLLM生成失败使用模板: {e}) return fallback_template.format(**signal)这个降级机制看起来简单但在实际运行中救过我好几次。LLM服务偶尔会抽风有了降级至少保证推送不断。4.3 推送频率与信息过载的平衡系统刚跑起来那阵子我恨不得把所有能想到的信号都加上结果一天推送十几条手机响个不停最后反而懒得看了。这就是典型的信息过载。后来我做了两个调整一是合并推送同一只票的多个信号合并成一条不同票的信号如果时间接近也合并二是分级推送把信号按重要性分成关注和提醒两级只有高优先级的才立即推送低优先级的攒到收盘后一起发。信号类型优先级推送时机均线金叉放量高触发即推单指标超买超卖低收盘汇总跌破关键支撑高触发即推日常数据更新低不推送这个分级不是拍脑袋定的是根据我自己看盘的习惯来的。真正需要马上知道的是趋势拐点和关键位突破其他都可以缓一缓。你可以根据自己的交易风格调整这个表。4.4 密钥与敏感信息的安全处理这一点必须单独拎出来说。系统里涉及两类敏感信息企微Webhook地址和LLM的API密钥。这两个东西一旦泄露前者会被人往你群里发垃圾消息后者会被人盗刷你的API额度。处理原则就一条永远不要硬编码在代码里永远不要提交到代码仓库。具体做法用环境变量或者.env文件存储.env文件加到.gitignore里。如果用的是云端LLM定期轮换API密钥。如果代码要分享给别人先检查一遍有没有残留的密钥。我见过太多人把自己的密钥不小心推到公开仓库几分钟内就被扫到盗用这个教训很贵。# .env 文件示例不要提交到git WECOM_WEBHOOKhttps://qyapi.weixin.qq.com/cgi-bin/webhook/send?key你的key LLM_API_KEY你的密钥from dotenv import load_dotenv import os load_dotenv() webhook os.environ.get(WECOM_WEBHOOK) api_key os.environ.get(LLM_API_KEY)5. 策略迭代与LLM提示词的进阶玩法系统跑通之后真正的乐趣才开始——不断迭代策略和优化LLM的输出质量。这一节分享几个我实践下来觉得有价值的进阶方向。5.1 从单指标到多条件组合的演进思路一开始我用的是单一均线金叉信号很多但质量参差。后来逐步叠加条件加成交量过滤掉无量假突破加趋势过滤比如只在ma20向上时才算有效金叉加位置过滤比如股价不在近期高位才考虑。每加一个条件信号数量会减少但质量会提升。这个过程本质是在提高信噪比。但条件也不是越多越好。加到五六个条件后可能一个月都不触发一次系统就失去意义了。我的经验是控制在三到四个核心条件每个条件都要有明确的逻辑支撑不要为了过滤而过滤。判断标准很简单这个条件能不能用一句话说清楚它为什么有效说不清楚的就别加。5.2 让LLM结合新闻做上下文关联单纯翻译技术信号LLM的价值有限。真正有意思的是让它把信号和当天的新闻关联起来。比如某只票触发了放量突破信号同时当天有行业利好新闻LLM可以把这两件事串起来告诉你今日该股放量突破消息面上有XX行业政策发布两者可能相关但需注意政策落地节奏。实现方式是在拉取行情数据的同时用财经新闻接口拉取该股票相关的新闻标题作为上下文一起喂给LLM。这里要注意控制新闻数量太多会稀释重点一般取最近3到5条标题就够。提示词里明确要求LLM只做关联描述不做因果断言避免它把巧合说成必然。def build_context(code, signal): news fetch_stock_news(code, limit5) # 拉取相关新闻标题 return { signal: signal, news: news, date: datetime.now().strftime(%Y-%m-%d) }5.3 提示词模板的迭代记录提示词不是一次写好的我前后改了十几版。最初的版本太笼统LLM输出很飘后来加了角色设定、输出格式要求、禁止事项才逐渐稳定。我建议你也把每次修改和效果记录下来形成自己的提示词库。版本主要改动效果v1简单要求生成推送文案输出随意经常超长v2加角色设定和字数限制长度可控但仍有夸张词v3加禁止词汇和风险提示要求基本稳定偶有遗漏v4加输出示例few-shot格式统一质量明显提升few-shot示例这一招特别管用。给LLM一两个输入信号→输出文案的完整示例它就能模仿这个风格比单纯用文字描述要求有效得多。示例要选有代表性的覆盖不同类型的信号。5.4 回测验证别让策略只是看起来很美任何策略上线前都应该回测。回测不是让你相信策略能赚钱而是让你知道它在什么情况下会失效。我的回测方法很朴素拿过去一两年的历史数据逐日跑一遍策略记录每次触发后5日、10日、20日的涨跌情况统计胜率和盈亏比。回测里最容易犯的错是未来函数——用了当天收盘后才能知道的数据去判断当天该不该买。比如你用当天的收盘价算均线又假设当天收盘价买入这就是未来函数回测结果会虚高。正确做法是用T日数据判断信号假设T1日开盘买入。这个细节决定了回测结果可不可信。6. 关于这套系统的一些个人体会搭这套系统的过程对我来说最大的收获不是多赚了多少钱而是把凭感觉交易变成了按规则执行。以前看到一只票涨了就想追现在会先问自己它触发了我的哪个策略条件如果没触发那就不在系统覆盖范围内不碰。这种纪律性本身就是价值。LLM在里面的角色我越来越觉得它像个帮你整理思路的助手而不是给你答案的专家。它能把散落的信息串起来能把你从重复的信息搜集里解放出来但最终的判断还得自己做。指望LLM告诉你买什么卖什么那方向就错了。最后分享一个小心得这套系统不要一上来就追求完美。先跑通最小可用版本——一只票、一个简单策略、一条推送能收到消息就算成功。然后再慢慢加股票、加策略、优化文案。我见过太多人一开始就想搭个大而全的系统结果卡在某个细节上就放弃了。小步快跑持续迭代才是个人项目能活下来的方式。