简介:面向股票量化投资爱好者与入门开发者,这份资源提供了完整的量化系统源码和配套学习教程,帮助用户从环境配置开始一步步构建自己的Python股票分析平台。系统以MySQL作为数据存储基础,通过运行win_main.py启动,教程围绕市场数据获取、历史行情分析、交易策略编写、回测执行、结果评估与风险控制等核心模块展开,并对主脚本的主要函数和运行逻辑进行了解读,适合希望通过实战掌握量化策略开发全流程的投资者和Python学习者。资源共244个文件,压缩包大小仅3.53MB,其中py文件是系统核心策略逻辑,pyc为编译生成的中间文件,png、jpg、gif图片用于界面展示和流程示意,json与ui文件承担参数配置与窗口布局,css、html、md文件提供前端样式和说明文档,整体分类明确,便于按模块查找和修改代码。目前已有38人学习下载,对于需要快速搭建本地量化实验环境、理解从策略设计到回测验证完整链路的学习者来说,是一套轻量、实用的参考资源。
1. 这套 Python 股票量化系统源码到底能做什么
一个压缩包里塞着几个 CSS、一个setting.html、一个demo.html和win_main.py,第一眼不像完整产品。但把它拆开会发现,这是一条“环境准备 -> 依赖安装 -> MySQL 建库 -> 主入口启动 -> 策略回测”的完整链路,适合刚把 Python 和 MySQL 装好、想知道量化系统代码怎么组织的人。对写过策略但没拆过工程的人也有价值:能看到一个最小可用系统的边界在哪,哪些模块必须自己补。下面按实际跑起来会遇到的顺序说,先把pip install -r requirement.txt这一步说透。
2. requirement.txt 依赖管理与 Python 环境搭建
2.1 一份典型 requirement.txt 里有什么
拿到源码包先别急着跑python win_main.py,先看requirement.txt。这套系统里,Python 版本和依赖库直接决定后面会不会出现兼容性问题。很多网上的 Python 安装教程只教你装解释器,真正让新手卡住的往往是装完依赖之后 import 失败。按常见的量化工程来分类,requirement.txt里的库大概会覆盖三块:数据获取、数值计算和界面展示。
第一块是数据源库,比如akshare、tushare这类通过 HTTP 接口抓行情和财务数据的库。第二块是numpy、pandas,量化策略本质上就是在做 DataFrame 的切片、重采样和滚动计算,没有 pandas 整个系统跑不起来。第三块是 GUI 或 Web 框架,因为压缩包里能看到setting.html、demo.html和一堆layui.css、layer.css样式文件,说明前端展示部分多半是 Web 页面,后端需要一个框架来渲染这些页面,常见的是 Flask。
另外还有pymysql或sqlalchemy用来连 MySQL,matplotlib用来画回测曲线。下面是按依赖作用划分的一张参考表:
| 依赖组 | 常见库 | 作用 |
|---|---|---|
| 数据获取 | akshare / tushare | 拉取行情、财务、板块数据 |
| 计算 | numpy / pandas | 序列计算、数据清洗、滚动窗口 |
| 数据库 | pymysql / sqlalchemy | 连接 MySQL,读写行情与策略结果 |
| 展示 | flask / PyQt5 | 渲染设置页和行情页 |
| 可视化 | matplotlib / pyecharts | 画净值曲线、信号图 |
这里把依赖拆成组,是因为排错时能更快定位:数据拿不到,问题在数据源;页面打不开,问题在 Web 框架。
2.2 一条命令装完并解决下载慢的问题
安装依赖时直接用官方给的命令:
pip install -r requirement.txt如果机器上同时存在 Python 2 和 Python 3,最好先用python -m pip install -r requirement.txt,确保装到当前python解释器对应的 pip 里。官方源在国内经常很慢,常见做法是追加清华镜像:
pip install -r requirement.txt -i https://pypi.tuna.tsinghua.edu.cn/simple --timeout 60这段命令里,-i指定镜像源,--timeout 60把网络超时时间拉长到 60 秒,避免大包下载到一半断掉。如果某个包编译失败,比如pandas在新版本 Python 下没有预编译 wheel,可以先单独装编译依赖,或者把 Python 降到系统支持更成熟的版本。requirement.txt里的==版本号是开发者的锁版本,不要随意改成最新的,因为numpy和pandas的版本对相互之间的二进制接口要求很严格,版本不匹配会直接出现numpy.dtype size changed这类只有重装才能解决的报错。
2.3 依赖装完但 import 失败的三个原因
依赖全部装完之后,在项目根目录执行:
python -c "import pandas, numpy, pymysql; print('ok')"如果输出ok,说明基础环境没问题。如果报错,最常见的有三种:第一种是当前命令行的工作目录不在项目根目录,导致requirement.txt没被读对,这时先cd到源码目录再装;第二种是 pip 装到了系统全局环境,而运行系统时用了虚拟环境,两个环境不是同一个site-packages,解决方法是创建虚拟环境后在同一个环境里安装和执行;第三种是系统的 Python 版本太低,比如 Python 3.6 跑新版 pandas 直接不支持,这时要读一下源码里的说明,或者看win_main.py顶部的版本判断代码。
提示:装依赖前先执行
python --version确认解释器版本,再执行which python或where python确认正在使用哪个解释器,避免装了半天发现装到了另一个环境里。
3. MySQL 用户与建库:跑通前的第一道关卡
3.1 为什么量化系统要有 MySQL
量化系统的运行离不开历史数据和实盘数据。如果只做一次性计算,用 CSV 文件也能凑合,但策略要反复回测,需要增量更新行情数据、保存每次回测参数和结果,还得在多进程或多线程下同时读写,CSV 很容易出现写冲突。MySQL 的 InnoDB 引擎支持行锁和事务,适合这种场景。
这套源码在说明里明确要求用户名root、密码88888888,说明代码里很可能直接写死了一组数据库连接配置。也就是说,本地 MySQL 的用户名密码必须和源码里的配置一致,否则win_main.py启动后第一件事就是连接数据库失败。常见做法是创建专用数据库用户而不是直接用 root,但这套资源已经指定了 root。为了不修改源码访问数据库的配置,按它的要求把本地 root 密码调整成88888888是最省事的方式。如果电脑是专用的开发机,没有其他业务系统依赖 root 密码,直接改掉即可。
3.2 root 用户与密码 88888888 的 SQL 配置
先确认 MySQL 服务已经启动,再使用 MySQL 客户端执行以下 SQL:
-- 本机已存在 root 时: ALTER USER 'root'@'localhost' IDENTIFIED BY '88888888'; -- 没有 root 或需要新建时: CREATE USER IF NOT EXISTS 'root'@'localhost' IDENTIFIED BY '88888888'; -- 建库: CREATE DATABASE IF NOT EXISTS stock_quant DEFAULT CHARACTER SET utf8mb4; FLUSH PRIVILEGES;第一行是修改已有 root 账号的密码,第二行是兜底创建用户,第三行建数据库。utf8mb4用来存中文股票名称,比utf8支持更全。FLUSH PRIVILEGES重新加载权限表,保证密码立即生效。
如果 MySQL 是 Docker 启动的,需要先进入容器再执行:
docker exec -it mysql bash -c 'mysql -uroot -p -e "ALTER USER ..."'注意,如果在 root 密码未知的情况下想重置密码,需要修改 MySQL 配置跳过权限验证后重启,这是另一个话题。这里只建议在干净开发机上操作。
3.3 Python 连接 MySQL 的配置写法
系统运行时需要读取 MySQL 里的历史数据,也会把新的行情写回数据库。常见的数据库连接写法有两种:直接用pymysql,或者用sqlalchemy做 ORM。源码里更常见的是在win_main.py或config.py里维护一个连接参数对象:
import pymysql DB_CONFIG = { "host": "127.0.0.1", "port": 3306, "user": "root", "password": "88888888", "database": "stock_quant", "charset": "utf8mb4", "autocommit": True, } def get_conn(): return pymysql.connect(**DB_CONFIG)这里把autocommit打开,是为了避免写入数据后忘记commit()导致下次查询看不到。charset必须和建库时的字符集一致,否则中文会出现乱码。如果源码里用sqlalchemy,则连接串长这样:
from sqlalchemy import create_engine engine = create_engine("mysql+pymysql://root:88888888@127.0.0.1:3306/stock_quant?charset=utf8mb4")3.4 常见连接报错排查
启动后如果控制台报Access denied for user 'root'@'localhost',说明密码和源码预期不一致,回到 3.2 重新设置。报Unknown database 'stock_quant',说明建库失败或库名大小写不对。报Can't connect to MySQL server on '127.0.0.1',说明 MySQL 服务没启动,或者端口不是 3306。下面这张表可以直接对照:
| 报错片段 | 原因 | 处理 |
|---|---|---|
| Access denied | 用户名或密码不匹配 | 执行 ALTER USER 重设密码 |
| Unknown database | 没有创建目标库 | 执行 CREATE DATABASE |
| Can't connect | 服务未启动或端口错误 | 检查服务状态和配置端口 |
| Host not allowed | 用户只允许本机访问 | 用 root@localhost 或授权网段 |
提示:改完 MySQL 权限后一定要
FLUSH PRIVILEGES,特别是通过mysql_install_db初始化过的环境。
4. 从 win_main.py 看量化系统的主入口设计
4.1 主入口脚本通常拆成哪四段
win_main.py是整个系统的启动文件,运行python win_main.py后,控制台会加载配置、连接数据库、启动策略、最后弹出或拉起页面。常见的组织方式可以分成四段:配置加载、环境检查、策略调度、页面服务。这样拆的优势是,出问题时从日志能直接定位到是哪个环节失败。
配置加载阶段会读取setting.html对应的配置项。看到setting.html和demo.html这两个文件,可以判断系统的设置页和演示页是前后端一体的。后端把数据库连接、策略参数写入页面,前端通过 layui 组件展示;前端改动配置后,再通过 HTTP 请求回写配置文件。也就是说,setting.html不是静态页面,而是和win_main.py有交互的模板。
环境检查阶段会验证 MySQL 连接和关键目录是否存在。策略调度阶段创建线程池,把选股、回测、信号生成放到不同线程里。页面服务阶段启动 Flask 或本地 HTTP server,让浏览器能访问demo.html。如果这一层没起来,页面能打开但数据是空的,问题往往出在接口路由或数据库连接上,而不是前端。
4.2 setting.html 与 demo.html 在入口脚本里的作用
拿demo.html来说,它展示的是行情图、策略信号和回测结果。页面中的数据不会硬编码进 HTML,而是通过后端接口动态填充。比如:
@app.route("/api/kline") def kline(): code = request.args.get("code", "600000") data = load_kline_from_mysql(code) return jsonify(data)这里/api/kline接口接收股票代码,从 MySQL 读取 K 线数据,返回 JSON。前端拿到数据后用layui或echarts渲染成图表。也就是说,前端页面是结果展示层,数据全在数据库里,运行win_main.py的目的之一就是把页面服务和数据接口拉起来。
4.3 策略注册与启动流程
一个能扩展的量化系统,不会把所有策略都写在主脚本里,而是在内存里建一张策略注册表,每个策略对应一个执行函数。常见做法是:
STRATEGY_REGISTRY = {} def register(name): def decorator(func): STRATEGY_REGISTRY[name] = func return func return decorator @register("ma_cross") def ma_cross(ctx): df = load_data(ctx["code"]) df["ma5"] = df["close"].rolling(5).mean() df["ma20"] = df["close"].rolling(20).mean() return df["ma5"] - df["ma20"]register装饰器把函数存进字典,load_data从数据库取数据。主入口启动后遍历STRATEGY_REGISTRY,逐个调用策略函数。这种方式的好处是,加新策略只需要新增一个@register函数,不需要修改主入口逻辑。源码包的价值也在这里:它不是只有一次回测,而是给你一套可以持续加策略的框架。
4.4 启动命令与参数验证
运行系统的标准命令是官方给出的:
python win_main.py如果源码里定义了argparse启动参数,也可以传入数据路径、回测周期等。比如:
import argparse parser = argparse.ArgumentParser() parser.add_argument("--mode", choices=["backtest", "live"], default="backtest") parser.add_argument("--code", default="600000") args = parser.parse_args()--mode控制是回测还是实盘模拟,--code指定默认标代码。启动后观察日志,如果出现Connected to MySQL和Strategy ma_cross loaded,说明数据库和策略加载都成功。下面这张表是启动日志的关键检查点:
| 日志关键字 | 含义 |
|---|---|
| Connected to MySQL | 数据库连接成功 |
| Strategy ma_cross loaded | 策略加载成功 |
| Serving on ... | 页面服务已启动 |
| ERROR / Traceback | 启动中断,定位到对应阶段 |
如果只看到页面启动,没有策略加载日志,就要回头查 4.3 的装饰器是否被执行。
5. 把数据、回测和信号接成一条完整链路
5.1 行情数据获取与 MySQL 缓存
量化系统第一步是获取干净的行情数据。现在常用的免费数据源有akshare和tushare,它们都支持通过 pip 安装。以 akshare 为例,拉取个股日线并写入 MySQL 是这样的:
import akshare as ak import pymysql def update_daily(code="600000"): df = ak.stock_zh_a_hist(symbol=code, period="daily", adjust="qfq") df.columns = ["date", "code", "open", "close", "high", "low", "volume", "amount", "amplitude", "pct_chg", "change", "turnover"] conn = pymysql.connect(host="127.0.0.1", user="root", password="88888888", database="stock_quant", charset="utf8mb4") for row in df.itertuples(): sql = """ INSERT INTO kline (code,date,open,close,high,low,volume) VALUES (%s,%s,%s,%s,%s,%s,%s) ON DUPLICATE KEY UPDATE close=%s """ conn.cursor().execute(sql, (code, row.date, row.open, row.close, row.high, row.low, row.volume, row.close)) conn.commit() conn.close()这段代码先把数据源的列名统一,然后逐行写入kline表。ON DUPLICATE KEY UPDATE依赖(code, date)的唯一索引,重复下载时只更新行情,不会产生重复记录。表结构建议这样建:
CREATE TABLE IF NOT EXISTS kline ( id BIGINT AUTO_INCREMENT PRIMARY KEY, code VARCHAR(10) NOT NULL, date DATE NOT NULL, open DECIMAL(10,2), high DECIMAL(10,2), low DECIMAL(10,2), close DECIMAL(10,2), volume BIGINT, UNIQUE KEY uk_code_date (code, date) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;唯一索引是这里的关键,没有它,ON DUPLICATE KEY UPDATE就无效,数据会越攒越多。另外,DECIMAL(10,2)比FLOAT更适合存价格,避免浮点精度问题。
5.2 双均线策略回测实现
有了数据,就可以写最简单的双均线策略。逻辑是:5 日均线上穿 20 日均线买入,下穿卖出。回测代码如下:
import pandas as pd def backtest(df, fast=5, slow=20): df = df.sort_values("date") df["ma_fast"] = df["close"].rolling(fast).mean() df["ma_slow"] = df["close"].rolling(slow).mean() df["position"] = 0 df.loc[df["ma_fast"] > df["ma_slow"], "position"] = 1 df["position"] = df["position"].shift(1).fillna(0) df["ret"] = df["close"].pct_change().fillna(0) df["strategy_ret"] = df["position"] * df["ret"] df["nav"] = (1 + df["strategy_ret"]).cumprod() return dfshift(1)非常关键,它模拟“信号出现后第二天开盘才交易”,避免用到当天收盘价形成未来函数。把position后移一天,再用收益乘仓位,得到的是实际可实现的策略收益。cumprod累计净值用于计算最终收益。
5.3 回测指标与参数选择的边界
回测结果不能只看最后赚不赚钱,至少要算几个指标:总收益率、年化收益率、最大回撤、夏普比率、胜率。下面是评估表模板:
| 指标 | 计算方式 | 参考意义 |
|---|---|---|
| 总收益率 | nav[-1] - 1 | 衡量整体盈利 |
| 年化收益率 | nav[-1] ** (252/len(df)) - 1 | 换算成一年 |
| 最大回撤 | min(nav / cummax(nav) - 1) | 衡量风险 |
| 夏普比率 | mean(strategy_ret)/std(strategy_ret) * sqrt(252) | 收益风险比 |
| 胜率 | sum(strategy_ret>0)/count>0 | 交易质量 |
双均线参数 5/20 只是默认值,换到不同股票上表现差距很大。调参时要注意过拟合:在历史数据上反复试参数总能找到“好看”的组合,但换一段行情就会失效。所以回测至少应该分成样本内和样本外两段,样本内选参数,样本外验证稳定性。这也是为什么这套系统把回测放在源码里而不是只给结论,因为你需要自己动手验证参数边界。
6. 二次开发前的验证:从日志、单元测试到可视化检查
6.1 先跑通最小闭环
改代码前先把主线跑通。MySQL 能查、依赖能 import、win_main.py能启动,这三个条件缺一个都只能算没跑通。最小闭环是运行一次 Python 连接语句,能连上数据库再运行系统;不要一上来就改策略,否则环境问题和策略问题混在一起,日志都看不懂。先确认数据库和页面服务正常,再谈策略。
6.2 给策略函数加单元测试
改完ma_cross后,最稳的验证是给它写一个测试用例,输入一个已知的 DataFrame,断言仓位序列符合预期:
import unittest import pandas as pd class TestMaCross(unittest.TestCase): def test_position_shift(self): df = pd.DataFrame({"close": [1,2,3,4,5,6,7,8,9,10], "date": pd.date_range("2024-01-01", periods=10)}) result = backtest(df, fast=2, slow=4) self.assertTrue(result["position"].iloc[-1] in (0, 1)) self.assertFalse(result["ma_fast"].isna().all())测试里重点检查position是否被后移,以及均线列是否都生成。unittest是标准库,不需要额外安装,直接执行python -m unittest就能跑这个用例。
6.3 用真实数据画一次信号图
最后把数据从 MySQL 读出来画图,确认信号是出现在价格图上合理的位置:
import matplotlib.pyplot as plt def plot_signal(df): plt.figure(figsize=(12, 6)) plt.plot(df["date"], df["close"], label="close") buy = df[df["position"].diff() == 1] sell = df[df["position"].diff() == -1] plt.scatter(buy["date"], buy["close"], marker="^", color="red", label="buy") plt.scatter(sell["date"], sell["close"], marker="v", color="blue", label="sell") plt.legend() plt.show()position.diff() == 1表示仓位从 0 变成 1,对应买入信号;diff() == -1对应卖出信号。如果图中买入点出现在明显下跌段,就要回查数据源是否存在前复权缺失。完成这一步,二次开发的验证闭环就完整了:依赖、数据库、策略、回测、可视化全部对齐。
本文还有配套的精品资源,点击获取