
简介go-stock 是一套基于大语言模型的AI赋能股票分析工具源码面向有一定Go与前端基础的开发者和量化投资爱好者用于构建支持A股、港股、美股的多市场本地化分析客户端。项目采用WailsNaiveUI构建覆盖行情获取、成本盈亏展示、涨跌报警推送、市场整体/个股情绪分析及K线技术指标等功能数据全部保留本地并适配DeepSeek、OpenAI、Ollama、硅基流动、火山方舟、阿里云百炼等主流模型平台方便二次扩展。资源共167个文件以Go后端逻辑63个、Vue界面26个、JSON配置与Markdown说明为主辅以脚本、图标、样式等文件压缩包仅5.08MB结构清晰便于梳理模块。已有107人浏览学习适合需要快速搭建AI股票分析工具或参考其多端交互与情绪分析实现的中高级开发者。通过源码可掌握Wails桌面应用打包流程、LLM接口统一封装以及行情数据与AI提示词结合的分析模式获得一套可直接编译运行的完整项目。1. 基于大语言模型的 AI 赋能股票分析工具这不是又一个行情软件如果只是想看 K 线同花顺、富途已经做到极致轮不到我们再造一个桌面客户端。这个标题真正值得做的是另一件事把大语言模型接入到行情分析链路里让工具不只展示「股价是多少」而是回答「市场现在什么情绪、这只股票为什么涨、技术指标在提示什么」。它基于 Wails 和 NaiveUI 构建桌面端覆盖 A 股、港股、美股核心功能是市场/个股情绪分析和 K 线技术指标分析。适合两类人一是想把 LLM 落到真实业务场景的桌面端开发者二是每天要盯多市场、想用 AI 辅助决策的量化散户。下文会按「选型理由 — 实现路径 — 多市场参数 — 踩坑 — 进阶」展开代码都可以直接抄。2. Wails NaiveUI 搭桌面客户端为什么选这套组合与最小搭建2.1 选型理由Wails 比 Electron 更适合这种工具做股票分析桌面端最常见的方案是 Electron但我最终选了 Wails。原因很直白占用内存差一个量级。Electron 起一个应用轻松吃掉 200MB 内存而 Wails 利用的是系统自带的 WebView2Windows或 WebKitGTKLinux前端用 HTML/JS 渲染后端是 Go一个自用行情工具 40MB 左右就能跑起来。对股票这种需要常驻托盘、频繁刷新数据的工具内存就是体验。另一个原因是后端语言。行情分析要处理多市场代码映射、技术指标计算、调用大语言模型 APIGo 在并发和部署上非常顺手。用go写接口用 Vue NaiveUI 写界面两者通过 Wails 的 binding 机制直接互调不用像 Electron 那样在 IPC 上写一堆胶水代码。而且 Wails 支持把 Go 方法直接暴露给前端前端调用就像调用本地函数开发心智负担低很多。2.2 最小工程初始化wails init 与 NaiveUI 集成先建工程。常见做法是用 Wails 官方脚手架生成 Vue 模板再手动装 NaiveUI。wails init -n stock-ai -t vue cd stock-ai npm install naive-ui # 开发模式启动 Vite 和 Go 后端 wails dev这段命令背后的逻辑是wails init生成两个部分——根目录的 Go 入口main.go和frontend/里的 Vue3 Vite 前端。加naive-ui是因为我们需要的是数据密集型界面NaiveUI 的暗色主题、n-data-table虚拟滚动、n-spin加载态都适合行情页。wails dev会同时启动 Vite 热更新和 Go 编译前端代码改动即时生效。提示如果你的电脑上wails dev起不来先跑wails doctor检查环境常见是 Windows 上没有 WebView2 Runtime或者 Linux 上缺libwebkit2gtk。初始化后把wails.json里的frontend:build保持默认打包时 Wails 会自动调用npm run build生成静态资源并嵌入 Go 二进制。这里有个参数要注意wails.json里的devServer端口默认 34115如果和其他项目冲突改成 34116 再跑。2.3 Binding 机制前端怎么直接调 Go 方法Wails 最核心的机制是 binding。Go 后端把结构体方法暴露给前端前端用window.go.backend.App.MethodName()调用。下面是最小示例// backend/app.go package main import context type Stock struct { Code string json:code Market string json:market Price float64 json:price } type App struct{} func (a *App) GetStockBasic(ctx context.Context) []Stock { return []Stock{ {Code: 00700, Market: HK, Price: 310.2}, {Code: 600519, Market: A, Price: 1700.5}, } }前端调用时不用写任何网络请求// frontend/src/api.ts export async function getStockBasic() { return await window.go.backend.App.GetStockBasic(); }这段逻辑要理解两个点。第一ctx context.Context是 Wails 注入的用来传递前端调用的取消信号长任务比如后面调大语言模型必须接收它用户关闭窗口时能及时中断。第二Go 结构体字段必须带jsontag因为 Wails 在底层会把返回数据做 JSON 序列化不带 tag 的话前端拿到的字段名可能是Code而不是code导致n-data-table列渲染不出来。2.4 项目目录与后续扩展点建议在初始化后按这样的结构组织代码后面第三章接大模型、第四章接行情数据都能对号入座stock-ai/ ├── main.go ├── backend/ │ ├── app.go # Wails binding 入口 │ ├── llm/ │ │ └── client.go # 大语言模型调用 │ ├── market/ │ │ ├── akshare.go # A股数据 │ │ └── yfinance.go # 港股美股数据 │ └── indicators/ │ └── compute.go # 技术指标计算 └── frontend/ ├── src/ │ ├── views/ │ │ ├── Market.vue # 市场整体情绪 │ │ └── StockDetail.vue │ └── api.ts这样拆的目的很明确大语言模型和行情数据源都是可能随时换的组件。今天用 OpenAI 兼容接口明天可能换本地部署的 Ollama只要llm/client.go里的接口签名不变前端就不动。数据源同理AKShare 某天挂了就换一个实现适配层隔离得越干净越好。3. 统一情绪分数提示词设计、结构化输出与大模型调用3.1 情绪分析的输入是什么标题里的「市场整体/个股情绪分析」不是让 AI 凭空猜涨跌而是先输入客观数据再让大语言模型做语义概括和风险标注。我一般把输入拆成三个来源一是市场全景数据A 股看涨跌家数比、涨停/跌停数量、两市成交额、北向资金净流入港股看恒指涨跌幅、大市成交额、南向资金美股看三大指数涨跌幅、VIX 指数、涨跌家数。二是个股行情数据日 K 线、分时量价、主力资金净流入、换手率。三是新闻或公告摘要个股新闻标题、公司公告要点。这些数据先被处理成一段文本再作为用户消息发给大语言模型。为什么不能直接把原始 JSON 丢给模型因为 token 有限且原始数据里的噪声很多。比如 5000 只股票的列表毫无意义但「跌停 120 家、成交额 8800 亿」这就是高度浓缩的情绪特征。所以这里要做一次「数据压缩」把结构化数据转成自然语言描述大模型的工作只负责语义分析和结论生成不是数据整理。3.2 提示词模板要求模型输出稳定 JSON情绪分析最容易翻车的是输出格式不稳定。一会儿输出中文结论一会儿输出 JSON前端根本没法渲染。解决办法是用结构化 JSON 输出并在系统提示词里给死格式。package llm const systemPrompt 你是一位冷静的股票市场情绪分析师。用户会给你以下数据 1. 市场全景数据 2. 个股行情与资金数据 3. 新闻或公告摘要 请结合这些数据输出一个 JSON格式必须如下 { sentiment_score: 0, sentiment_label: 看涨|看跌|中性, key_factors: [因素1, 因素2], risk_tags: [风险1] } 约束 - sentiment_score 是 0 到 100 的整数50 表示中性越高越看涨。 - key_factors 最多列 4 条必须来自输入数据中的事实禁止编造。 - risk_tags 只在有明确风险时列出没有就返回空数组。 - 不要输出 JSON 以外的任何文字。 这里有个容易被忽略的参数temperature。情绪分析不是创作题温度太高模型会自己发挥同一个输入上午打 70 分下午打 30 分你根本不敢用。我一般把温度设成 0越接近 0 越好同时开启response_format: json_object来进一步约束输出。3.3 Go 后端调用大语言模型用标准库还是 SDK很多文章喜欢直接贴 OpenAI SDK但实际项目中我建议先用标准net/http写一层这样无论接哪个厂商模型只要兼容 OpenAI 协议就能用。下面是最小可用的调用代码package llm import ( bytes context encoding/json fmt io net/http ) type ChatMessage struct { Role string json:role Content string json:content } type chatRequest struct { Model string json:model Messages []ChatMessage json:messages Temperature float64 json:temperature ResponseFormat map[string]string json:response_format } func AnalyzeSentiment(ctx context.Context, baseURL, apiKey, model string, userContent string) (map[string]interface{}, error) { reqBody, _ : json.Marshal(chatRequest{ Model: model, Messages: []ChatMessage{ {Role: system, Content: systemPrompt}, {Role: user, Content: userContent}, }, Temperature: 0, ResponseFormat: map[string]string{type: json_object}, }) req, _ : http.NewRequestWithContext(ctx, POST, baseURL/chat/completions, bytes.NewReader(reqBody)) req.Header.Set(Content-Type, application/json) req.Header.Set(Authorization, Bearer apiKey) resp, err : http.DefaultClient.Do(req) if err ! nil { return nil, err } defer resp.Body.Close() body, _ : io.ReadAll(resp.Body) var result struct { Choices []struct { Message struct { Content string json:content } json:message } json:choices } if err : json.Unmarshal(body, result); err ! nil { return nil, fmt.Errorf(parse llm response failed: %w, err) } var sentiment map[string]interface{} json.Unmarshal([]byte(result.Choices[0].Message.Content), sentiment) return sentiment, nil }这段代码的逻辑有几个关键点。第一baseURL和model全部做成参数意味着同一条代码既可以直接连大模型服务商的 API也可以指向本地 Ollama 的http://127.0.0.1:11434/v1切换成本只是改配置。第二temperature写死在 0这是我在多次测试后确定的值后面踩坑会细说。第三接口返回的content本身是字符串需要二次解析成 map 才能读sentiment_score。注意如果模型服务商不支持response_format参数这个字段会被忽略或报错。兼容 OpenAI 协议的本地模型大多支持但个别版本需要升级。3.4 把情绪分数推给前端展示拿到结构化结果后Wails 后端可以把它缓存起来同时通过事件推给前端避免前端轮询。import github.com/wailsapp/wails/v2/pkg/runtime func (a *App) AnalyzeMarketSentiment(ctx context.Context) { // 假设 DataBuilder 负责把市场全景数据拼成 userContent userContent : a.DataBuilder.BuildMarketSummary() sentiment, err : llm.AnalyzeSentiment(ctx, cfg.BaseURL, cfg.APIKey, cfg.Model, userContent) if err ! nil { runtime.EventsEmit(ctx, sentiment:error, err.Error()) return } runtime.EventsEmit(ctx, sentiment:update, sentiment) }前端配合 NaiveUI 渲染import { useMessage } from naive-ui; import { onMounted } from vue; onMounted(() { window.runtime.EventsOn(sentiment:update, (data: any) { // data.sentiment_score 直接传给 n-progress 或 n-tag sentimentScore.value data.sentiment_score; }); });这里的取舍是把大语言模型调用放在事件里异步跑前端不用等模型响应慢时界面还能操作。如果直接做成同步 binding用户点一下按钮卡住十秒体验很差。情绪分析从触发到返回通常需要 3-8 秒走事件推送是更稳的做法。4. K线技术指标分析数据源、指标计算与AI解读管线4.1 多市场数据源AKShare 与 yfinance 怎么选K 线技术指标分析的第一步是拿到可靠的日 K 数据。如果只是个人工具我一般这样选A 股用 AKShare因为它对 A 股接口覆盖全、免费、字段比较干净港股和美股用 yfinance它通过 Yahoo 接口能拿到历史 K 线不用申请密钥。下面是两个数据源的最小示例# data_fetcher.py import akshare as ak import yfinance as yf def fetch_a_daily(code: str, days: int 120): # A股代码如 600519AKShare 返回包含 日期,开盘,收盘,最高,最低,成交量 的 DataFrame df ak.stock_zh_a_hist(symbolcode, perioddaily, adjustqfq) return df.tail(days) def fetch_hk_us_daily(code: str, days: int 120): # 港股如 0700.HK美股如 AAPL df yf.download(code, progressFalse) return df.tail(days)注意这里的adjustqfq参数。前复权数据能消除分红送股带来的跳空对计算 MACD、RSI 这种依赖连续价格的指标很重要。如果你用不复权数据一只股票 10 送 10 后价格直接腰斩RSI 会被误导成超卖。但也要记住前复权数据不适合计算目标价和持仓盈亏指标分析用前复权组合计算用原始价。4.2 指标计算MACD、RSI、BOLL 的实现细节技术指标我不建议调第三方包因为 A 股常见的几套指标算法在不同平台间有细微差异直接自己实现最可控。下面是用 Python pandas/numpy 计算 RSI 和 MACD 的代码import pandas as pd def compute_rsi(df: pd.DataFrame, period: int 14) - pd.Series: delta df[close].diff() gain delta.clip(lower0) loss -delta.clip(upper0) avg_gain gain.ewm(alpha1 / period, min_periodsperiod).mean() avg_loss loss.ewm(alpha1 / period, min_periodsperiod).mean() rs avg_gain / avg_loss rsi 100 - (100 / (1 rs)) return rsi def compute_macd(df: pd.DataFrame, fast: int 12, slow: int 26, signal: int 9): ema_fast df[close].ewm(spanfast, adjustFalse).mean() ema_slow df[close].ewm(spanslow, adjustFalse).mean() dif ema_fast - ema_slow dea dif.ewm(spansignal, adjustFalse).mean() macd_bar (dif - dea) * 2 return dif, dea, macd_barEWM 是核心。RSI 的ewm(alpha1/period)是 Wilder 平滑法不是普通移动平均MACD 必须用adjustFalse的 EMA这样昨天算出来的值和今天增量算出来的一致不会因为窗口变化产生偏差。如果你用df[close].rolling(14).mean()那种均值RSI 在数据更新时会跳变这就是很多人指标计算「今天和昨天不一致」的原因。4.3 把 K 线指标压缩成「喂给大模型的特征文本」大语言模型不理解数字表格但它能理解「RSI 14 从 62 降到 48MACD 绿柱缩短」。所以我们在把指标送进 LLM 前要做一次文本化压缩。只保留最近 5-10 根 K 线的关键特征而不是把所有历史数据塞进去。def build_indicator_text(df: pd.DataFrame) - str: df df.tail(10).copy() df[rsi] compute_rsi(df) dif, dea, macd_bar compute_macd(df) df[dif] dif df[dea] dea df[macd_bar] macd_bar latest df.iloc[-1] prev df.iloc[-2] builder [] builder.append(f最新收盘:{latest[close]:.2f}涨跌幅:{latest[pct_change]:.2f}%) builder.append(fRSI14最新值:{latest[rsi]:.1f}前值:{prev[rsi]:.1f}) builder.append(fMACD柱最新值:{latest[macd_bar]:.3f}前值:{prev[macd_bar]:.3f}DIF:{latest[dif]:.3f}DEA:{latest[dea]:.3f}) builder.append(f最近10日最高:{df[high].max():.2f}最低:{df[low].min():.2f}区间涨幅:{df[pct_change].sum():.2f}%) return \n.join(builder)这段代码产出的文本会把「AI 可以自己看 K 线」变成「AI 读取人类整理的指标特征」。这是效率最高的做法token 消耗极低。比如 10 行数据拼出来只有 300 多字模型可以快速给出解读而且要编数据都难因为所有数字都来自真实计算结果。把这段文本喂给大语言模型再让模型输出金叉、死叉、量价背离之类的技术判断才是标题里「K线技术指标分析」的完整链路。4.4 技术指标的边界哪些结论不能交给大语言模型不能指望大语言模型自己算指标也不能让它凭空预测点位。我见过有人把原始 OHLC 全部塞给模型问「明天涨还是跌」模型会给一个看似合理的回答但实际没有任何概率保证。正确的打开方式是指标计算交给代码模型只负责解读指标状态和给出候选策略同时明确告诉模型「你只能基于指标事实说话不许预测涨停」。在提示词里加一句「给出该股票当前的风险等级和值得关注的信号而不是买卖建议」能显著降低模型胡说八道的概率。5. 避坑与常见问题多市场数据、情绪分析飘移与Wails打包排查5.1 AKShare 接口返回空数据字段名经常在变现象脚本昨天跑得好好的今天ak.stock_zh_a_hist(symbol600519)返回空 DataFrame或者报字段缺失错误App 直接白屏。原因AKShare 这类免费接口本质是网页爬虫封装目标网站改版、加反爬后字段名或解析逻辑就失效了。解决不裸调 AKShare外面套一层适配层并且加基础异常捕获。比如先请求如果返回len(df) 0自动降级到备选接口并在日志打印阶段。落地做法是写一个MarketDataProvider接口有两个实现AKShare 和本地 CSV 缓存AKShare 挂了就从缓存读取最后一份数据保证 App 不闪退。5.2 同一份新闻情绪分数不稳定temperature 的坑现象同一段新闻摘要上午分析结果是 65 分看涨下午变成 40 分看跌连续调用三次三个不同结果。原因大语言模型本身有随机性我们最初没有固定temperature用默认值 1.0模型在语义模糊处会发挥。解决把情绪分析请求的temperature固定为 0这是最便宜、最有效的稳定手段。如果还不稳就做三次调用取情绪分数均值这个「投票」的工程做法比调任何花哨参数都实用。注意分数均值不是对 JSON 求平均而是分别解析sentiment_score后求算术平均key_factors取出现次数最多的那轮输出。5.3 港股/美股代码与时区错乱A股开市时你还在用美债数据现象港股用0700.HK返回的数据和软件里的行情对不上美股开盘时间算错导致技术指标序列错位。原因多市场数据源在代码命名、交易时区、复权方式上都不统一。Yahoo 用0700.HKAKShare 用00700如果不做映射前端会展示错误代码。解决建立统一代码表入库时把外部代码映射成内部market symbol比如{ market: HK, symbol: 0700, yahooSymbol: 0700.HK }。所有 K 线数据统一存 UTC 时间戳展示时再转本地时区。技术指标计算必须在同一市场交易时段下进行A 股和港股不要混成一根时间序列。5.4 Wails 在 Windows 打包后白屏WebView2 Runtime 缺失现象wails build产物在开发机上能跑拷到另一台 Windows 上双击后黑屏/白屏没有报错。原因Wails 依赖系统 WebView2 RuntimeWindows 10/11 大多数有但精简版系统或 Windows Server 没有。解决在安装检查代码里判断runtime.EventsOn(wails:ready)是否成功触发配合注册表检测 WebView2。更稳妥的做法是在项目发布说明里写明要求安装「Microsoft Edge WebView2 Runtime」或者在安装包构建脚本里用bootstrapper唤起在线安装。这个坑在 Linux 上对应的是编译时缺libwebkit2gtk-4.1-dev所以跨平台编译前先跑wails doctor。5.5 NaiveUI 高频刷新卡顿行情字段没做节流现象自选页每秒收到最新价更新n-data-table在做排序、渲染时明显掉帧滚动卡涩。原因Wails 事件推送频率高Vue 组件每次收到数据都触发响应式更新表格大面积重渲染。解决前端维护一个「数据版本」字段只有收到新batch_id才更新表格行情推送做 500ms 节流。实际处理中我用n-data-table的virtual-scroll开启虚拟滚动同时把价格组件拆成最小子组件更新只发生在价格文本节点不触发整行重绘。经过上述处理后同时追踪 50 只股票的刷新都没问题。5.6 停牌和退市股数据导致指标 NaN脏数据喂给大模型现象某只港股停牌五天日 K 缺失计算 MACD 时出现NaN文本化后喂给大语言模型模型给出「数据不足」的含糊结论。原因没有做数据清洗直接把缺失值拼进了提示词。解决在指标计算前检查数据长度少于 30 根 K 线直接跳过分析缺失日期的行dropna后如果连续缺失超过 3 根判断为停牌用停牌标记替代指标结论所有指标文本必须带「数据完整性」字段让大语言模型知道这些结论的置信度。这个字段看似冗余但在模型拒绝瞎猜时非常有用。6. 进阶技巧流式输出、本地部署大语言模型与情绪有效性验证6.1 让模型分析结果流动起来情绪分析和 K 线解读都是耗时操作与其等完成后一次性展示不如用 Wails 的事件机制做流式输出。后端在拿到大语言模型的分段响应时通过runtime.EventsEmit(ctx, llm:token, chunk)推送前端监听事件后把文本追加到 NaiveUI 的n-input或自定义流式文本块里。这样用户能看到模型一句一句「写」分析过程体验上比转圈等待好很多。关键参数是一次推理的max_tokens我习惯设 600既能完整表达分析逻辑又不会让流式展示拖太长。6.2 用 Ollama 本地部署替换在线大语言模型很多用户对股票数据敏感不愿意把持仓数据发到在线 API。常见做法是让后端baseURL可配置一键切换到本地 Ollama 服务。以 Qwen2.5-7B 为例启动命令是ollama run qwen2.5:7b它会监听http://127.0.0.1:11434/v1完全兼容第三章的调用代码。切换后提示词不用改但温度控制比在线模型更关键本地小模型在temperature0时也会偶尔不稳定所以情绪分数仍按三次投票取均值。本地部署的代价是分析速度变慢7B 模型在普通显卡上约每秒 20-30 token单次分析要 15 秒左右这是换取数据私密性的必要成本。6.3 验证情绪指标是否真的有效情绪分析做得再花哨如果分数和次日涨跌没有相关性就是自我安慰。我的验证方法是回测取历史 60 天数据每天在收盘后计算市场情绪分数记录次日指数涨跌统计分数大于等于 70 的日子中次日上涨比例。import pandas as pd def evaluate_sentiment(df: pd.DataFrame, threshold: int 70): # df 包含列date, sentiment_score, next_day_return high df[df[sentiment_score] threshold] hit_rate (high[next_day_return] 0).mean() return hit_rate, len(high)这个回测只适合验证一个趋势在你的数据源和提示词组合下情绪分数是否有区分度。如果连续两周回测的命中率都低于 50%说明提示词里的关键因子取错了或者数据源时间滞后这时候不要急着加更多指标先回头检查数据清洗。这个习惯帮我避免了不少黑匣子式的无效功能。6.4 一点经验收尾把大语言模型接进行情分析后我最大的教训是永远不要信任模型输出的数字模型只是「语义翻译器」它翻译的底层事实必须由代码保证。最早我让模型直接读原始行情 JSON它把 8 元股价解读成 8 万元让我差点做出一个离谱的仓位策略。从那以后所有喂给模型的数据都先在本地完成单位换算和字段清洗模型输出只做展示和参考。这条路适合真心想把 AI 落到投资决策工具里的开发者但请记住它分析的是情绪和技术信号不是确定性收益。希望这篇笔记能帮你在搭建 Wails NaiveUI 大语言模型股票分析工具时少踩几个坑复现出真正敢用、能看效果的产品原型。本文还有配套的精品资源点击获取