先分享一套近期在做的 AI 编程落地思路:借助大模型,从零搭建一个属于自己的股票分析系统。整个项目包含数据获取、技术指标计算、大模型深度分析、交互页面展示和 Docker 部署五个环节,核心方法可以概括为 FDE(Flow-Driven Engineering,流程驱动工程)——先把业务链路拆成清晰的数据流,再让大模型在每一个关键节点辅助完成代码生成、参数调优和问题排查。这篇文章会把完整流程、可运行代码和踩坑点都整理出来,适合正在学习大模型应用开发、AI 编程,以及想快速搭建量化分析工具的开发者。
1. 背景与核心概念
1.1 什么是 FDE
FDE 的全称是 Flow-Driven Engineering,即“流程驱动工程”。它不是某一个具体的框架或软件,而是一套基于大模型 AI 编程的工程组织方式。
传统开发流程里,我们习惯把系统拆成模块、类、函数,再逐个实现。FDE 的切入角度不同,它强调先画清一条完整的业务数据流:
数据输入 -> 数据清洗 -> 指标计算 -> 分析判断 -> 结果输出 -> 部署运维这条链路中的每一环都有自己的输入、处理和输出。当我们把链路界定清楚之后,大模型就能更好地理解上下文,生成的代码也不容易跑偏。比如要做一个股票分析系统,先梳理链路:
- 输入:股票代码、时间范围、行情源;
- 清洗:去除停牌日、空值、异常跳空数据;
- 计算:移动平均线、RSI、MACD 等技术指标;
- 分析:把行情摘要和指标结果交给大模型,生成投资观点和风险提示;
- 输出:以网页形式展示图表、指标表和 AI 结论;
- 部署:通过 Docker 打包,运行在服务器或本地。
FDE 的核心价值在于,把 AI 编程从一个“写代码”的动作,升级为“设计一条可执行、可验证的数据流”。对开发者来说,这种思维方式也能大幅减少大模型生成的代码与业务需求不一致的问题。
1.2 为什么用大模型来做股票分析系统
传统股票分析系统通常包括行情数据、技术指标和图表展示,这类系统本身不难做。但用户真正需要的往往不只是“金叉死叉”这种机械信号,而是能结合当前行情特征、指标变化和市场环境,生成一段有逻辑、有风险意识的解读。
这时候大模型的价值就体现出来了。它可以完成三类典型任务:
- 自然语言转查询:用户输入“帮我分析最近 60 天的走势和成交量变化”,大模型理解意图后,调用对应函数获取数据并生成分析;
- 结构化数据总结:把最近 N 天的 K 线数据、均线排列、RSI 数值整理成摘要,让大模型输出通俗易懂的结论;
- 智能问答与复盘:把历史行情、指标、新闻摘要组合成上下文,让大模型回答用户提出的具体问题,比如“当前是否处于超卖区间”。
所以,本文实现的不是一个大而全的商业炒股软件,而是一个“数据 + 指标 + 大模型”的轻量级分析工作台。它足够灵活,方便你后续接入更多数据源、更换不同大模型,甚至扩展成定时任务。
1.3 系统整体设计
系统架构可以简化为三层:
| 层级 | 主要职责 | 对应技术 |
|---|---|---|
| 数据层 | 获取股票行情、缓存数据、数据清洗 | yfinance / akshare、pandas |
| 智能层 | 计算指标、调用大模型、生成分析结论 | pandas、OpenAI 兼容接口 |
| 展示层 | 图表展示、指标表格、AI 对话页面 | Streamlit、Plotly |
这三层是分离的。数据层只负责“把数据送到内存里”,智能层专注“计算和推理”,展示层负责“让用户看得见”。FDE 思路下,每一层都可以独立测试,也方便后续替换。
2. 环境准备与版本说明
2.1 运行环境
本文示例以 Python 项目为主,建议使用 Python 3.10 或更高版本。操作系统方面,Windows、macOS、Linux 均可,需要注意的地方是部分数据源对系统网络环境有要求,如果拉取行情失败,可以换成 akshare 或本地 CSV 数据。
推荐使用虚拟环境:
python -m venv stock_env source stock_env/bin/activate # Windows 下使用 stock_env\Scripts\activate2.2 技术栈清单
| 组件 | 作用 | 说明 |
|---|---|---|
| streamlit | Web 交互页面 | 轻量、适合快速搭建数据应用 |
| pandas | 数据处理与指标计算 | 核心依赖 |
| plotly | 交互式图表 | 展示 K 线、均线 |
| yfinance | 获取美股行情 | 也可换 akshare 获取 A 股 |
| openai | 调用大模型 API | 支持 DeepSeek、Ollama 等 OpenAI 兼容服务 |
| docker | 容器化部署 | 统一运行环境 |
版本建议:
streamlit>=1.30 pandas>=2.0 plotly>=5.18 yfinance>=0.2.40 openai>=1.30.0 akshare>=1.13.0具体版本需要根据你的项目实际情况调整,本文示例以常见环境为例,重点演示配置思路。如果你的 Python 版本较旧,先升级 Python,再安装依赖会更省心。
2.3 安装依赖
pip install streamlit pandas plotly yfinance openai akshare安装完成后,可以快速验证:
python -c "import pandas; print(pandas.__version__)" python -c "import streamlit; print(streamlit.__version__)"如果输出正常,说明环境准备完成。
3. 核心模块实现
3.1 股票数据获取模块
数据层是所有分析的前提。这里以 yfinance 为例,它可以通过接口直接获取历史行情,适合快速实现。同时兼容 akshare 的思路也会在注释里给出。
创建stock_data.py:
import pandas as pd import yfinance as yf def fetch_stock_data(symbol: str, period: str = "6mo", interval: str = "1d") -> pd.DataFrame: """ 获取股票历史行情。 :param symbol: 股票代码,例如 600519.SS 代表贵州茅台 :param period: 1d, 5d, 1mo, 3mo, 6mo, 1y, 2y, 5y, 10y, max :param interval: 1m, 2m, 15m, 1h, 1d, 1wk, 1mo :return: DataFrame,包含 Open High Low Close Volume 等字段 """ ticker = yf.Ticker(symbol) df = ticker.history(period=period, interval=interval, auto_adjust=True) if df.empty: raise ValueError(f"未获取到 {symbol} 的数据,请检查代码或网络环境") # 统一列名为大写,便于后续使用 df = df.reset_index() df.columns = [col.title() for col in df.columns] return df def clean_stock_data(df: pd.DataFrame) -> pd.DataFrame: """ 数据清洗:去空值、去除成交量异常为 0 的行。 """ df = df.dropna(subset=["Open", "High", "Low", "Close"]) df = df[df["Volume"] > 0] return df.reset_index(drop=True)这里需要注意:
auto_adjust=True表示使用复权价格,更贴近真实收益;- A 股代码在 yfinance 中需要带后缀,例如
600519.SS; - 如果网络请求失败,建议在项目里增加重试逻辑或切换数据源。
3.2 技术指标计算模块
技术指标我们不依赖 TA-Lib,因为它在 Windows 上安装比较麻烦。用 pandas 手写几个常用指标,代码可控性更高,也方便后续扩展。
创建indicators.py:
import pandas as pd def add_moving_average(df: pd.DataFrame, windows: list = [5, 10, 20, 60]) -> pd.DataFrame: """ 增加多条移动平均线,例如 MA5、MA10、MA20。 """ df = df.copy() for w in windows: df[f"MA{w}"] = df["Close"].rolling(window=w).mean() return df def add_rsi(df: pd.DataFrame, period: int = 14) -> pd.DataFrame: """ 计算 RSI 相对强弱指标。 RSI > 70 表示超买,RSI < 30 表示超卖。 """ df = df.copy() delta = df["Close"].diff() gain = delta.clip(lower=0) loss = -delta.clip(upper=0) avg_gain = gain.rolling(window=period).mean() avg_loss = loss.rolling(window=period).mean() rs = avg_gain / avg_loss df["RSI"] = 100 - (100 / (1 + rs)) return df def add_macd(df: pd.DataFrame, fast: int = 12, slow: int = 26, signal: int = 9) -> pd.DataFrame: """ 计算 MACD 指标。 """ df = df.copy() ema_fast = df["Close"].ewm(span=fast, adjust=False).mean() ema_slow = df["Close"].ewm(span=slow, adjust=False).mean() df["DIF"] = ema_fast - ema_slow df["DEA"] = df["DIF"].ewm(span=signal, adjust=False).mean() df["MACD"] = (df["DIF"] - df["DEA"]) * 2 return df def compute_all_indicators(df: pd.DataFrame) -> pd.DataFrame: """ 一键计算 MA、RSI、MACD 指标。 """ df = add_moving_average(df) df = add_rsi(df) df = add_macd(df) return df移动平均线能反映趋势方向,RSI 能提示超买超卖,MACD 能捕捉短期动能变化。实际使用中,不需要所有指标同时出现,你可以根据分析需求按需选择。
3.3 大模型接口封装
大模型是这个系统的“分析大脑”。为了兼容不同服务商,我建议按 OpenAI 兼容接口来封装,这样无论是 DeepSeek、Qwen、通义,还是本地部署的 Ollama、vLLM,只要提供base_url和api_key就能直接切换。
创建llm_client.py:
from openai import OpenAI class LLMClient: """ 大模型客户端,使用 OpenAI 兼容协议。 可通过环境变量配置 base_url 和 api_key。 """ def __init__(self, model_name: str = None): self.api_key = os.getenv("LLM_API_KEY", "EMPTY") self.base_url = os.getenv("LLM_BASE_URL", "https://api.openai.com/v1") self.model_name = model_name or os.getenv("LLM_MODEL", "gpt-3.5-turbo") self.client = OpenAI(api_key=self.api_key, base_url=self.base_url) def chat(self, messages: list, temperature: float = 0.3, max_tokens: int = 2000): """ 发送对话消息。 :param messages: 形如 [{"role": "system", "content": "..."}, {"role": "user", "content": "..."}] """ response = self.client.chat.completions.create( model=self.model_name, messages=messages, temperature=temperature, max_tokens=max_tokens, ) return response.choices[0].message.content使用时的环境变量示例:
export LLM_API_KEY="你的 API Key" export LLM_BASE_URL="https://api.deepseek.com/v1" export LLM_MODEL="deepseek-chat"如果你希望完全本地化运行,也可以将base_url指向本机 Ollama:
export LLM_BASE_URL="http://localhost:11434/v1" export LLM_MODEL="qwen2.5:14b"这样设计的好处是代码不绑定具体厂商,换模型只改环境变量,不影响业务逻辑。
3.4 提示词工程
大模型能否输出高质量分析,取决于提示词设计。在股票分析场景中,我通常使用“角色设定 + 任务描述 + 数据上下文 + 输出要求”四段式结构。
创建prompts.py:
def build_stock_analysis_prompt(symbol: str, recent_data_desc: str, indicator_desc: str) -> list: system_prompt = ( "你是一名专业的股票分析师,擅长技术面分析。" "你给出的分析必须基于用户提供的行情数据和指标数据," "不能编造不存在的价格信息。" "回答要结构化,包含趋势判断、支撑压力位、风险提示三个部分。" ) user_prompt = f""" 请分析 {symbol} 的最新走势。 【最近行情摘要】 {recent_data_desc} 【核心指标】 {indicator_desc} 【输出要求】 1. 用 200 字以内概括当前趋势; 2. 指出关键支撑位和压力位; 3. 给出风险提示,说明指标局限性; 4. 不构成投资建议。 """ return [ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_prompt.strip()} ]这里的亮点是“不编造价格”和“不构成投资建议”。大模型很容易一本正经地补充细节,因此我们要在提示词里明确约束数据边界,降低幻觉风险。
4. 实战:从零搭建股票分析系统
4.1 创建项目结构
建议按下面的目录结构组织代码:
stock-ai/ ├── stock_data.py ├── indicators.py ├── llm_client.py ├── prompts.py ├── app.py ├── requirements.txt ├── .env.example └── Dockerfile4.2 编写数据摘要生成模块
大模型无法直接处理几千行 K 线数据,所以我们需要把原始数据变成“摘要”。合理的做法是取最近若干条数据,再加上统计特征。
在indicators.py中新增函数:
def generate_data_summary(df: pd.DataFrame, tail: int = 10) -> str: """ 将最近 tail 行数据转换为适合大模型阅读的文本摘要。 """ recent = df.tail(tail) lines = [] for _, row in recent.iterrows(): lines.append( f"{row['Date'].strftime('%Y-%m-%d')} " f"开 {row['Open']:.2f} 高 {row['High']:.2f} " f"低 {row['Low']:.2f} 收 {row['Close']:.2f} " f"量 {row['Volume']:.0f}" ) return "\n".join(lines) def generate_indicator_desc(df: pd.DataFrame) -> str: """ 生成指标汇总文本。 """ latest = df.iloc[-1] desc = [] for col in ["MA5", "MA10", "MA20", "MA60", "RSI"]: if col in df.columns: desc.append(f"{col}: {latest[col]:.2f}") if "DIF" in df.columns and "DEA" in df.columns: desc.append(f"DIF: {latest['DIF']:.2f}") desc.append(f"DEA: {latest['DEA']:.2f}") desc.append(f"MACD: {latest['MACD']:.2f}") return ",".join(desc)这一步非常关键。把连续数据压缩成“最近 10 天 + 指标快照”,既保留趋势细节,又控制 token 数量,能明显降低 API 费用。
4.3 搭建 Streamlit 页面
创建app.py:
import os import pandas as pd import plotly.graph_objects as go import streamlit as st from stock_data import fetch_stock_data, clean_stock_data from indicators import compute_all_indicators, generate_data_summary, generate_indicator_desc from llm_client import LLMClient from prompts import build_stock_analysis_prompt st.set_page_config(page_title="AI 股票分析系统", layout="wide") st.title("📈 用大模型打造你的专属股票分析系统") with st.sidebar: symbol = st.text_input("股票代码", value="AAPL") period = st.selectbox("周期", ["3mo", "6mo", "1y", "2y"], index=1) use_ai = st.checkbox("启用大模型分析", value=True) if use_ai: st.info("大模型将从行情与指标中生成分析结论,仅供参考。") @st.cache_data(ttl=600) def load_data(symbol, period): df = fetch_stock_data(symbol, period) df = clean_stock_data(df) df = compute_all_indicators(df) return df try: df = load_data(symbol, period) except Exception as e: st.error(f"数据获取失败:{e}") st.stop() st.subheader(f"{symbol} 最新行情") latest = df.iloc[-1] col1, col2, col3, col4 = st.columns(4) col1.metric("收盘价", f"{latest['Close']:.2f}", f"{latest['Close'] - df.iloc[-2]['Close']:.2f}") col2.metric("成交量", f"{latest['Volume']:.0f}") col3.metric("RSI", f"{latest['RSI']:.2f}") col4.metric("MACD", f"{latest['MACD']:.2f}") st.subheader("K 线与均线") fig = go.Figure() fig.add_trace(go.Candlestick( x=df["Date"], open=df["Open"], high=df["High"], low=df["Low"], close=df["Close"], name="K线" )) for ma in ["MA5", "MA10", "MA20", "MA60"]: if ma in df.columns: fig.add_trace(go.Scatter(x=df["Date"], y=df[ma], name=ma, mode="lines")) fig.update_layout(xaxis_rangeslider_visible=False, height=500) st.plotly_chart(fig, use_container_width=True) st.subheader("最近行情数据") st.dataframe(df.tail(20)) if use_ai: with st.spinner("大模型正在分析行情,请稍候..."): try: client = LLMClient() data_summary = generate_data_summary(df) indicator_desc = generate_indicator_desc(df) messages = build_stock_analysis_prompt( symbol=symbol, recent_data_desc=data_summary, indicator_desc=indicator_desc ) result = client.chat(messages) st.subheader("🤖 AI 分析结论") st.write(result) except Exception as e: st.error(f"大模型分析失败:{e},请检查 API 配置是否填写正确。")运行方式:
streamlit run app.py浏览器访问http://localhost:8501就能看到系统界面。
4.4 运行与验证
在本地运行后,你会看到:
- 左侧可以输入股票代码、选择周期;
- 中间区域展示 K 线图和均线;
- 下方可以查看最近 20 条行情数据;
- 勾选“启用大模型分析”后,页面会输出结构化的 AI 解读。
预期输出的大模型分析大致如下:
【趋势判断】 近 20 日股价维持在 MA20 上方,整体处于上升通道,但短期 RSI 接近 65,动能有所减弱。 【支撑与压力】 短期支撑位在 178.5 附近,压力位在 185.2 附近。 【风险提示】 技术指标存在滞后性,若成交量不能持续放大,需警惕高位回调风险。以上内容不构成投资建议。这里的数值只是示例,实际输出会随行情变化。
4.5 如果是 A 股数据怎么做
如果你更关注 A 股,可以把数据层换成 akshare,核心代码改动很小。示例:
import akshare as ak def fetch_stock_data_akshare(symbol: str, period_days: int = 120): """ 使用 akshare 获取 A 股日线数据。 :param symbol: 例如 "600519" """ df = ak.stock_zh_a_hist( symbol=symbol, period="daily", start_date="20240101", end_date="20241231", adjust="qfq" ) df.rename(columns={ "日期": "Date", "开盘": "Open", "最高": "High", "最低": "Low", "收盘": "Close", "成交量": "Volume" }, inplace=True) return df注意 akshare 接口有时会随数据源页面结构变化而变化,如果遇到字段对不上,需要根据实际返回调整列名。
5. 部署实战:Docker 容器化
5.1 编写 Dockerfile
为了让系统能部署到任意服务器,我们使用 Docker 打包。Streamlit 应用本身很轻量,镜像我通常会基于 Python 3.10-slim。
创建Dockerfile:
FROM python:3.10-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . EXPOSE 8501 CMD ["streamlit", "run", "app.py", "--server.port=8501", "--server.address=0.0.0.0"]5.2 构建镜像并运行
docker build -t stock-ai:latest . docker run -d --name stock-ai \ -p 8501:8501 \ -e LLM_API_KEY="你的 API Key" \ -e LLM_BASE_URL="https://api.deepseek.com/v1" \ -e LLM_MODEL="deepseek-chat" \ stock-ai:latest运行后,访问服务器 IP 的 8501 端口即可。如果是在云服务器上部署,还需要在安全组放行 8501 端口。生产环境建议用 Nginx 反向代理并配置 HTTPS,避免 API Key 和页面被明文传输。
5.3 环境变量说明
| 环境变量 | 是否必填 | 默认值 | 说明 |
|---|---|---|---|
| LLM_API_KEY | 使用大模型时必填 | EMPTY | 大模型服务商提供的 API Key |
| LLM_BASE_URL | 可选 | OpenAI 官方 | 可切换为 DeepSeek、Ollama 等 |
| LLM_MODEL | 可选 | gpt-3.5-turbo | 按照服务商支持的模型名称填写 |
在本地开发时,可以把变量写入.env文件,但建议不要提交到 Git 仓库。示例.env.example:
LLM_API_KEY=your-api-key LLM_BASE_URL=https://api.deepseek.com/v1 LLM_MODEL=deepseek-chat6. 常见问题与排查思路
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| yfinance 获取数据为空 | 网络受限、股票代码格式错误 | 检查代码后缀,例如 A 股使用 600519.SS;切换 akshare 数据源 |
| Streamlit 页面白屏 | 依赖版本冲突 | 重建虚拟环境,按 requirements.txt 重新安装 |
| 大模型返回结果不相关 | 提示词未约束数据范围 | 在 system prompt 中强调“只能基于给定数据” |
| 大模型分析超时 | 请求数据量过多 | 缩短 recent_data_desc 的长度,只保留最近 10 条左右 |
| Docker 容器启动后无法访问 | 端口未映射或安全组未放行 | 检查 docker run 的 -p 参数,确认服务器防火墙规则 |
| RSI 出现 NaN | 数据量不足 | 加大周期参数,例如从 6mo 改为 1y |
| API Key 泄露风险 | 硬编码在代码里 | 改用环境变量管理,生产环境使用密钥管理工具 |
排查思路可以总结成三步:先看数据层是否拿到数据,再看指标层是否为 NaN,最后看大模型层是否成功调用。只要数据链路通畅,系统基本就能正常运行。
7. 最佳实践与工程建议
7.1 大模型输出需要增加校验
大模型生成的内容不适合直接作为事实。建议在展示前做两个校验:
- 是否包含明确的“不构成投资建议”表述;
- 是否包含数据和风险提示等关键结构。
如果输出缺失,可以增加重试机制或提示词后处理。更严谨的做法是让大模型输出 JSON 格式,再进行结构化解析。
7.2 数据缓存与成本控制
股票行情一天内变化不大,不需要每次访问页面都重新拉取。利用 Streamlit 的@st.cache_data(ttl=600)可以缓存 10 分钟,减少请求次数。
大模型调用也是成本来源。建议增加以下控制:
- 指标摘要只传最近 10 条数据;
- 用户点击“生成分析”按钮时才调用,而不是每次刷新都调用;
- 对话历史限制上下文长度;
- 使用
temperature=0.3等较低参数,提高回答稳定性。
7.3 安全边界
整个系统最需要注意的是 API Key 安全。不要把 Key 写入前端页面,也不要提交到公开仓库。生产环境建议使用 Docker 环境变量或者云厂商的密钥管理服务。
如果系统部署到公网,建议在 Nginx 或网关层增加基础认证,避免任何人都能访问你的分析页面,防止被刷接口产生费用。
7.4 模型选择建议
| 场景 | 推荐方案 | 说明 |
|---|---|---|
| 快速体验 | 云端 API | 免去本地 GPU 成本,适合原型 |
| 数据敏感 | 本地部署 Ollama | 数据不出内网,满足合规要求 |
| 高并发生产 | 私有化部署 vLLM | 吞吐量高,适合团队使用 |
FDE 方法在这里体现得很明显:模型选择不应该是写死代码,而是作为流程中的一个节点,通过环境变量随时切换。
8. 总结与下一步学习路线
本文从 FDE 流程驱动工程的角度,完整实现了一个基于大模型的股票分析系统,包含数据获取、指标计算、大模型分析、Streamlit 展示和 Docker 部署。你可以基于这套代码继续扩展:
- 接入更多数据源,例如 tushare、Wind、本地数据库;
- 增加基本面分析能力,把财报数据导入大模型上下文;
- 加入定时任务,每天收盘后自动生成分析报告并推送到钉钉或飞书;
- 引入向量数据库,构建历史复盘问答机器人;
- 使用多模型投票机制,让多个大模型分别分析并汇总观点。
如果本文对你有帮助,可以收藏备用,也欢迎在实际项目里验证 FDE 这套方法。下一步建议你先跑通本地版,再尝试 Docker 部署,最后逐步把数据源换成自己常用市场的真实数据。