
前言

````
```go
股票量化系统QTYX支持三因子轮动"抄作业"功能。最新运行情况如下所示:从2025年12月18日至2026年5月15日,策略累计收益率+19.83%,超额同期沪深300指数(+6.74%)+13.09%。
⚠️ 风险提示:以上数据为测试策略数据,不构成投资建议,不预示未来表现,投资须谨慎。
```
````
``````
`````
````
```
`"抄作业"虽然方便,但只是为了让大家直观上手。由于每个人的交易风格不同,所以因子参数需要个性化调整,ETF池需要个性化配置。
为了满足大家DIY自己的量化系统的初衷,我们将DAY21中终极系统,一步步升级为本地Web版三因子轮动系统。`
```
````
`````
``````

本次升级

V2.1.5 版本的核心在于重构了回测模块,建立了全流程的日志复盘机制。回测不再是一个黑盒,每一次调仓逻辑、参数生效过程及最终绩效都被结构化地记录在logs/目录中,确保了策略评估的可解释性与可追溯性。****(每一次小升级,都是搭建你个人量化系统的一个坚实小步,从"能用"到"好用",我们一起见证系统的进化)
V2.1.5 围绕回测模块进行了三项核心改进:
1. 参数确认机制:点击回测后首先弹出确认对话框,强制用户核对回测周期、动量周期、持仓数量等关键参数,避免因参数误填导致的无效回测,提升了回测结果的有效性。

2. 异步执行与进度可视化 :回测任务在后台线程中异步执行,前端通过轮询机制实时更新进度条(0%→100%),解决了长时间回测导致界面卡顿的问题,用户可随时了解数据加载、策略计算等环节的执行状态。


3. 全流程日志持久化 :回测启动时自动在 logs/ 目录下生成以时间戳命名的日志文件(如 backtest_20260116_143052.txt),完整记录了参数配置、数据加载结果、每次调仓决策、每笔交易执行及最终绩效指标,为策略复盘、问题定位和参数优化提供了可追溯的依据。

现在策略参数在系统内拥有了"统一调度权":
以下是 V2.1.5 回测模块相关代码的功能讲解,按执行流程分段说明:
参数确认对话框
python
@app.callback( [Output("backtest-confirm-modal", "is_open"), Output("confirm-start-date", "children"), ...], [Input("start-backtest-btn", "n_clicks"), Input("cancel-backtest-btn", "n_clicks")], [State("backtest-date-range", "start_date"), State("initial-capital-input", "value"), ...])def show_backtest_confirm(...):
| 功能点 | 实现方式 |
|---|---|
| 拦截回测请求 | 点击"开始回测"时并不直接执行,而是触发确认对话框 |
| 参数回显 | 将用户在界面填写的参数(日期、资金、动量周期等)格式化后显示在对话框中 |
| 二次确认 | 用户点击"确认开始回测"后才真正启动回测任务 |
| 取消操作 | 点击"取消"则关闭对话框,不做任何处理 |
设计目的:防止用户因误触或参数填错就开始耗时较长的回测,降低无效计算。
异步回测执行 + 进度监控
启动后台线程
python
def start_backtest_after_confirm(...): global backtest_progress_global # 重置进度状态 backtest_progress_global = { 'running': True, 'current': 0, 'total': 100, 'status_text': '🚀 启动回测引擎...', 'params': {...} # 保存参数 } # 启动后台线程(不阻塞Dash主进程) thread = threading.Thread(target=run_backtest_in_background) thread.daemon = True thread.start() return backtest_progress_global, False, False # 启用轮询
后台执行函数
python
def run_backtest_in_background(): """在后台线程中执行回测""" # 1. 设置日志记录器 backtest_logger, log_file_path = setup_backtest_logger() # 2. 分步骤更新进度(5% → 100%) backtest_progress_global.update({ 'current': 5, 'status_text': '🔧 初始化回测引擎...' }) # 3. 加载ETF数据(10% → 50%) for i, etf_code in enumerate(etf_pool): progress = 10 + int((i / total_etfs) * 40) backtest_progress_global.update({ 'current': progress, 'status_text': f'📥 加载数据 {i+1}/{total_etfs}: {etf_code}' }) # 4. 配置Cerebro引擎并运行回测(55% → 90%) cerebro = bt.Cerebro() cerebro.addstrategy(MomentumRotationStrategy, ...) results = cerebro.run() # 5. 计算绩效指标并保存结果(90% → 100%) backtest_progress_global['result'] = {...} backtest_progress_global['running'] = False
前端进度轮询
python
@app.callback( [Output("backtest-progress-bar", "style"), Output("backtest-progress-text", "children"), ...], [Input("backtest-progress-interval", "n_intervals")], [State("backtest-progress-store", "data")])def monitor_backtest_progress(...): progress = backtest_progress_global if progress.get('running'): percent = (current / total) * 100 return {...} # 返回进度条样式和文字 elif progress.get('result'): return {...} # 返回回测结果(收益率、夏普比率、净值曲线)
设计要点:
-
回测在独立线程中运行,Dash界面不卡顿
-
通过全局变量
backtest_progress_global在主线程和后台线程间共享状态 -
前端每 500ms 轮询一次,实时更新进度条和状态文字
日志持久化
python
def setup_backtest_logger(): """设置回测日志记录器""" log_dir = "logs" if not os.path.exists(log_dir): os.makedirs(log_dir) # 以时间戳命名日志文件 log_filename = os.path.join( log_dir, f"backtest_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt" ) logger = logging.getLogger('backtest') # 同时输出到文件和控制台 file_handler = logging.FileHandler(log_filename, encoding='utf-8') console_handler = logging.StreamHandler() return logger, log_filename
日志记录内容
| 阶段 | 记录内容 |
|---|---|
| 启动 | 回测参数(周期、资金、动量周期、持仓数量等) |
| 数据加载 | 每只ETF的数据行数、成功/失败状态 |
| 策略执行 | 每次调仓的日期、持仓变化、动量排名 |
| 交易执行 | 每笔买卖的价格、数量、盈亏 |
| 结果汇总 | 总收益率、夏普比率、最大回撤、胜率、交易次数 |
策略内部日志
python
class MomentumRotationStrategy(bt.Strategy): def log(self, txt, dt=None): if self.p.printlog: dt = dt or self.datas[0].datetime.date(0) print(f'{dt.isoformat()} | {txt}') # 会被logger捕获
设计要点:
-
临时替换
builtins.print,将backtrader的策略日志重定向到自定义logger -
日志文件包含完整的时间戳 和日志级别,便于复盘分析
-
每次回测生成独立文件,避免覆盖历史记录
代码架构总结
bash
用户点击"开始回测" ↓【参数确认对话框】 ← 拦截、回显、二次确认 ↓启动后台线程 (threading.Thread) ↓【run_backtest_in_background】 ├── 创建日志文件 (logs/backtest_时间戳.txt) ├── 更新进度: 5% → 初始化 ├── 更新进度: 10%-50% → 加载ETF数据 ├── 更新进度: 55%-90% → 运行backtrader回测 └── 更新进度: 100% → 保存结果到全局变量 ↓【前端轮询】 (500ms间隔) ├── 运行中 → 更新进度条 └── 完成 → 显示净值曲线 + 绩效卡片 + 完成图标

页面功能展示

目前已经完成的功能介绍如下所示:
我的轮动池:实时展示ETF动量排名列表,按动量分数从高到低排序。点击"详情"按钮可对该ETF进行手动买入/卖出操作。点击"刷新数据"重新计算所有ETF的动量分数并更新排名。点击"自动下单"自动对比当前持仓与前5名ETF,执行调仓。




我的账户:展示总资产、持仓市值、可用资金、当前盈亏四大核心数据。通过饼图展示持仓市值分布,柱状图展示持仓收益率排行,表格展示详细持仓明细。
策略回测:设置回测日期、初始资金、动量周期、调仓周期、持有数量等参数,点击开始回测后运行Backtrader引擎,展示总收益率、夏普比率、最大回撤、胜率、交易次数、盈亏比等指标,并生成净值曲线图。
参数配置:配置策略参数(动量周期、调仓周期、持有数量)、交易参数(手续费率、滑点、最小交易单位)、风险参数(止损止盈、回撤警戒、仓位限制等)。
实盘明细:点击刷新按钮获取QMT当日委托记录,展示委托时间、证券代码、证券名称、买卖方向、委托状态、委托量、成交数量、已撤数量、委托价格、成交均价、冻结资金、订单编号、废单原因等信息。
数据管理:点击"立即更新"按钮,遍历所有ETF从网络获取历史数据并保存到本地"行情数据库"目录,同时更新表格显示每只ETF的数据状态。
系统监控:展示服务器、数据库、策略运行、交易接口四大组件的运行状态;展示CPU和内存使用率曲线图;展示系统运行日志列表。
策略文档:展示动量轮动策略的说明、使用指南和注意事项。
`````
````
```go
AI学习代码
```
从星球下载代码后,您可以直接将代码输入到AI大模型中。配合我们提供的分点式提示词(已按由浅入深的顺序梳理成多个小问题),您可以循序渐进地理解代码结构、掌握核心逻辑,并高效完成二次开发。
````
第一部分:系统整体认知
`````
```markdown
请根据以上代码,用简短的一句话回答以下每个问题:1. 这个系统叫什么名字?2. 系统是用什么框架搭建Web界面的?3. 回测功能用的是哪个库?4. 实盘交易连接的是哪个券商接口?5. 历史行情数据是从哪个网站获取的?6. 实时行情数据是从哪里获取的?7. 数据默认存在哪个目录下?8. 持仓记录存在哪个文件里?9. 系统默认运行在哪个端口?10. 页面左侧有几个导航菜单?11. 默认监控多少只ETF?12. 动量计算周期默认是多少天?13. 调仓周期默认是多少天?14. 默认每次持有几只ETF?15. 自动调仓时默认每只买多少股?16. 账户数据每隔几秒自动刷新?17. 系统状态显示在哪里?18. 退出程序时QMT进程会被自动关闭吗?
```
```go
第二部分:数据相关
```
```
`请根据以上代码,用简短的一句话回答以下每个问题:1. get_etf_data() 函数获取数据时,先查哪里再查哪里?2. 本地CSV文件找不到时,系统会怎么办?3. AKShare获取数据用的是哪个函数?4. 沪市ETF代码前面加什么前缀?深市呢?5. 数据保存到CSV时,文件名格式是什么?6. 数据管理页面的状态数据是真实的还是模拟的?7. 点击"立即更新"按钮后,数据会保存到哪里?8. 更新数据时,如果某只ETF获取失败,会影响其他ETF吗?9. 本地数据目录叫什么名字?10. 回测时优先使用本地数据还是网络数据?11. 动量计算需要最少多少天的数据?12. 数据表的K线数列显示的是什么内容?13. 数据状态有哪几种可能的值?14. 如何判断本地数据是否"最新"?15. CSV文件保存时用什么编码格式?`
```
```go
第三部分:交易相关
```
```markdown
请根据以上代码,用简短的一句话回答以下每个问题:1. QMT客户端是运行在主进程还是子进程?2. 主进程和子进程之间通过什么通信?3. 下单函数 place_order 的 is_buy=True 表示买入还是卖出?4. QMT下单时,order_type=23 表示什么?24表示什么?5. 卖出操作会先检查什么?6. 买入操作会先检查什么?7. 下单手续费默认是万分之几?8. 自动调仓时,先执行卖出还是先执行买入?9. 调仓成功后会保存到哪里?10. 手动下单后,用户通过什么组件看到反馈?11. 订单提交失败的提示是什么颜色的?12. 订单提交成功的提示是什么颜色的?13. 实盘明细页面从哪里获取委托记录?14. 委托状态"已报"、"部分成交"、"全部成交"分别是什么意思?15. 什么情况下订单可以被撤单?16. 废单原因存放在哪个字段?
```
```
`第四部分:策略相关`
```
```markdown
请根据以上代码,用简短的一句话回答以下每个问题:1. 动量计算用的是收盘价还是开盘价?2. 动量公式:当前价格减去多少天前的价格?3. 动量分数的单位是什么(百分比还是绝对值)?4. 动量分数为正是涨还是跌?5. backtrader策略类叫什么名字?6. 策略的调仓逻辑写在哪个方法里?7. 每次调仓时,卖出什么条件的ETF?8. 每次调仓时,买入什么条件的ETF?9. 买入时的仓位比例默认是多少?10. 买入数量为什么要除以100再乘以100?11. 回测时的手续费是万几?12. 回测报告的夏普比率从哪里获取?13. 回测报告的最大回撤从哪里获取?14. 回测报告的交易次数从哪里获取?
```
```
`第五部分:前端/UI相关`
```
```markdown
请根据以上代码,用简短的一句话回答以下每个问题:1. 页面切换是通过什么机制实现的?2. 哪个组件负责定时刷新账户数据?3. 定时刷新的间隔是多少毫秒?4. 表格数据是通过哪个回调函数生成的?5. 搜索框输入后,表格会实时过滤吗?6. ETF代码和名称的映射存在哪个字典里?7. 轮动池表格里,排名前5名的颜色是什么?8. 表格中动量分数的正负用什么颜色区分?9. 下单模态框是如何被触发展开的?10. 模态框里的"当前价格"是怎么获取的?11. 填写数量和价格后,委托金额会自动计算吗?12. 饼图展示的是什么数据?13. 柱状图展示的是什么数据?14. 系统监控页面的日志是真实的还是模拟的?
```
```
`第六部分:自动调仓相关``请根据以上代码,用简短的一句话回答以下每个问题:1. 自动调仓按钮的ID是什么?2. 自动调仓需要先点击哪个按钮准备数据?3. 上一次的持仓记录存在哪个文件里?4. CSV文件里按什么格式存储持仓?(几列?什么分隔符?)5. 如何判断哪些ETF需要卖出?6. 如何判断哪些ETF需要买入?7. 调仓过程中如何显示进度条?8. 进度条更新是通过什么组件触发的?9. 调仓完成后状态文本变成什么?10. 如果前后持仓完全一样,系统会怎么做?11. 调仓结果会保存在哪里(除了CSV)?12. 每次调仓最多操作几只ETF(买入+卖出)?`第七部分:异常与边界情况
```
```markdown
请根据以上代码,用简短的一句话回答以下每个问题:1. QMT连接失败时,系统会崩溃吗?2. 获取某只ETF数据失败时,其他ETF还会继续更新吗?3. 本地没有top5_holdings.csv文件时,自动调仓会怎么处理?4. 实盘明细页面,没有当日委托时表格显示什么?5. 我的账户页面,没有持仓时图表显示什么?6. 下单时资金不足,用户会看到什么提示?7. 下单时可卖数量不足,用户会看到什么提示?8. 网络断开时点击刷新数据,会发生什么?9. 回测时如果没有足够的历史数据,会返回什么?10. 动量计算时数据不足会返回什么值?11. 多个用户同时点击自动下单会冲突吗?12. 子进程异常退出后,主进程会怎样?
```
```
第八部分:配置与常量`请根据以上代码,用简短的一句话回答以下每个问题:1. QMT的安装路径在代码的哪里配置的?2. QMT的session_id是多少?3. 沪市ETF代码以哪几个数字开头?4. 深市ETF代码以哪几个数字开头?5. 默认的ETF池子里有多少只ETF?6. 动量周期在代码里默认值是多少?7. 持有数量的默认值是多少?8. 回测初始资金的默认值是多少?9. 下单默认数量的默认值是多少?10. 系统版本号是多少?`
```
`````
````
```
`部署与运行`首先在 CMD 里安装依赖。复制粘贴这一整行,回车安装
```
`pip install dash dash-bootstrap-components plotly pandas numpy akshare backtrader``然后,正常启动 QMT 客户端,独立交易模式。``接下来,用PyCharm运行这个项目,右键python文件点运行即可。运行成功后,控制台会显示:``Dash is running on http://127.0.0.1:8090/`打开浏览器,输入:http://localhost:8090 即可使用。
````
`````

总结

当系统迭代至可满足实战需求时,我们会推出线上培训课程,从框架设计到代码实现,逐层拆解、完整讲解。
```
`关于Web轮动系统迭代改进的过程可以查看链接:21天搭建ETF量化系统`
```
```````
``````
关于QTYX的使用攻略可以查看链接:[QTYX使用攻略](https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzUxMjU4NDAwNA==&action=getalbum&album_id=2524935077060001794#wechat_redirect)
`````
````
```
`如何用Python从0一步步搭建出一套ETF量化交易系统,我们推出了DAY1-DAY21链接如下:21天搭建ETF量化交易系统`掌握了方法之后,可以换成期货系统、比特币系统、美股系统,然后在实战中不断去完善自己的系统了。
```
````
`````
``````
```````
说明
此系列为连载专栏,是《玩转股票量化交易》知识星球的配套学习资料,欢迎大家私信交流,共同进步成长!
星球介绍点击:知识星球《玩转股票量化交易》精华内容概览
