做股票复盘的朋友应该都有这种体会每天收盘后想快速找出近期跌幅比较大的票看看有没有超跌反弹的机会或者排查自己持仓里那些跌得凶的品种到底是因为什么。手动翻行情软件一个个点太费劲写个脚本直接跑数据又不够直观。我之前就把这个需求做成了一款基于Python的股票近期跌幅分析GUI工具从数据拉取、跌幅计算到结果呈现全部自动化界面操作不需要写一行命令。这篇文章就把这套工具的思路、技术选型、完整实现和踩坑记录都整理出来适合想用Python做个股分析工具、又不想只停留在命令行阶段的朋友参考。先说清楚这个工具能干什么你输入一个时间区间比如近5日、近20日、近60日程序自动拉取全市场或指定板块的日线行情计算出每只股票在这个区间内的区间跌幅然后按跌幅从高到低排序用表格展示在GUI窗口里。你可以双击某一行查看该股票的近期走势摘要也可以一键把结果导出成CSV。整个工具用Python标准库Tkinter做界面核心数据处理用pandas数据源用免费的A股行情接口不需要额外付费数据服务。1. 需求拆解与整体设计1.1 目标场景与核心指标做跌幅分析这件事核心是想回答一个问题哪些股票在最近一段时间内跌得最多。但是“跌得最多”这个定义其实有很多细节不同的口径会直接影响分析结果。第一个口径是区间跌幅的计算起点和终点。最简单的方式是用区间最后一根K线的收盘价除以区间起始日前一根K线的收盘价再减1。注意这里要用起始日的前一根K线因为如果直接用起始日当天的收盘价实际上会把起始日当天的跌幅也算进去而很多场景下我们想要的是“从起始日开始持有到现在”的跌幅。举个例子你想看近20个交易日的跌幅那起点应该是20个交易日之前的那个收盘价而不是第20个交易日当天的开盘价或收盘价。第二个口径是是否需要剔除异常股票。ST股、退市整理期的股票、上市不足N天的次新股它们的跌幅数据参考意义不大甚至会因为涨跌停规则不同而产生误导。比如科创板、创业板注册制股票的涨跌幅限制是20%而主板是10%同样跌10%在不同板块里意味着完全不同的市场情绪。所以工具里必须加入板块类型和股票状态的过滤逻辑。第三个口径是指数的对比基准。单独看一只股票的跌幅很难判断它到底是跟随大盘下跌还是独立走弱。如果同期沪深300跌了8%某只股票跌了9%那它其实并没有特别弱但如果大盘只跌了1%它跌了9%那性质就完全不同。所以工具在计算个股跌幅的同时会同步计算上证指数、深证成指或沪深300在同区间的跌幅并在GUI里展示出来作为参考。基于以上这些思考我的核心指标设计如下区间跌幅终点收盘价相对起点前收盘价的涨跌幅区间换手率区间总成交量 / 流通股本区间最大回撤区间内收盘价相对区间最高价的最大回撤幅度相对大盘超额跌幅个股区间跌幅 - 基准指数区间跌幅1.2 技术选型为什么是Python Tkinter这个工具的技术选型我前后对比过好几个方案包括PyQt/PySide、Tkinter、甚至用Web技术做成本地网页服务。最终选择了Tkinter主要基于三个考虑。第一是部署成本。Tkinter是Python的标准库安装Python之后自带不需要额外安装第三方GUI框架。对于同事和朋友来说我的工具要分享给别人使用对方只需要装好Python、pip install几个数据处理和网络请求的库就能跑起来不用处理PyQt那套复杂的动态库依赖。尤其是在Windows环境下PyQt经常遇到某个DLL缺失的问题排查起来非常头疼。第二是足够满足需求。我的界面不算复杂一个参数区、一个表格区、一个状态栏再加一个简单的详情弹窗。这些控件Tkinter的ttk组件都有现成的实现Treeview做表格展示、Combobox做下拉选择、Progressbar做进度提示完全够用。如果用PyQt功能上当然更强但对我来说属于杀鸡用牛刀开发效率反而降低。第三是打包体积。Tkinter程序用PyInstaller打包之后大约20MB左右PyQt的程序起步就是50到80MB。作为要经常发给别人的分析工具体积小一些分发体验会好很多。当然Tkinter也有短板主要就是界面美观度比较差默认样式有种上世纪的感觉。不过可以通过ttk的样式主题做一定程度的优化比如用clam主题再自定义一些颜色的配置观感可以接受。1.3 整体交互流程设计工具的使用流程设计得尽量简单目标是一个没接触过程序的人也能在30秒内上手。用户打开程序后界面上方是参数设置区包括行情数据源下拉框可选akshare免费接口或tushare接口分析周期下拉框近5日、近10日、近20日、近60日、自定义日期区间股票池范围下拉框全市场、沪深300成分、中证500成分、自选股列表板块过滤选项是否剔除ST、是否剔除次新股、是否包含北交所排序方式按区间跌幅降序 / 按相对大盘超额跌幅降序中间是结果展示区使用Treeview表格展示列包括代码、名称、区间跌幅、区间换手率、最大回撤、相对大盘超额跌幅、最新收盘价、所属行业。表头支持点击排序双击行弹出该股近30日的走势摘要。底部是状态栏显示当前数据拉取进度、加载了多少只股票、耗时多少秒以及一个“开始分析”按钮和“导出CSV”按钮。点击“开始分析”之后程序会启动一个后台线程拉数据和计算界面不卡死计算完成后通过队列把结果回传到主线程刷新表格。2. 数据获取与跌幅计算2.1 行情数据源该怎么选数据源是这个工具最关键的依赖直接决定了分析的覆盖面、准确性和稳定性。我重点测试过两个免费方案一个是akshare一个是tushare pro积分制另外还试过直接抓新浪财经和腾讯财经的接口。akshare开源库直接封装了多个公开数据源接口丰富A股日线行情可以拿到全市场几千只股票的数据。优点是完全免费、无需注册token、接口覆盖广缺点是部分接口存在上游数据源限流问题高频率请求时可能报错而且接口偶尔会因为上游页面改版失效需要及时更新库版本。tushare pro需要注册获取token基础积分有调用频率限制日线行情接口每分钟可以调用的次数有限。优点是数据结构规范、稳定有官方文档和社区支持字段比akshare更完整。缺点是免费用户的积分限制比较严格全市场拉取一轮可能要分多次排队等待的时间比较长。直接抓新浪/腾讯的实时接口做日线复盘的话新浪的d20240219e20240220这类历史K线接口效率高一次请求一只股票的全部历史数据速度很快。缺点是没有统一的维护字段变化了需要自己修而且不规范。我最终的做法是以akshare为主力数据源同时在代码里预留了tushare的适配接口用户可以自己配置token切换到tushare。之所以默认用akshare是因为对个人用户来说不需要注册、不需要积分、直接pip install就能用的体验是最友好的。需要注意akshare的股票列表接口和日线行情接口是分开的。要拿到全市场的股票列表用ak.stock_info_a_code_name()获取代码和名称要获取个股日线行情用ak.stock_zh_a_hist()。这两个接口配合使用才能避免出现数据对不上代码的情况。2.2 跌幅指标的计算与其背后的意义跌幅指标的计算看似简单直接两个价格相除再减1就行但实际落地时会有几个细节需要特别处理。第一是复权问题。如果股票在区间内发生了分红送转不复权的价格会出现人为的跳空导致计算出来的跌幅失真。比如一只股票10送10股价会从20元变成10元左右不复权算出来的跌幅接近50%但实际持有人的资产并没有缩水。所以计算区间跌幅时必须用前复权价格。akshare的stock_zh_a_hist接口里有一个adjust参数传入qfq就是前复权这个必须设置。第二是停牌问题。有些股票在分析区间内停牌了较长一段时间导致区间内只有很少的交易日数据。比如近20个交易日该股票只交易了3天算出来的跌幅参考意义就非常有限。工具里我设置了最小有效交易日数量默认是区间交易日数量的50%低于这个阈值的股票会被标记为“数据不足”不参与排序。第三是数据的对齐。不同股票的上市时间不同有些股票在分析区间的起点并没有上市。这种情况在次新股中很常见。处理方式有两种一种是直接剔除另一种是用上市首日收盘价作为起点价格。我默认采用剔除方式因为次新股的涨跌停规则和筹码结构跟老股差异太大混在一起容易得出错误结论。计算跌幅的伪代码如下def calc_interval_drop(df, start_date, end_date, min_trade_days): # df 为单只股票的前复权日线数据按日期升序 # 找到区间内的数据 mask (df[日期] start_date) (df[日期] end_date) period_df df.loc[mask] if len(period_df) min_trade_days: return None # 数据不足剔除 # 区间终点价格最后一根K线的收盘价 end_price period_df[收盘].iloc[-1] # 区间起点价格起始日前一根K线的收盘价 before_df df[df[日期] start_date] if before_df.empty: return None # 区间起点前没有数据 start_price before_df[收盘].iloc[-1] drop (end_price / start_price - 1) * 100 return round(drop, 2)注意这里用的是“起始日前一根K线的收盘价”而不是区间内第一天的开盘价。这个细节很多人容易忽略但它决定了跌幅是否包含起点当天的变化。从持有角度来说用前一根收盘价作为成本基准更符合实际。2.3 多周期跌幅聚合逻辑除了单周期跌幅工具里还设计了一个“近5日、近20日、近60日”三周期联动的分析模式。为什么需要多周期因为不同周期的跌幅揭示了不同层面的信息。近5日跌幅大可能只是短期情绪宣泄比如突发利空或者板块轮动导致的抛压这种超跌反弹的概率相对较大但风险也高因为可能还有持续下跌的惯性。近20日跌幅大属于中期趋势走弱可能是基本面出了问题也可能是持续阴跌的“温水煮青蛙”这种票要谨慎因为下跌趋势一旦形成短期很难反转。近60日跌幅大说明该股票已经经历了一轮比较完整的下跌可能已经接近估值底部区间但也可能是基本面彻底恶化的“价值陷阱”。多周期聚合的实现逻辑是分别计算三个周期的跌幅然后把结果放在同一行展示。用户可以在GUI表格里同时看到一只股票的短期、中期、长期跌幅方便快速判断它的下跌阶段。另外我还在聚合逻辑中加入了“跌幅加速”的判断。如果近5日跌幅的日均跌幅 近20日跌幅的日均跌幅说明这只股票近期在加速下跌这种情况通常意味着恐慌情绪集中释放后期反而可能出现技术性反弹。工具会在表格里用背景色高亮这类股票作为超跌反弹候选池的重要参考。3. GUI界面设计细节3.1 界面布局与交互设计思路Tkinter的界面布局主要靠pack、grid、place三种方式。我的习惯是优先用grid它在控制行列对齐方面更直观。整体界面分为三个区域顶部参数区、中部表格区、底部状态区。顶部参数区使用两个Frame嵌套。外层Frame负责整体布局内层用LabelFrame做分组。每一行放下拉框和标签保持整齐。参数项之间的间距用padx和pady控制避免挤在一起。中部的表格区使用ttk.Treeview组件开启垂直滚动条和水平滚动条。Treeview的列设置需要注意列宽要合理分配代码列窄一点名称列适中跌幅和换手率列要留足够的空间显示数字行业列稍微宽一些。表头点击排序功能需要绑定heading的command事件点击时切换升序降序。底部状态区比较简单左边放按钮右边放状态文字。状态文字用StringVar绑定线程更新时通过队列传递消息主线程用after定时检查队列并刷新状态栏。整体配色上我用ttk的clam主题然后通过style.configure设置了Treeview的选中色、表头背景色、行高。跌幅为负数的数字用绿色显示A股习惯红跌绿涨但考虑到国际惯例和多数人的直觉我默认用红跌绿涨的国内配色并在设置里提供切换选项这一点见仁见智用户可自行调整。3.2 关键控件实现表格、筛选器与进度反馈Treeview的排序功能是GUI里使用频率最高的交互操作。点击表头触发排序的核心逻辑如下def sort_treeview(tree, col, reverseFalse): data [(tree.set(child, col), child) for child in tree.get_children()] # 对于数值列需要先把字符串转成float再排序 try: data.sort(keylambda x: float(x[0]), reversereverse) except ValueError: data.sort(keylambda x: x[0], reversereverse) for index, (_, child) in enumerate(data): tree.move(child, , index) tree.heading(col, commandlambda: sort_treeview(tree, col, not reverse))这里有一个坑Treeview从界面取出来的值都是字符串直接按字符串排序会出现“10 9”这种问题所以必须根据列的类型做数值转换。我的做法是每个列在定义时指定一个numeric标志数值列走float转换文本列走字符串排序。筛选器的实现上参数区的“剔除ST”“剔除次新股”复选框直接对应数据过滤条件。次新股的判定标准是上市日期距分析结束日不足120个自然日。ST的判定更简单股票名称中包含“ST”或“*ST”即命中。进度反馈也是一个容易被忽视的点。拉全市场数据时即使开了并发几百只股票的行情数据也要花几十秒到几分钟。如果界面上没有任何进度提示用户会以为程序卡死了。我用了ttk.Progressbar模式为determinate通过队列接收后台线程上报的进度百分比配合状态栏文字“正在获取XX/5000”让用户对进度心里有数。3.3 界面卡顿问题与异步优化方案GUI程序的大忌是主线程阻塞。如果直接在按钮回调里同步去拉数据界面会冻结严重的话系统会提示“程序无响应”。必须用多线程加队列的经典方案。我的实现思路是点击按钮时启动一个threading.Thread执行数据分析和计算任务后台线程通过queue.Queue向主线程发送两类消息进度消息和结果消息主线程用root.after(100, poll_queue)每100毫秒检查一次队列更新进度条和状态栏数据处理完成时后台线程发送结果DataFrame主线程收到后刷新Treeview后台线程的完整结构是import threading import queue def start_analysis(self): if self.worker and self.worker.is_alive(): return # 防止重复点击 self.msg_queue queue.Queue() self.worker threading.Thread( targetself.analysis_worker, args(self.get_params(), self.msg_queue), daemonTrue ) self.worker.start() self.root.after(100, self.poll_queue) def poll_queue(self): try: while True: msg self.msg_queue.get_nowait() if msg[type] progress: self.progress_var.set(msg[value]) self.status_var.set(msg[text]) elif msg[type] done: self.load_results_to_tree(msg[data]) elif msg[type] error: messagebox.showerror(错误, msg[text]) except queue.Empty: pass self.root.after(100, self.poll_queue)这个方案有一个需要注意的地方Tkinter不是线程安全的后台线程绝对不能直接调用控件的set方法否则会出现随机性的崩溃和数据显示错乱。所有涉及界面更新的操作都必须通过队列转发到主线程执行这个约束要严格遵守。4. 实操过程与完整实现4.1 环境准备与依赖安装这个工具依赖的基础库不多核心就是四个akshare、pandas、tkinter标准库不需要额外安装、pyinstaller打包时需要。安装命令如下pip install akshare pandas pyinstaller需要注意akshare的依赖比较多它会顺带安装pandas、requests、beautifulsoup4等一堆包所以安装过程中如果遇到网络超时可以换国内镜像源pip install akshare pandas pyinstaller -i https://pypi.tuna.tsinghua.edu.cn/simple版本兼容性方面我用的是Python 3.10配合akshare 1.12.x版本运行稳定。如果用的是Python 3.12以上的新版本个别依赖可能出现兼容问题建议优先创建虚拟环境运行。4.2 核心代码实战从数据拉取到跌幅计算下面这段代码是工具最核心的部分把数据拉取、清洗、跌幅计算全部串起来。我简化掉了一些GUI相关的逻辑保留了数据处理的主线方便你理解整体流程。import akshare as ak import pandas as pd from datetime import datetime, timedelta def fetch_stock_list(): 获取A股全部股票代码和名称 df ak.stock_info_a_code_name() # 过滤掉北交所代码8开头、4开头默认只保留沪深主板、创业板、科创板 df df[ df[code].str.startswith((60, 00, 30, 68)) ] return df def fetch_daily_data(stock_code, start_date, end_date, adjustqfq): 获取单只股票的前复权日线数据 df ak.stock_zh_a_hist( symbolstock_code, perioddaily, start_datestart_date, end_dateend_date, adjustadjust, ) if df is None or df.empty: return None df[日期] pd.to_datetime(df[日期]) df.sort_values(日期, inplaceTrue) return df def calc_stock_drop(stock_code, stock_name, start_date, end_date, min_trade_days): 计算单只股票的区间跌幅 df fetch_daily_data(stock_code, start_date, end_date) if df is None or df.empty: return None mask (df[日期] pd.Timestamp(start_date)) (df[日期] pd.Timestamp(end_date)) period_df df.loc[mask] if len(period_df) min_trade_days: return None end_price period_df[收盘].iloc[-1] before_df df[df[日期] pd.Timestamp(start_date)] if before_df.empty: return None start_price before_df[收盘].iloc[-1] drop (end_price / start_price - 1) * 100 return round(drop, 2) def run_analysis(start_date, end_date, min_trade_days): stock_list fetch_stock_list() results [] total len(stock_list) for idx, row in stock_list.iterrows(): code, name row[code], row[name] drop calc_stock_drop(code, name, start_date, end_date, min_trade_days) if drop is not None: results.append({ 代码: code, 名称: name, 区间跌幅(%): drop, }) if (idx 1) % 200 0: print(f已处理 {idx 1}/{total}) result_df pd.DataFrame(results) result_df.sort_values(区间跌幅(%), ascendingTrue, inplaceTrue) return result_df这里面有几个细节值得展开说一下。第一个是股票代码的过滤。akshare的stock_info_a_code_name接口返回的是全A股代码包括北交所的8开头和4开头的股票。如果你不做区分直接拿去做日线行情分析会大幅增加请求量而且北交所股票的涨跌幅限制是30%跟主板差异很大混在一起统计没有意义。所以我在代码里按前缀做了一个默认过滤。第二个是start_date和end_date的传入格式。akshare要求是YYYYMMDD这种字符串我上面写的是直接传字符串实际使用时你需要做一次格式转换datetime.strftime(%Y%m%d)。第三个是min_trade_days的设置。这个值设置得太低会有很多数据不完整的股票混进来设置得太高一些正常但中途停牌时间长的股票会被误杀。我用的是分析区间理论交易日数量的50%。可以用pd.bdate_range(start_date, end_date)来计算理论交易日数量。4.3 并发优化多线程拉取让速度提升5倍上面这个简单的循环实现跑全市场五千多只股票效率非常低。我用实际测试数据给你一个直观感受单个线程按顺序拉取每只股票一次HTTP请求平均耗时0.2到0.5秒五千只股票需要20到40分钟这还是在网络状态良好的情况下。对于复盘工具来说这个速度完全不可接受。优化方案是使用线程池并发拉取。akshare的接口是IO密集型操作多线程能显著提升效率。我把请求改成线程池方式并发数控制在16左右。from concurrent.futures import ThreadPoolExecutor, as_completed def run_analysis_concurrent(start_date, end_date, min_trade_days, max_workers16): stock_list fetch_stock_list() results [] total len(stock_list) with ThreadPoolExecutor(max_workersmax_workers) as executor: future_map { executor.submit( calc_stock_drop, row[code], row[name], start_date, end_date, min_trade_days, ): (row[code], row[name]) for _, row in stock_list.iterrows() } done_count 0 for future in as_completed(future_map): code, name future_map[future] try: drop future.result() if drop is not None: results.append({代码: code, 名称: name, 区间跌幅(%): drop}) except Exception as e: print(f处理 {code} {name} 出错: {e}) done_count 1 if done_count % 1000 0: print(f已完成 {done_count}/{total}) result_df pd.DataFrame(results) result_df.sort_values(区间跌幅(%), ascendingTrue, inplaceTrue) return result_df并发数不是越大越好。我测试过10、16、24、32这几个档位16并发时效率最高超过20之后因为上游接口限流严重反而会出现大量失败请求需要反复重试整体耗时没有明显下降。这里建议你在自己的网络环境下跑一遍测试找到合适的并发数。此外因为akshare的上游接口存在限流高并发下会报错所以在calc_stock_drop里加了简单重试逻辑遇到异常时等待1秒再试一次。这个处理对整体成功率提升非常明显。4.4 打包成独立可执行文件工具写完之后为了方便日常使用和分享给朋友我用PyInstaller打包成exe文件Windows平台。打包命令比较简单pyinstaller -F -w stock_analyzer.py参数解释一下-F打包成单文件模式所有依赖都塞进一个exe里-w不显示命令行窗口因为是GUI程序打包过程可能会遇到一个问题akshare的包太大依赖很多打包出来的exe可能超过50MB。解决办法是用--exclude-module排除掉用不到的模块比如akshare里的期货、外汇、债券接口所依赖的额外模块。另一个问题是akshare的动态数据源有时需要读取一些数据文件PyInstaller单文件模式打包后程序运行时会解压到临时目录个别数据文件加载路径可能出问题。解决方法是把需要的资源文件用--add-data参数一并打包进去。不过在我测试的版本里akshare没有遇到这个问题如果你使用其他版本遇到类似报错可以从这个方向排查。5. 常见问题与排查技巧实录5.1 数据接口报错与限流怎么办这是使用akshare类免费数据源最常遇到的问题。我遇到过的报错主要有三类接口返回空数据、接口超时、接口返回的数据格式与预期不符。接口返回空数据大概率是股票代码权限问题。个别股票比如科创板、创业板的部分标的上游数据源可能有延迟或者访问限制。解决方法是跳过这些股票不让一个失败影响整体流程。接口超时几乎都是上游限流导致的。特别是连续大量请求时akshare使用的上游接口会拒绝服务。我遇到最严重的一次全市场跑到三分之二后面的请求全部超时程序直接崩了。后来在代码里给每个HTTP请求加了超时参数并配合退避重试策略整体成功率才稳定下来。数据格式不符的情况比较隐蔽。比如stock_zh_a_hist在返回数据时某些特殊情况下列名会出现变化或者日期格式变成字符串。我在清洗环节做了列名和类型的统一转换避免因为个别数据异常导致整个DataFrame处理失败。5.2 GUI界面无响应与数据错位排查界面无响应的问题几乎都是主线程被阻塞导致的。如果你把耗时的数据拉取操作直接写在按钮的回调函数里Tkinter的主循环在这段时间内无法处理窗口事件表现就是窗口拖不动、按钮点了没反应、甚至直接白屏。解决方案就是我前面提到的多线程加队列。数据错位是另一个容易遇到的问题。使用Treeview展示数据时如果DataFrame的索引没有重置或者表格行的顺序和DataFrame不一致就会出现在界面上看到的数据和实际数据对不上的情况。我的建议是每次往Treeview里填充数据前先调用result_df.reset_index(dropTrue)然后按行索引一一对应插入。还有一个经验是Treeview在不重新加载data时原来的行并不会自动清空。如果你点击“开始分析”两次第二次运行时表格里可能会出现重复数据。所以每次开始分析前必须先执行tree.delete(*tree.get_children())清空所有旧数据。5.3 跌幅计算中的“口径坑”很多非量化背景的朋友在写跌幅计算时容易踩几个坑我把它们集中列出来你对照检查一下自己的代码。第一个坑是前复权和后复权的选择。前复权适合看当前价格对应的历史跌幅表现后复权适合做长周期的收益率计算。对于近期跌幅分析前复权就足够了。记住不要用不复权数据尤其是在除权除息密集的时段。第二个坑是涨跌停对跌幅的扭曲。一只股票连续跌停时每天只能跌10%主板所以跌停状态下它的真实跌幅被规则人为限制了。如果你想分析的是“如果没有跌停限制这只股票会跌多少”那就需要另做估算。工具里我暂时没有做这个特殊处理但在结果展示中对连续跌停的股票加了标记提醒用户注意。第三个坑是除权除息日的数据对齐。前复权虽然解决了除权日的价格跳空问题但如果在区间起点前恰好有除权除息复权因子调整后的起点价格可能与真实的交易价格有偏差。这种偏差在绝大多数情况下对分析结果影响非常小但如果你追求极致的准确性可以考虑使用后复权数据做计算最后再把跌幅结果映射回前复权口径。5.4 常见问题速查表问题现象可能原因解决方案界面完全无法响应数据拉取在主线程执行使用threadingqueue方案后台线程处理数据表格数据乱序DataFrame索引未重置reset_index(dropTrue)后再填充Treeview重复点击按钮后表格重复未清空旧数据先清空Treeview全部行再加载拉取到一半全部超时上游接口限流降低并发数增加退避重试逻辑跌幅明显失真未使用复权数据传入adjustqfq参数次新股跌幅异常上市时间不足导致数据缺失加入上市天数过滤剔除次新股打包后无法运行资源文件丢失使用--add-data打包资源文件ST股干扰排行未做ST过滤检查名称字段剔除ST/*ST股票6. 基于跌幅分析的扩展思路6.1 从“跌得惨”到“有机会”超跌反弹策略跌幅排行本身只是数据展示真正有价值的是基于跌幅数据做进一步的策略筛选。工具目前已经实现了“跌幅换手率最大回撤”的组合筛选你可以据此搭建一个简单的超跌反弹候选池。我的筛选逻辑大致是近20日跌幅超过20%、近5日跌幅小于近20日跌幅的日均跌幅、区间换手率在5%到30%之间、剔除ST和次新股、相对大盘超额跌幅排名前50。这套条件的逻辑是这只股票近期确实跌得多但最近几天跌势在放缓同时换手率维持在一定水平说明有资金在关注和进出并不是一支彻底死掉的票。这些候选股票只会作为一个参考池不能直接当作买入信号。股票下跌的原因千差万别有的是错杀有的是基本面恶化前的预演。工具的价值是把符合条件的股票快速筛出来节省人工翻找的时间最终的分析判断还是要结合公告、财务数据、行业新闻一起看。6.2 后续可以扩展的方向这套工具后续可以扩展的方向非常多我自己也在逐步完善。第一个方向是增加个股详情页。双击表格中的股票不仅展示近30日走势摘要还可以显示它的财务数据、主力资金流向、同行业对比等。数据源同样可以用akshare的接口比如ak.stock_individual_financial_report和ak.stock_individual_fund_flow。第二个方向是引入K线图展示。Tkinter自带的Canvas可以画简单的折线图但如果要展示专业的K线图建议直接用mplfinance或pyecharts生成图片再嵌入到Tkinter中显示。mplfinance生成K线图非常方便把它渲染成图片放入Label控件即可。第三个方向是把工具改造成一个本地服务。界面用浏览器实现Python只负责数据接口。这样无论是部署在云服务器还是局域网内共享大家都能通过浏览器访问。这个方案在多人协作时比较有优势类似自己搭一个轻量的数据看板。7. 实操过程中的一些体会工具做了这么长时间最大的体会是数据获取和清洗往往比界面开发更花时间。整个项目里GUI相关代码大概只占三成七成的精力都花在了数据接口适配、异常处理、清洗逻辑这些“看不见”的环节上。很多人一提到做股票分析工具第一反应就是“用现有的量化平台”或者“先搞一个高深的策略模型”。但实际上当你真正去落地的时候会发现最基础的数据可靠性和工具易用性才是决定这个工具能否被日常使用的关键。一个拉数三天两头出错、界面卡成PPT的工具再高深的算法也会被搁置。如果你也想做类似的工具我的建议是从小切口入手。不要一开始就想着做一个功能齐全的量化平台先把你日常复盘中最费时间的那一个动作自动化比如本文的“近20日跌幅排行”。把一个动作做到足够顺手、足够稳定再逐步叠加其他功能。这个思路不仅适用于股票分析工具也适用于绝大多数个人项目的启动。现在这个工具我基本每天收盘后都会跑一遍大约1分多钟就能拿到全市场所有股票的多周期跌幅排行配合点击排序和自选股筛选复盘的效率比过去手动翻行情软件提升了很多。希望这篇文章对你做自己的股票分析工具有所帮助。
