ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

每日股票分析系统:从数据采集到报告生成的完整实践

每日股票分析系统:从数据采集到报告生成的完整实践 每天开盘前你是不是也要经历这样一段流程先打开三四个行情软件看指数再翻一遍财经新闻和热门板块最后在自选股里逐一检查均线、成交量、MACD 这些指标等把所有信息拼凑完一个上午已经过去了。更尴尬的是即使花了一两个小时你依然很难说清楚今天的市场到底处于什么状态哪些信号值得关注哪些变化只是噪音。这就是我关注daily_stock_analysis这个项目的原因。从名字就能看出来它的目标是做“每日股票分析”。但它不是一个带界面的炒股软件也不是那种号称能预测涨跌的玄学工具。它更像是一个自动化的数据分析流水线每天定时去抓取行情数据计算一批技术指标筛选出值得关注的股票和信号最后生成一份结构化的分析报告。你早上打开报告几秒钟就能掌握当天需要了解的客观信息。这篇文章会从项目核心思路出发带你完整拆解一个每日股票分析系统应该具备的模块、代码实现和工程落地方式。我会先讲清楚系统架构和数据流再逐步给出数据获取、指标计算、可视化、报告生成这几个核心环节的完整示例。无论你是想把项目跑起来还是想自己动手写一套类似的分析工具这篇文章都能给你一条清晰可执行的路径。1. 每日股票分析系统到底解决什么问题先别急着看代码想清楚一个问题为什么需要一个“每日股票分析系统”如果你只是偶尔看一眼股票确实不需要。打开行情软件看两眼指数和自选股就够了。但如果你有固定的复盘习惯或者正在学习量化投资就会发现人工分析有几个绕不开的痛点。第一个痛点是信息过载。A 股市场五千多只股票每个交易日会产生海量的行情数据。靠肉眼去翻涨幅榜、成交量异动、板块轮动效率极低而且很容易被当天最显眼的热门股带偏注意力看不到真正值得关注的资金流向和趋势变化。第二个痛点是判断标准不统一。同样一只票有人看均线有人看突破有人看量能。如果没有统一的计算口径和筛选规则每天的复盘结论就会飘忽不定。今天觉得它放量要启动明天又觉得它冲高乏力该离场最后被短期的随机波动反复打脸。第三个痛点是时间成本。数据分析本身并不难但每天重复做就非常消耗精力。下载数据、算指标、翻图表、做记录一套流程走下来至少要一两个小时。而这些步骤其实完全可以用脚本自动化每天定时执行醒来就能看到结果。daily_stock_analysis这类项目真正降低的不是“预测股票走势”的难度而是每天获取信息、整理信息、沉淀判断素材的时间成本。它把“今天市场怎么样、哪些股票触发了我关心的信号”这个问题的回答过程从手动翻看变成了自动生成。这也意味着它的核心价值在于把分析过程标准化、可重复、可追溯而不是给出什么稳赚不赔的买卖点。所以适合这个项目的人主要有三类一是做每日复盘但不想在重复劳动上花时间的个人投资者二是正在学习 Python 数据分析想用真实场景练手的开发者三是有量化交易基础想搭建一套预警筛选工具的策略研究者。如果你只是想要一个直接告诉你“明天买什么”的工具那它不适合你任何基于历史数据的分析工具都无法承诺这种结论。明白了这个前提我们再来看系统的整体设计。2. 每日股票分析系统的整体架构一个完整的每日股票分析系统从数据流的角度看可以拆成五个模块数据采集、数据清洗、指标计算、信号筛选、报告输出。每个模块只负责一件事模块之间通过标准的数据结构衔接这样既方便单独调试也方便以后替换数据源或新增分析维度。模块划分如下模块职责输入输出数据采集从数据源获取行情数据股票代码列表、日期范围原始 DataFrame数据清洗处理缺失值和异常值原始 DataFrame干净的行情数据指标计算计算技术指标行情数据带指标列的 DataFrame信号筛选根据规则生成交易信号指标数据触发明细报告输出生成图表和文字报告分析结果Markdown / HTML / 图片数据流方向是单向的采集 → 清洗 → 计算 → 筛选 → 输出。这样设计的核心好处是任何一步出问题都可以单独定位。比如收盘后数据源没有更新你只需要重跑采集模块后面所有步骤可以复用上次的结果缓存不必全部重来。在这个流程里最容易被忽略的是“数据清洗”环节。很多新手写股票分析脚本拿到数据就开始算指标结果算出来的均线、RSI 全都不对最后花大量时间排查才发现是原始数据里混入了停牌期间的空值或者除权除息导致的价格跳变。真实市场数据不像教科书里的样例那么干净清洗这一步并不是可有可无的仪式而是决定后续分析是否可信的地基。系统的运行方式也不复杂。最朴素的方案是每天早上开盘前手动执行一次脚本进阶方案是用定时任务触发每天收盘后自动运行生成报告后推送到邮件、企业微信或者本地文件夹。后面章节我会给出两种运行方式的完整配置。还有一个容易踩的坑是数据库选型。刚开始做完全不需要上 MySQL 或 PostgreSQL直接用 CSV 文件按日期分目录存储就够了。等数据量积累到一定程度再考虑用 SQLite 或 ClickHouse 这类更适合时序数据的存储方案。理由很简单日线级别的分析一天几千只股票数据量也就是几万行CSV 完全能扛住过早引入数据库只会增加部署复杂度。3. 环境准备与依赖安装在动手写代码之前先把运行环境准备好。项目本身没有绑定特定的操作系统Windows、macOS、Linux 都可以运行只要 Python 版本不低于 3.8 即可。本文以 Python 3.10 为例演示版本差异不大逻辑完全通用。需要安装的第三方库主要有以下几个pandas数据处理的核心库几乎所有数据操作都依赖它numpy数值计算底层库很多指标计算会用到akshare开源财经数据接口库用于获取 A 股行情数据matplotlib生成静态图表适合嵌入分析报告jinja2模板引擎用于把分析结果渲染成 HTML 报告其中 akshare 是获取数据的关键。它是一个开源项目聚合了多个公开数据源不用注册账号就能获取 A 股历史行情数据对个人学习研究非常友好。不过要特别提醒一句akshare 的接口底层依赖第三方网站的数据接口对方接口变动或限制访问频率时akshare 可能需要更新版本才能继续使用。所以安装时建议直接安装最新版本遇到数据获取失败先尝试升级这个库。创建虚拟环境并安装依赖的命令如下# 创建项目目录 mkdir daily_stock_analysis cd daily_stock_analysis # 创建虚拟环境Windows python -m venv venv venv\Scripts\activate # 创建虚拟环境macOS / Linux python3 -m venv venv source venv/bin/activate # 升级 pip 并安装依赖 pip install --upgrade pip pip install pandas numpy akshare matplotlib jinja2安装完成后可以快速验证依赖是否就绪python -c import pandas, numpy, akshare, matplotlib, jinja2; print(all dependencies ok)如果输出all dependencies ok说明环境没问题可以开始写代码了。这里再提醒一个在 Windows 上很常见的问题matplotlib 默认不支持中文字体图表里的中文标题和标签会显示成方块。解决方法是手动指定中文字体后面写图表代码时我会给出具体配置。macOS 和 Linux 上一般不需要额外处理但如果你的系统没装中文字体也需要手动指定。4. 股票数据获取模块数据是整个分析系统的起点这个模块直接决定后续所有分析是否有意义。本节先从“怎么获取数据”开始再给出“获取哪些数据”和“存到哪里”的具体方案。4.1 获取股票列表A 股股票数量逐年变化手动维护一份清单既不现实也容易出错。更稳妥的方式是通过 akshare 的接口动态获取然后保存到本地复用。下面这段代码演示了如何拉取全部 A 股股票的代码和名称# 文件路径data_fetcher.py import akshare as ak import pandas as pd def fetch_stock_list(): 获取A股全部股票列表返回DataFrame df ak.stock_info_a_code_name() print(f获取到 {len(df)} 只A股股票) return df if __name__ __main__: stock_list fetch_stock_list() stock_list.to_csv(data/stock_list.csv, indexFalse, encodingutf-8-sig)这段代码会把股票列表保存为data/stock_list.csv。utf-8-sig编码是为了让 Excel 直接打开时中文不乱码如果你不打算用 Excel 查看用默认的utf-8也完全可以。运行前需要先创建data目录mkdir -p data python data_fetcher.py第一次运行时akshare 会自动请求远端接口通常需要几秒钟。如果出现网络报错先检查能否正常访问目标数据源再确认 akshare 是不是最新版本。4.2 获取日线行情拿到股票列表后就可以逐只获取个股的历史日线数据了。下面这段代码演示了获取单只股票近一年日线数据并保存为 CSV 的过程# 继续在 data_fetcher.py 中添加 import datetime def fetch_daily_data(stock_code, start_date, end_date): 获取单只股票的日线行情 stock_code: 股票代码如 600519 start_date: 起始日期如 20240101 end_date: 结束日期如 20241231 df ak.stock_zh_a_hist( symbolstock_code, perioddaily, start_datestart_date, end_dateend_date, adjustqfq # 前复权自动处理除权除息造成的价格跳空 ) return df if __name__ __main__: end datetime.date.today() start end - datetime.timedelta(days365) df fetch_daily_data(600519, start.strftime(%Y%m%d), end.strftime(%Y%m%d)) print(df.head()) print(df.tail())这里一个关键参数是adjustqfq即前复权。为什么必须用复权数据因为股票分红送转后价格会向下跳空如果直接用原始价格计算均线和涨跌幅会造成虚假的“暴跌”信号指标计算也会失真。前复权是以当前价格为基准把历史价格统一调整让价格走势保持连续。做趋势分析和技术指标计算时这是必备选项。4.3 数据存储设计对于每日分析任务我建议按日期分目录存储原始数据目录结构如下data/ ├── stock_list.csv ├── daily/ │ ├── 2024-12-01/ │ │ ├── 600519.csv │ │ ├── 000001.csv │ │ └── ... │ └── 2024-12-02/ │ ├── 600519.csv │ └── ... └── reports/这样做的好处是每天生成的数据天然带有时间维度后续回溯某一天的分析结论时直接翻对应日期的目录即可不需要额外维护元数据。再加上日线数据量本身不大这种文件存储方式在很长一段时间内都足够高效。4.4 批量获取的注意事项批量获取全市场数据时需要特别注意请求频率。akshare 的接口底层调用的是公开网站接口如果短时间内请求过于密集很容易触发对方的频率限制表现为突然出现的超时或空数据。一个典型的稳妥策略是在每只股票请求之间加入 0.3 到 1 秒的随机延时同时失败后重试两到三次。示例代码如下import time import random def batch_fetch(stock_list, start_date, end_date): failed [] for idx, row in stock_list.iterrows(): code row[code] try: df fetch_daily_data(code, start_date, end_date) if df is not None and not df.empty: df.to_csv(fdata/daily/{end_date}/{code}.csv, indexFalse) print(f[{idx 1}/{len(stock_list)}] {code} ok) else: failed.append(code) print(f[{idx 1}/{len(stock_list)}] {code} empty) except Exception as e: failed.append(code) print(f[{idx 1}/{len(stock_list)}] {code} error: {e}) # 随机延时避免频率过高 time.sleep(random.uniform(0.3, 1.0)) print(f完成失败 {len(failed)} 只: {failed}) return failed全市场每日抓取虽然可行但耗时较长对于个人学习和分析来说更实用的做法是先维护一个自选股列表只对重点关注的一二十只股票做每日分析。这样既能保证数据质量又不会因为请求频率问题频繁失败。如果你想做全市场扫描建议把调度频率控制在每日一次并尽量在收盘后错峰执行。5. 技术指标计算与交易信号数据拿到手后下一步就是计算指标。指标的意义不在于预测未来而是用统一的标准描述当前市场的状态让决策有据可依。这里重点讲三个最常用、也最容易上手的指标移动平均线MA、相对强弱指标RSI和指数平滑异同移动平均线MACD。5.1 指标计算的通用工具函数pandas 本身提供了很强的计算能力配合 numpy 可以快速完成指标计算。先写一个工具函数统一处理前复权后的数据格式# 文件路径indicators.py import pandas as pd import numpy as np def load_stock_data(csv_path): 加载CSV行情数据统一列名和日期格式 df pd.read_csv(csv_path) # akshare返回的列名是中文统一转成英文方便后续处理 df.columns [date, open, close, high, low, volume, amount, amplitude, pct_change, change, turnover] df[date] pd.to_datetime(df[date]) df df.sort_values(date).reset_index(dropTrue) return df这里有一个细节akshare 返回的列名随版本升级可能略有差异所以实际使用前最好先df.columns打印确认。写代码时看到具体列名再调整映射关系不要直接照搬。5.2 计算 MA、RSI、MACD接下来实现指标计算def calculate_indicators(df): 在行情数据基础上计算技术指标 新增列MA5, MA10, MA20, RSI14, MACD, MACD_signal, MACD_hist result df.copy() # 移动平均线 MA result[MA5] result[close].rolling(window5).mean() result[MA10] result[close].rolling(window10).mean() result[MA20] result[close].rolling(window20).mean() # 相对强弱指标 RSI14日 delta result[close].diff() gain delta.clip(lower0) loss -delta.clip(upper0) avg_gain gain.rolling(window14).mean() avg_loss loss.rolling(window14).mean() rs avg_gain / avg_loss result[RSI14] 100 - (100 / (1 rs)) # MACD12, 26, 9 ema12 result[close].ewm(span12, adjustFalse).mean() ema26 result[close].ewm(span26, adjustFalse).mean() result[MACD] ema12 - ema26 result[MACD_signal] result[MACD].ewm(span9, adjustFalse).mean() result[MACD_hist] result[MACD] - result[MACD_signal] return result三个指标的计算逻辑都不复杂但背后含义值得解释一下MA移动平均线是最朴素的趋势指标MA5 和 MA10 反映短期趋势MA20 反映中期趋势。当价格站上短期均线且短期均线拐头向上说明短期趋势偏强相反则偏弱。它最大的优点是直观最大的缺点是滞后。RSI 衡量一段时间内涨跌幅的相对强弱取值 0 到 100。通常以 70 和 30 作为超买超卖的参考阈值但不同股票和不同周期适用的阈值会有差异。RSI 在震荡行情中参考价值更高在强趋势行情中容易出现钝化。MACD 是趋势跟随型指标快线DIF上穿慢线DEA时产生金叉看多信号下穿时产生死叉看空信号。零轴上方和下方的金叉死叉含义也不同这一点新手经常忽略。5.3 基于规则生成交易信号指标算完之后还需要把它们组合成可执行的规则。这里不追求复杂先用一个简单的多条件规则演示def generate_signals(df): 生成交易信号 规则示例 1. 收盘价站上MA20且MA5上穿MA10 - 看多 2. RSI低于30 - 超卖提醒 3. MACD金叉且位于零轴上方 - 强势看多 result df.copy() # 均线金叉判断今天MA5 MA10昨天MA5 MA10 result[MA5_above_MA10] result[MA5] result[MA10] result[MA5_above_MA10_prev] result[MA5_above_MA10].shift(1) result[golden_cross_ma] result[MA5_above_MA10] ~result[MA5_above_MA10_prev].fillna(False) # 收盘价站上MA20 result[above_MA20] result[close] result[MA20] # RSI超卖RSI 30 result[rsi_oversold] result[RSI14] 30 # MACD金叉且DIF 0 result[macd_golden_cross] (result[MACD] result[MACD_signal]) (result[MACD] result[MACD_signal].shift(1)) result[macd_above_zero] result[MACD] 0 # 综合信号 result[signal] neutral result.loc[result[golden_cross_ma] result[above_MA20], signal] bullish result.loc[result[macd_golden_cross] result[macd_above_zero], signal] strong_bullish result.loc[result[rsi_oversold], signal] oversold_alert return result这段代码的关键在于“交叉”的判断方式用当前状态和前一天状态做比较只有从“不满足”变成“满足”时才触发信号。如果直接判断MA5 MA10那么上升趋势的每一天都会返回 True无法形成明确的入场时间点。还有一种常见的错误是忽略shift(1)之后的 NaN 值。数据前几行因为窗口不足指标本身就是空值做比较时要用fillna(False)处理否则判断结果会变成 True 或 NaN干扰信号生成。5.4 信号结果汇总对关注的每只股票执行计算后可以把结果汇总成一张总表# 文件路径daily_analysis.py import pandas as pd import os from indicators import load_stock_data, calculate_indicators, generate_signals def analyze_single_stock(csv_path, code): df load_stock_data(csv_path) df calculate_indicators(df) df generate_signals(df) # 只看最新一天的信号 latest df.iloc[-1] return { code: code, date: latest[date].strftime(%Y-%m-%d), close: latest[close], pct_change: latest[pct_change], signal: latest[signal], MA5: round(latest[MA5], 2), MA10: round(latest[MA10], 2), MA20: round(latest[MA20], 2), RSI14: round(latest[RSI14], 2), } def analyze_stock_list(stock_dir, stock_codes): rows [] for code in stock_codes: csv_path os.path.join(stock_dir, f{code}.csv) if not os.path.exists(csv_path): print(f{code} 数据不存在跳过) continue try: row analyze_single_stock(csv_path, code) rows.append(row) except Exception as e: print(f{code} 分析失败: {e}) return pd.DataFrame(rows)到这一步分析系统的核心逻辑就已经完整了数据有了指标有了信号也有了。剩下的事情是把结果以更直观的方式呈现出来。6. 可视化图表生成对很多非程序员背景的使用者来说一堆数字远没有一张图表直观。图表是分析结果的重要补充但不是越复杂越好。每日分析报告只需要保留几张最有信息量的图K 线图、均线图、成交量图、RSI 指标图。matplotlib 是 Python 生态中最基础的绘图库虽然样式不算华丽但胜在稳定、可控、无需额外依赖。下面这段代码演示如何生成一张带均线和成交量的个股分析图# 文件路径charts.py import matplotlib import matplotlib.pyplot as plt import pandas as pd # Windows下配置中文字体 matplotlib.rcParams[font.sans-serif] [SimHei, Microsoft YaHei, PingFang SC] matplotlib.rcParams[axes.unicode_minus] False def plot_stock_chart(df, code, save_path): 绘制个股K线与指标图保存为PNG fig, axes plt.subplots(3, 1, figsize(12, 10), sharexTrue) # 第一张图收盘价和均线 ax1 axes[0] ax1.plot(df[date], df[close], labelClose, colorblack, linewidth1) ax1.plot(df[date], df[MA5], labelMA5, colororange, linewidth1) ax1.plot(df[date], df[MA10], labelMA10, colorblue, linewidth1) ax1.plot(df[date], df[MA20], labelMA20, colorgreen, linewidth1) ax1.set_ylabel(Price) ax1.legend(locbest) ax1.set_title(f{code} Daily Analysis) ax1.grid(True, alpha0.3) # 第二张图成交量 ax2 axes[1] colors [red if c o else green for c, o in zip(df[close], df[open])] ax2.bar(df[date], df[volume], colorcolors, alpha0.5) ax2.set_ylabel(Volume) ax2.grid(True, alpha0.3) # 第三张图RSI ax3 axes[2] ax3.plot(df[date], df[RSI14], labelRSI14, colorpurple, linewidth1) ax3.axhline(70, colorred, linestyle--, alpha0.6) ax3.axhline(30, colorgreen, linestyle--, alpha0.6) ax3.set_ylabel(RSI) ax3.set_ylim(0, 100) ax3.legend(locbest) ax3.grid(True, alpha0.3) plt.tight_layout() plt.savefig(save_path, dpi150) plt.close() print(f图表已保存: {save_path})这里的关键点有两个。第一中文字体必须显式配置。在 Windows 上SimHei和Microsoft YaHei都有较高的可用性macOS 上PingFang SC一般也能正常显示Linux 服务器上如果没有中文字体就需要先安装fonts-wqy-zenhei等字体包否则图表上全是方块。第二savefig之后要执行plt.close()。如果不关闭当前 Figure在一个长时间运行的脚本里连续绘制几十张图内存会持续上升最终导致程序变慢甚至崩溃。这是最容易忽略的性能细节。图表的数量不必追求面面俱到。对于每日分析一张价格趋势图加一张 RSI 指标图已经能覆盖大部分使用场景。图表的价值是辅助阅读帮助发现纯数字表格里不容易察觉的走势变化而不是成为报告的负担。7. 每日分析报告自动生成分析结果和图表都准备好之后最后一步是把它们组装成一份可阅读的报告。报告格式我最推荐 HTML因为它可以通过邮箱客户端和浏览器直接预览而且容易用模板定制样式不需要额外安装 PDF 工具。7.1 生成 HTML 报告使用 jinja2 模板引擎可以把数据结果和图表路径渲染到固定页面模板中。先写一个简单的报告模板!-- 文件路径templates/report_template.html -- !DOCTYPE html html langzh-CN head meta charsetUTF-8 title{{ date }} 每日股票分析报告/title style body { font-family: Microsoft YaHei, PingFang SC, sans-serif; margin: 40px auto; max-width: 1000px; color: #333; } table { border-collapse: collapse; width: 100%; margin: 20px 0; } th, td { border: 1px solid #ddd; padding: 8px; text-align: center; } th { background-color: #f5f5f5; } .bullish { color: #d33; font-weight: bold; } .bearish { color: #3a7d44; font-weight: bold; } .neutral { color: #999; } .chart-box { margin-top: 30px; } .chart-box img { width: 100%; border: 1px solid #eee; border-radius: 6px; } /style /head body h1{{ date }} 每日股票分析报告/h1 p生成时间{{ generated_at }}/p h2市场信号汇总/h2 {{ summary_table }} h2个股K线与指标/h2 {% for item in charts %} div classchart-box h3{{ item.code }} - {{ item.signal_text }}/h3 img src{{ item.image_path }} alt{{ item.code }} 图表 /div {% endfor %} hr p stylecolor:#999;font-size:12px; 免责声明本报告由程序自动生成所有数据仅供参考不构成任何投资建议。 /p /body /html注意图片路径的处理。报告里的图表文件被放入data/reports/images/目录HTML 文件放在data/reports/目录。为了让浏览器能正常显示图片模板中的image_path应该使用相对路径例如images/600519.png。不要使用绝对路径否则文件挪动后图片会全部失效。接下来是渲染报告的代码# 继续在 daily_analysis.py 中 from jinja2 import Environment, FileSystemLoader import datetime import os def render_report(df, chart_list, report_dirdata/reports): 渲染每日分析HTML报告 os.makedirs(report_dir, exist_okTrue) # 汇总表转HTML summary_table_html df.to_html(indexFalse, classessummary-table, border0) # 图表信息 charts [] for item in chart_list: signal_text { bullish: 偏多信号, strong_bullish: 强势看多信号, oversold_alert: 超卖提示, neutral: 无信号, }.get(item.get(signal), 无信号) charts.append({ code: item[code], signal_text: signal_text, image_path: item[image_path], }) # 渲染模板 env Environment(loaderFileSystemLoader(templates)) template env.get_template(report_template.html) html_output template.render( datedatetime.date.today().strftime(%Y-%m-%d), generated_atdatetime.datetime.now().strftime(%Y-%m-%d %H:%M:%S), summary_tablesummary_table_html, chartscharts, ) # 保存报告 output_path os.path.join(report_dir, fdaily_report_{datetime.date.today().strftime(%Y%m%d)}.html) with open(output_path, w, encodingutf-8) as f: f.write(html_output) print(f报告已保存: {output_path})df.to_html()生成的是标准 HTML 表格虽然样式简单但信息完整。如果你希望表格更美观可以用模板的循环语法逐行输出数据把筛选逻辑完全控制在自己手中而不是依赖 pandas 的默认输出。7.2 完整运行流程到这里整套每日分析流程的所有模块都写好了把它们串起来# 文件路径run_daily_analysis.py import os import datetime import pandas as pd from data_fetcher import fetch_daily_data from indicators import load_stock_data, calculate_indicators, generate_signals from charts import plot_stock_chart from daily_analysis import render_report def main(): # 1. 读取自选股清单 stock_list pd.read_csv(data/stock_list.csv) stock_list stock_list.head(10) # 先用前10只演示 # 2. 获取当日最新行情用于演示实际应每天收盘后批量抓取 today_str datetime.date.today().strftime(%Y%m%d) start_date (datetime.date.today() - datetime.timedelta(days365)).strftime(%Y%m%d) os.makedirs(fdata/daily/{today_str}, exist_okTrue) # 3. 批量分析 rows [] chart_items [] for _, row in stock_list.iterrows(): code row[code] name row[name] csv_path fdata/daily/{today_str}/{code}.csv if not os.path.exists(csv_path): df_data fetch_daily_data(code, start_date, today_str) if df_data is None or df_data.empty: continue df_data.to_csv(csv_path, indexFalse) df load_stock_data(csv_path) df calculate_indicators(df) df generate_signals(df) latest df.iloc[-1] rows.append({ code: code, name: name, close: latest[close], pct_change: latest[pct_change], signal: latest[signal], MA5: round(latest[MA5], 2), MA10: round(latest[MA10], 2), MA20: round(latest[MA20], 2), }) # 生成图表 image_path fdata/reports/images/{code}.png os.makedirs(data/reports/images, exist_okTrue) plot_stock_chart(df, code, image_path) chart_items.append({ code: code, signal: latest[signal], image_path: fimages/{code}.png, # 相对于报告目录 }) # 4. 生成报告 result_df pd.DataFrame(rows) render_report(result_df, chart_items) if __name__ __main__: main()在开始编写这篇代码时我建议你先把stock_list.head(10)改成只取两到三只股票跑通全流程确认每个环节都正常工作后再扩大分析范围。全市场分析虽然听起来厉害但在数据源不稳定、图表渲染速度慢的情况下全量跑一遍可能消耗很长时间中间还容易因为个别股票数据异常而中断。先小范围跑通再批量扩展是这类数据项目中比较稳妥的做法。7.3 自动化定时执行手动执行不是长久之计。在 Linux 服务器或本地环境中可以用 crontab 设定每天收盘后自动执行# 每个交易日15:30执行分析脚本周一到周五 30 15 * * 1-5 cd /path/to/daily_stock_analysis /path/to/venv/bin/python run_daily_analysis.py logs/daily_analysis.log 21在 Windows 上可以用计划任务程序注册一个每日触发任务。需要注意定时任务的日志必须保留因为数据源偶尔会失败如果脚本静默异常你根本不知道今天的报告没有生成。这里要特别强调一个合规和安全边界定时任务自动化运行在你自己控制的主机和数据源完全授权的前提下是没有问题的但不要在未经授权的情况下把脚本挂到公司内网或生产环境去抓取外网公开接口。这个约束既是为了不违反数据源的使用条款也是为了不让自动化任务成为公司安全审计的黑洞。8. 常见问题与排查思路把项目从零到一跑通的过程中大概率会遇到下面几个问题。这里整理成一份排查清单遇到问题时按表逐项检查比盲目试错高效得多。问题现象可能原因排查方式解决方案运行fetch_stock_list报错akshare 版本过旧数据源接口变更查看报错信息检查 akshare 版本pip install -U akshare升级到最新版获取行情时返回空 DataFrame股票代码错误或接口临时限流打印接口返回的前几行尝试单只股票重跑核对代码是否带交易所前缀适当增加休眠时间图表中文显示为方块系统缺少中文字体或 matplotlib 未配置字体查看matplotlib.font_manager已加载字体安装中文字体在代码中显式指定font.sans-serif指标计算出现大量 NaN数据窗口长度不足或存在停牌日空值检查原始数据行数和缺失值情况确保获取足够长的历史数据清洗时用dropna或前向填充信号判断结果总是 neutral规则条件过严或指标交叉判断逻辑写错单独打印指标列查看交叉判断中间结果先用宽松规则验证信号逻辑再逐步收紧条件批量抓取时频繁超时请求频率过高触发对方限流查看日志中错误码和失败时间间隔增加随机延时失败后指数退避重试分批处理定时任务没有执行cron 环境变量或 python 路径不对检查 cron 日志手动执行同一命令使用绝对路径的 python 解释器和项目目录这里面最有代表性的问题是“信号判断结果总是 neutral”。新手最容易犯的错误是同时要求多个条件在“同一根K线”上全部满足这在历史上本来就很少发生最后自然选不出任何结果。正确的做法是先把单个条件分别输出逐个观察它们的触发频率再决定如何组合。分析类项目最需要的不是更复杂的规则而是对规则触发的历史分布有清晰的感知。9. 工程化建议与合规提示项目跑通之后如果想要长期使用建议从下面几个方向做工程化改造。第一把配置和代码分离。股票代码列表、数据源地址、延时策略、阈值参数等都应该抽到配置文件里不要硬编码在 Python 脚本中。最简单的做法是使用 YAML 或.env文件# 文件路径config.yaml stock_pool: - code: 600519 name: 贵州茅台 - code: 000001 name: 平安银行 data_source: start_date_offset_days: 365 delay_min: 0.3 delay_max: 1.0 max_retry: 3 indicators: ma_windows: [5, 10, 20] rsi_window: 14 macd_fast: 12 macd_slow: 26 macd_signal: 9 report: output_dir: data/reports title: 每日股票分析报告这样修改阈值或股票池时不需要动代码脚本的维护成本会明显下降。更重要的好处是当你想把脚本分享给其他同事或换一台机器部署时直接复制配置和代码不需要一行一行找参数。第二做好异常报警。自动化脚本的宿命是总会在某一天悄无声息地失败。所以必须把日志和异常通知融入到整个流程中。日志至少要记录四个信息执行时间、成功/失败状态、失败原因、耗时。如果关键环节失败可以发送通知到企业微信或邮件。初期用 Python 的logging模块即可不需要引入复杂的监控系统。第三保证数据可追溯。当某一天报告里的信号明显异常时你要能回答“这个信号是依据哪一份数据、哪一条规则生成的”。所以建议报告里附带上数据源版本、数据抓取时间和代码版本号。简单一点的做法是在每天的报告文件中同步保存当天的原始数据目录和一份执行说明这样任何结论都能回查。第四也是最重要的必须明确合规边界。任何股票分析工具的输出结果都不能构成投资建议报告页脚要加免责声明。数据获取只能在数据源允许的范围内使用要尊重接口的使用条款和频率限制。此外历史回测结果与现实交易之间还存在滑点、手续费、涨跌停无法成交等大量差异不要用未经验证的策略直接做实盘交易。这套系统的正确使用方式是辅助人做判断而不是代替人做决定。10. 总结与实践路径回到daily_stock_analysis这个项目本身。它提醒我们一件重要的事个人做数据分析最有价值的不是写出多么复杂的模型而是把重复的、有标准流程的事情自动化把精力留给真正需要判断力的部分。这篇文章已经把每日股票分析系统的完整链路拆解清楚了从数据采集、清洗、指标计算、信号生成到图表可视化和报告输出每一步都有对应的代码和可运行示例。你可以直接复制代码跑一个最小原型也可以基于这套架构替换数据源、增加指标、调整规则把它改造成适合自己使用习惯的分析工具。如果你想把项目继续深化可以考虑这几个方向一是用 Ruff 或 Black 统一代码风格提高代码的可维护性二是引入 SQLite 做数据增量存储方便回溯更长时间段的数据三是增加板块和行业维度分析个股与所属板块的共振关系四是把信号触发历史做成回测模块用历史数据验证规则是否有效。相比之下一上来就追求复杂模型并不一定值得先把基础设施做扎实更重要。如果你也正在做类似的数据分析项目我的建议是先选定两三只你非常熟悉的股票把全流程跑通慢慢把数据源、指标、规则都摸透然后再逐步扩大股票池和改进策略。真正可靠的系统不是一次性搭建出来的而是通过持续调整和验证打磨出来的。希望这篇文章能帮你迈出第一步。说明本文所有代码仅为技术演示不构成投资建议。实盘操作请遵守相关法律法规并自行承担决策风险。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进