简介这是一份基于Python的股票自动交易系统毕业设计项目源码面向计算机、软件工程等专业学生与开发者适用于毕业设计、期末大作业、课程设计等场景。项目历经完整调试与测试答辩评审分达95分。系统涵盖行情数据获取、交易策略编写、自动下单执行及SQL数据存储等核心模块。压缩包共686个文件、14.77MB主体为Python策略与调度代码、131个Java业务模块、HTML/CSS/JS前端页面、XML配置文件及近300张PNG/JPG界面截图另附Dockerfile与SQLite数据库文件。目前已有337人学习下载下载后可直接运行项目工程获得完整目录结构、数据库脚本与配置说明基础扎实的读者还可在此基础上扩展交易策略或接入实盘接口实现个性化功能改造。1. 股票自动交易系统到底是什么一个毕设源码包里的完整闭环拿到“基于Python的股票自动交易系统的设计与实现”这个题目时很多人第一反应是“让程序自己买卖股票、躺着赚钱”。实际打开这类毕设源码包你会发现它讲的是另一件事把一套完整的交易闭环拆成数据获取、策略信号、订单执行、风险控制、回测验证五个模块用 Python 串起来。真正值钱的部分不是“自动下单”那一步而是“信号怎么算、仓位怎么控、回测怎么不骗人”。这套系统适合两类人一类是要交毕设的学生需要快速搞懂每行代码对应哪个交易环节另一类是刚接触量化、想用最小代价验证一个策略想法的开发者。它解决的核心问题是在不上真金白银的前提下把“如果按这个规则交易会怎样”这个黑匣子打开。2. 拆解源码包一个自动交易系统的模块划分与选型理由拿到 zip 先别急着跑先看目录。绝大多数股票自动交易系统的源码包都长成一个样子一个 main.py 做启动入口一个 config.py 放所有可调参数剩下按职责拆成 data、strategy、trader、risk、backtest 五个包。能分清这五块后面改代码、写论文、答辩都有抓手。2.1 五块核心模块数据、策略、执行、风控、回测各自干什么第一块是数据模块。它负责拉取历史 K 线和实时行情清洗后落库。常见做法是每天收盘后跑一次增量更新把当日 OHLCV开高低收量追加到 SQLite 或 CSV 文件里。数据模块做得差后面所有信号都是垃圾进垃圾出所以源码里这一块通常写着最多的重试和异常处理。第二块是策略模块。它接收 DataFrame 格式的行情数据经过指标计算后输出一个信号买、卖、持有。毕业设计里最常见的策略是双均线金叉死叉、RSI 超买超卖、布林带突破因为它们逻辑简单、画图直观、答辩时三句话能说清楚。第三块是执行模块。它的职责是把“买”这个信号翻译成具体订单买多少股、按什么价格、什么时间提交。毕设源码里这里通常是一个订单状态机维护等待、已提交、已成交、被拒绝几个状态。第四块是风控模块也是评阅老师最喜欢追问的部分。它的典型工作是三件事单笔仓位不超过总资金的一定比例、一天内下单次数设上限、单笔亏损超过阈值就强制平仓。没有风控模块的系统只能叫“策略演示”不配叫“交易系统”。第五块是回测模块。它把历史数据喂给策略模拟按历史价格成交最后算出净值曲线、年化收益、最大回撤。注意回测模块最容易造假很多毕设源码里回测收益高得离谱原因后面第 5 章细说。2.2 为什么 Python 能扛住这套系统三点选型支撑选 Python 不是因为它快而是因为它让“策略想法到验证结果”的距离最短。第一pandas 的 rolling、shift、groupby 天生就是为 K 线计算设计的写一个双均线策略只要十行以内换成 C 光是处理数据结构就要半天。第二数据获取有现成的开源接口a 股日线数据可以直接通过公共数据接口拉取不需要自己维护爬虫。第三Python 的调度生态成熟APScheduler 加两行配置就能实现“每个交易日收盘后自动跑任务”不需要额外部署队列系统。有些同学问过能不能用 Node.js 或者 Go 写。可以但你要自己处理 DataFrame 缺失值、时间序列对齐、复权换算这些在 Python 里都是已经解决的问题。做毕设的核心是快速出结果不是重新发明轮子。2.3 最小工程骨架先按这份目录把代码立起来我一般建议拿到任何源码包后先重建一份最小骨架确认每个文件职责单一再往里面填逻辑。下面这份目录结构是这类毕设源码最常见的形式你可以拿它跟手里的 zip 对比stock_auto_trader/ ├── config.py # 全局配置股票代码、策略参数、风控参数 ├── main.py # 启动入口注册定时任务 ├── data_fetcher.py # 行情数据获取与本地落库 ├── strategy/ │ ├── __init__.py │ ├── base.py # 策略基类规定输入输出接口 │ └── dual_ma.py # 双均线策略 ├── trader/ │ ├── __init__.py │ └── order.py # 订单状态机 ├── risk/ │ └── risk_manager.py # 仓位与频次风控 └── backtest/ ├── __init__.py └── engine.py # 回测撮合引擎main.py 是这个系统的心脏它决定了每天什么时候干活。下面是一份最小启动入口from apscheduler.schedulers.blocking import BlockingScheduler from data_fetcher import fetch_and_store_daily from strategy.dual_ma import DualMaStrategy from trader.order import OrderManager def job(): # 1. 拉取最新行情并追加到本地库 df fetch_and_store_daily(code600000) # 2. 用最新数据生成策略信号 strategy DualMaStrategy(short_window5, long_window20) signal strategy.generate_signal(df) # 3. 把信号交给订单管理器去处理 order_manager OrderManager() order_manager.exec_if_needed(signal) if __name__ __main__: scheduler BlockingScheduler() # 周一至周五 15:05 执行A股已收盘当日K线定型 scheduler.add_job(job, cron, day_of_weekmon-fri, hour15, minute5) scheduler.start()这段代码要讲清楚两点。第一为什么选 15:05 而不是盘中跑日线策略依赖“当日收盘价”这个确定值盘中跑时最后一根 K 线还在变动信号会跳来跳去。第二BlockingScheduler 会阻塞主线程所以这个进程必须常驻运行适合部署在实验室或家里的普通电脑上。config.py 里通常会这样组织参数# config.py CODE 600000 # 目标股票代码 SHORT_WINDOW 5 # 双均线短周期 LONG_WINDOW 20 # 双均线长周期 MAX_POSITION_RATIO 0.3 # 单票最大仓位占总资金比例 MAX_TRADES_PER_DAY 1 # 单日最大下单次数 SLIPPAGE 0.001 # 滑点比例回测时使用 COMMISSION 0.0003 # 佣金比例双边收取把所有魔法数字集中到 config.py是这类源码包最值得学习的设计习惯。答辩时老师问“这个 5 和 20 是什么意思”你只需要指着配置文件说这是短中两条均线的周期可调。3. 策略信号是怎么算出来的双均线与订单状态机策略模块是整套系统里最像“玄学”的部分因为同样的均线参数换一只股票、换一段行情结果可能完全相反。但源码实现本身并不玄它就是一个确定的函数输入历史 DataFrame输出一个字符串信号。下面拆开看。3.1 双均线策略的源码实现与未来函数边界双均线的逻辑一句话短期均线上穿长期均线时买入下穿时卖出。下面是去掉多余装饰后的核心实现import pandas as pd class DualMaStrategy: def __init__(self, short_window5, long_window20): self.short_window short_window self.long_window long_window def generate_signal(self, df: pd.DataFrame) - str: # 关键用 shift(1) 取前一根K线的收盘价避免未来函数 close df[close].shift(1) short_ma close.rolling(self.short_window).mean() long_ma close.rolling(self.long_window).mean() diff short_ma - long_ma cross_up (diff 0) (diff.shift(1) 0) cross_down (diff 0) (diff.shift(1) 0) if cross_up.iloc[-1]: return buy if cross_down.iloc[-1]: return sell return hold这里最值得讲的是shift(1)这一行。很多初写策略的同学会直接拿当天的close算均线然后当天就按这个信号买入这在实盘里是不可能的——收盘价走完的那一刻当天交易已经结束了。shift(1)的含义是只用昨天及以前的数据决定今天的动作这是策略代码正确性的最低底线如果源码里没有这一步回测收益再高都是假的。参数方面短窗常用 3 到 10长窗常用 20 到 60。短窗越小信号越频繁交易成本越高长窗越大信号越迟钝趋势启动后进场晚。毕业设计里最稳妥的组合是 5 和 20因为它是均线策略的经典默认值论文里也好解释。3.2 订单状态机把信号变成不重复发单的可执行订单策略输出“buy”之后不能直接下单。你还需要回答三个问题当前是不是已经持有今天是不是已经发过单账户里有没有足够资金这三个问题由一个订单管理器统一回答源码里通常长这样from enum import Enum from datetime import datetime class OrderStatus(Enum): PENDING pending # 等待条件确认 SUBMITTED submitted # 已提交到模拟撮合 FILLED filled # 已成交 REJECTED rejected # 被风控拒绝 class OrderManager: def __init__(self, init_cash100000): self.cash init_cash self.position 0 self.last_order_date None self.status OrderStatus.PENDING def exec_if_needed(self, signal: str) - str: today datetime.now().strftime(%Y-%m-%d) if signal hold: return no_order # 同一天不允许重复发单防止信号反复触发 if self.last_order_date today: print(同日重复信号跳过) return skipped if signal buy and self.position 0: print(已有持仓跳过买入) return skipped if signal sell and self.position 0: print(空仓状态跳过卖出) return skipped self.last_order_date today self.status OrderStatus.SUBMITTED print(f{today} 提交 {signal} 订单) return fsubmitted_{signal}状态机要解决的核心问题是重复发单。双均线金叉出现后如果接下来几天 short_ma 一直在 long_ma 上方策略会连续输出多天“buy”没有这个状态机就会连续开仓仓位迅速失控。这里的last_order_date就是一把锁同一个交易日只认第一个信号。init_cash和position两个字段维护了账户状态这个设计虽然简单但已经构成了一个可扩展的仓位模型。想改成按比例买入只需要在订单提交前计算buy_amount self.cash * ratio / price然后把结果传给模拟撮合模块。3.3 核心参数怎么定短窗、长窗、仓位与止损参数不是一拍脑袋定的。毕业设计里最常用的方法是网格扫描固定长窗遍历短窗固定短窗遍历长窗然后把每组参数的回测结果拉成一张表挑一组回撤可控且收益合理的。仓位上限建议设在总资金的 20% 到 30% 之间。满仓单票是这类系统最常见的翻车原因因为 A 股个股跌停时卖不出去满仓意味着没有补救余地。止损参数建议以固定比例实现比如亏损达到买入价的 5% 就强平。注意止损逻辑要放在风控模块里不要和策略信号混在一起。策略负责“什么时候买”风控负责“什么时候必须走”两个模块职责分离代码才不会改一处崩一处。4. 把股票自动交易系统在本地跑起来环境、数据与启动时序理论讲完现在进入实操。很多同学卡在第一步源码拿到手装了一堆依赖但跑起来就报错。这一章给出一条经过验证的最短路径从创建虚拟环境到看到第一条日志全程半小时内完成。4.1 环境准备与依赖清单Python 版本建议 3.9 到 3.11太新或太旧都可能遇到某些库没有预编译包的问题。先创建虚拟环境python -m venv venv source venv/bin/activate # Windows 下用 venv\Scripts\activate依赖就装五个核心库装多了反而容易冲突pip install pandas apscheduler akshare sqlalchemy如果你手里的源码用了 tushare就把 akshare 换成 tushare其余不变。pandas 负责数据处理apscheduler 负责定时调度akshare 负责拉行情数据sqlalchemy 负责把 DataFrame 写进 SQLite。装好后验证环境python -c import pandas, apscheduler, akshare; print(env ok)出现env ok说明环境没问题。如果 akshare 安装失败常见原因是网络超时可以换国内镜像源再装一次。4.2 接入行情数据并落库数据是整套系统的地基。下面这段代码演示如何拉取一只股票的历史日线并写入本地 SQLiteimport akshare as ak import pandas as pd from sqlalchemy import create_engine def fetch_and_store_daily(code: str, start_date: str 20200101): # 拉取不复权的日线数据 df ak.stock_zh_a_hist( symbolcode, perioddaily, start_datestart_date, end_date20250101, adjustqfq, # qfq 表示前复权回测必须用前复权 ) # 统一列名方便后续策略代码调用 df.columns [ date, open, close, high, low, volume, amount, amplitude, pct_change, change, turnover ] df[date] pd.to_datetime(df[date]) # 写入 SQLite如果表已存在则追加 engine create_engine(sqlite:///market.db) df.to_sql(daily_quote, engine, if_existsappend, indexFalse) print(f{code} 数据已更新最新日期 {df[date].iloc[-1]}) return df这里有两个参数必须说明。symbol传的 6 位数字代码比如600000不需要带交易所前缀。adjustqfq是前复权它把历史价格按分红送股做了修正保证均线计算不会被除权跳空误导。如果这里改成即不复权遇到股票分红时 K 线会出现向下跳空双均线策略会错误触发卖出信号。代码里还有一处值得注意if_existsappend表示追加写入但日常使用中重复跑会写入重复数据。改进办法是每次全量拉取后先清空表再写或者用date code做唯一索引去重。毕设阶段用追加模式问题不大但要在论文里写明数据更新的幂等性如何保证。4.3 启动调度与验证系统是否跑通数据通了之后把第 2 章的 main.py 完整跑起来。第一次建议不要等定时任务触发手动执行一次主流程python -c from data_fetcher import fetch_and_store_daily from strategy.dual_ma import DualMaStrategy from trader.order import OrderManager df fetch_and_store_daily(600000) signal DualMaStrategy(5, 20).generate_signal(df) OrderManager().exec_if_needed(signal) 三条日志是跑通标志第一行出现“数据已更新”第二行没有报错返回第三行打印出“提交 x 订单”或“无操作”。然后查一下数据库里的数据行数python -c import pandas as pd from sqlalchemy import create_engine engine create_engine(sqlite:///market.db) df pd.read_sql(SELECT COUNT(*), MAX(date) FROM daily_quote, engine) print(df) 能看到统计结果说明数据落库成功。最后再把 main.py 的调度器启动观察是否在设定的时间点触发任务。注意一点如果你的电脑在 15:05 正好休眠或断网任务会错过APScheduler 默认不补跑。毕设演示时可以手动触发一次任务效果等同。5. 自动交易系统的五个常见坑与排查记录这一章是血泪经验汇总。下面五个坑每一个我都见过对应版本的源码翻车现场。按“现象 → 原因 → 解决”的顺序写方便你排查时对照。5.1 回测曲线漂亮但模拟盘变形滑点和手续费被忽略了现象回测年化 35%最大回撤 8%看起来很完美。但把同一策略放到最近三个月的行情里模拟跑收益明显变差。原因源码的回测引擎按收盘价直接成交没有建模手续费和滑点。A 股双边佣金加印花税合计大约千分之二到千分之三加上买卖价差一笔交易的实际成本在 0.3% 到 0.5% 之间。双均线这种高频换手的策略一年交易几十次成本累积起来会吃掉大部分收益。解决在回测引擎里加上成本模型。买入价 信号日次日开盘价 × (1 滑点比例)卖出同理每笔成交扣佣金比例。参数在 config.py 里已经预留回测时把SLIPPAGE0.001、COMMISSION0.0003带入成交价计算即。5.2 均线信号闪烁当日K线未走完导致的未来函数现象策略在盘中每隔一分钟跑一次买入信号出现后十分钟又变成持有过半小时又变成买入日志里同一日期出现多次信号。原因盘中最后一根 K 线没有走完收盘价还在跳动均线值随之变化金叉死叉来回穿越。这是典型的未来函数变体——用了“当前不确定的价格”来决定是否交易。解决两个方案任选。一是把系统改成每日收盘后运行只在 15:00 之后取数据计算这是日线策略的标准做法二是在策略函数里丢弃 DataFrame 最后一行只信任已经收盘的 K 线。改完后再看日志同一个交易日最多出现一次信号变化。5.3 非交易时段启动任务第一笔信号是空的现象周一早上 8:00 启动系统日志打印“数据已更新”但策略输出 hold一整天都没有交易。检查数据库发现当天根本没有新数据写入。原因A 股周末和节假日不交易数据源的日线接口不会生成新记录。系统按工作日调度启动但当天可能不是交易日或者当天数据源接口尚未更新。解决在 job 函数里加交易日判断。数据源本身提供交易日历接口直接查询当天是否交易如果非交易日直接 return不打日志不跑策略。另外把调度时间设到 15:05 之后避开数据源更新时间。还需要在任务开头判断“最新数据日期是否等于今天”防止节假日误跑。5.4 行情接口限频导致数据中断现象系统连续运行几天后某天日志出现大量连接超时或返回空 DataFrame之后策略一直输出 hold但没有人发现。原因免费的公共数据接口通常有频率限制短时间调用次数太多会被临时封禁。日线接口虽然调用频率低但配合回测批量拉取几百只股票时很容易触发限制。解决加三层防护。第一数据拉取函数里设置重试机制失败后等待 3 秒重试最多重试 3 次。第二把历史数据落库后增量更新只拉最近几个交易日而不是每次全量拉取。第三回测批量使用时在循环里加time.sleep(1)控制频率。这三层加上后接口中断的概率会大幅下降。5.5 同一天重复开仓仓位失控现象账户里有 10 万资金某天日志里出现五笔买单每笔都按三成仓位买入结果五笔加起来超过了总资金。原因策略每天输出信号后订单管理器没有按日期去重。金叉出现后连续几天信号都是 buy如果last_order_date没有正确更新或进程重启后状态丢失就会重复开仓。解决把订单管理器的状态落盘。常见做法是在本地维护一个order_log.csv每次提交订单先读文件检查今天是否已经下过单然后追加写入。另外在风控模块加一条硬约束总持仓市值不得超过总资金的MAX_POSITION_RATIO超了就拒绝新订单并告警。这两条同时生效能兜住绝大多数重复开仓问题。6. 从毕设到可验证仿真撮合与参数扫描两个进阶点如果你的答辩老师问“这个系统真的能交易吗”你不需要展示实盘但要展示一个让结果可信的仿真环境。这里有两个进阶点成本低、效果好。6.1 一个能说服导师的仿真撮合器回测引擎里最容易被质疑的就是成交价格。真实交易中你不可能刚好买在收盘价所以仿真撮合要手动加上成交约束。一个能说服人的撮合逻辑是信号生成于 T 日收盘后成交于 T1 日开盘按 T1 日开盘价加滑点计算def simulate_fill(next_bar: pd.Series, order_side: str, slippage: float 0.001): 按次日开盘价撮合买入加滑点卖出减滑点 if order_side buy: fill_price next_bar[open] * (1 slippage) elif order_side sell: fill_price next_bar[open] * (1 - slippage) else: return None # 加上双边佣金 commission fill_price * 1000 * 0.0003 return fill_price * 1000 commission这段代码的核心是“次日成交”它彻底切断了当日信号与当日成交之间的未来函数联系。回测结果可能变差但变得可信答辩时老师的下一个问题你就已经回答完了。6.2 参数扫描找出稳健区间而不是单点最优手动试参数是玄学参数扫描是工程。把短窗从 3 到 20、长窗从 20 到 60 各取几个代表值两两组合回测最后画出收益和回撤的二维表import itertools results [] for short_win, long_win in itertools.product([3, 5, 10, 20], [20, 30, 60]): strategy DualMaStrategy(short_win, long_win) stats run_backtest(strategy, df) # run_backtest 返回收益和回撤 results.append({ short: short_win, long: long_win, annual_return: stats[annual_return], max_drawdown: stats[max_drawdown] }) results_df pd.DataFrame(results) print(results_df.sort_values(annual_return, ascendingFalse))看结果时关注两个点一是收益最高的参数往往伴随最大的回撤不要只挑收益最高二是一段参数区间内结果相近时选中间值说明策略对参数不敏感稳定性更好。我看过很多同学挑出收益最高的那组参数结果换一段行情就失效真正的稳健区间比单点最优值重要得多。最后说说我的个人习惯每做完一次参数调整我会把回测净值曲线和基准指数叠在一张图上先看这条曲线有没有长期向上的趋势再看下跌段的坡度是否在自己能承受的范围。只看最终收益数字是容易骗自己的。希望这一整套从源码拆解到跑通再到避坑的路径能帮你把这个项目真正变成自己的东西也希望帮到你。本文还有配套的精品资源点击获取