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_path="data/daily"): """拉取关注股票的日线数据并落地到本地""" for code in stock_list: try: df = ak.stock_zh_a_hist( symbol=code, period="daily", start_date="20240101", end_date=datetime.now().strftime("%Y%m%d"), adjust="qfq" # 前复权,做策略必须用复权数据 ) df.to_csv(f"{save_path}/{code}.csv", index=False) print(f"{code} 数据已更新,共 {len(df)} 条") except Exception as e: print(f"{code} 拉取失败: {e}")注意adjust="qfq"这个参数,前复权处理是必须的。如果不复权,遇到除权除息日,价格会出现跳空缺口,你的均线、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": f"5日均线上穿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, json=payload, timeout=10) 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(f"data/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(f"LLM生成失败,使用模板: {e}") return fallback_template.format(**signal)这个降级机制看起来简单,但在实际运行中救过我好几次。LLM服务偶尔会抽风,有了降级至少保证推送不断。
4.3 推送频率与信息过载的平衡
系统刚跑起来那阵子,我恨不得把所有能想到的信号都加上,结果一天推送十几条,手机响个不停,最后反而懒得看了。这就是典型的信息过载。后来我做了两个调整:一是合并推送,同一只票的多个信号合并成一条,不同票的信号如果时间接近也合并;二是分级推送,把信号按重要性分成"关注"和"提醒"两级,只有高优先级的才立即推送,低优先级的攒到收盘后一起发。
| 信号类型 | 优先级 | 推送时机 |
|---|---|---|
| 均线金叉+放量 | 高 | 触发即推 |
| 单指标超买超卖 | 低 | 收盘汇总 |
| 跌破关键支撑 | 高 | 触发即推 |
| 日常数据更新 | 低 | 不推送 |
这个分级不是拍脑袋定的,是根据我自己看盘的习惯来的。真正需要马上知道的是趋势拐点和关键位突破,其他都可以缓一缓。你可以根据自己的交易风格调整这个表。
4.4 密钥与敏感信息的安全处理
这一点必须单独拎出来说。系统里涉及两类敏感信息:企微Webhook地址和LLM的API密钥。这两个东西一旦泄露,前者会被人往你群里发垃圾消息,后者会被人盗刷你的API额度。处理原则就一条:永远不要硬编码在代码里,永远不要提交到代码仓库。
具体做法:用环境变量或者.env文件存储,.env文件加到.gitignore里。如果用的是云端LLM,定期轮换API密钥。如果代码要分享给别人,先检查一遍有没有残留的密钥。我见过太多人把自己的密钥不小心推到公开仓库,几分钟内就被扫到盗用,这个教训很贵。
# .env 文件示例(不要提交到git) WECOM_WEBHOOK=https://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, limit=5) # 拉取相关新闻标题 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日数据判断信号,假设T+1日开盘买入。这个细节决定了回测结果可不可信。
6. 关于这套系统的一些个人体会
搭这套系统的过程,对我来说最大的收获不是多赚了多少钱,而是把"凭感觉交易"变成了"按规则执行"。以前看到一只票涨了就想追,现在会先问自己:它触发了我的哪个策略条件?如果没触发,那就不在系统覆盖范围内,不碰。这种纪律性本身就是价值。
LLM在里面的角色,我越来越觉得它像个"帮你整理思路的助手",而不是"给你答案的专家"。它能把散落的信息串起来,能把你从重复的信息搜集里解放出来,但最终的判断还得自己做。指望LLM告诉你买什么卖什么,那方向就错了。
最后分享一个小心得:这套系统不要一上来就追求完美。先跑通最小可用版本——一只票、一个简单策略、一条推送,能收到消息就算成功。然后再慢慢加股票、加策略、优化文案。我见过太多人一开始就想搭个大而全的系统,结果卡在某个细节上就放弃了。小步快跑,持续迭代,才是个人项目能活下来的方式。