开源交易系统“策略为王”源码拆解:回测引擎与二次开发实战

发布时间:2026/10/16 9:48:14
开源交易系统“策略为王”源码拆解:回测引擎与二次开发实战 简介这套源码来自策略为王开源交易软件面向具有C/MFC基础、想深入研究证券行情客户端实现的开发者与交易系统学习者。软件基本覆盖行情显示、K线、实时走势图等核心交易功能并已在VS2008下编译通过、可正常运行。压缩包约10.54MB共1299个文件源代码以219个h头文件和215个cpp实现文件为主另有656个svn-base版本库记录、bmp/ico界面资源、VC工程文件及若干说明文档便于按模块查看客户端框架、行情绘制与数据接口设计。已有1140人学习下载。最大价值在于拿到一套完整可编译的客户端源码可实际跟踪从行情接入、数据解析到界面刷新的完整链路同时包内保留了作者关于行情服务器搭建、推送服务程序编写与接收对应关系的问题说明适合继续开发或改造成自有行情系统。1. 策略为王真正值钱的不是下单按钮而是回测和策略引擎如果只看界面策略为王和市面上大多数股票行情软件长得差不多分时、K线、自选股列表该有的都有。等把源码翻到 backend/engine 那一层你会发现真正值钱的不是下单按钮而是「信号怎么来、信号来了怎么验证」这条链路上的回测引擎和策略调度框架。这套开源交易软件把行情接入、技术指标计算、回测撮合、模拟交易串成了一条完整流水线让你从「拉行情」一路跑到「出信号」再到「模拟下单」拿到的是一个完整闭环。它适合两类人一类是想验证交易思路的量化初学者缺的是一个能快速回测的代码骨架另一类是准备做二次开发的成型团队借这套骨架把自研策略接进去不用从零造轮子。2. 项目拆箱行情、策略、回测、交易四层代码分别扛什么事2.1 从根目录看职责每个目录对应一个可替换的模块先不急着敲命令把项目根目录摊开看。策略为王的目录归属是「按职责分层」的经典布局后端、前端、策略、数据四块互相独立每一块都能单独替换。常见的目录大概长这样strategy-king/ ├── backend/ # Python 服务端 │ ├── main.py # 启动入口创建应用并注册路由 │ ├── api/ # HTTP 与 WebSocket 接口层 │ ├── engine/ # 策略引擎与回测引擎 │ ├── collector/ # 行情数据采集与推送 │ └── models/ # 数据库模型 ├── web/ # 前端工作区 │ ├── src/ │ │ ├── pages/ # 行情页、策略页、回测结果页 │ │ └── components/ # K线图、指标面板等 ├── strategies/ # 用户策略脚本目录 │ ├── base.py # 策略基类 │ └── demo_ma.py # 示例策略 ├── data/ # 落盘数据与缓存 └── scripts/ # 初始化与一键启动脚本每个目录的替换边界都很清楚collector 换数据源不影响 engineengine 换策略不影响 apiweb 整体重写也不碰 backend。这个设计是我个人最喜欢的部分因为交易软件最大的通病是模块之间互相咬死想换个数据源就得重写一半业务代码而策略为王把这条边界切得很干净。对新手来说读源码先认准 strategies 和 engine 两个目录就够了对要深度改造的团队backend/collector 和 backend/engine 才是核心战场。前端页面只是消费结果真正的业务逻辑全在后端这两块。2.2 技术选型为什么不是 C 桌面端而是 Web Python早期的股票交易软件大多走 C 桌面客户端路线界面酷炫但改起来慢想加一个自定义指标得重新编译发布策略迭代一次的成本非常高。策略为王选了「Web 前端 Python 后端」的组合这是近些年开源交易项目比较主流的选择。核心原因是策略迭代频率远高于界面迭代频率Python 写策略和回测最顺手Web 端画图表、做交互体验也足够好两边用 WebSocket 保持实时推送不需要轮询。后端技术栈常见搭配是 FastAPI 提供 REST 接口和 WebSocket 网关Redis 做实时行情缓存SQLite 或 MySQL 做历史数据持久化。前端用 Vue 或 React 配一个 K 线组件库ECharts 这类方案在几千个数据点的场景下表现已经够用。整套组合单机就能跑也方便以后拆服务属于典型的「先跑通再壮大」路线。有一点需要注意选 Web 前端意味着实时性上限受限于浏览器渲染能力。如果你追求毫秒级刷新的专业交易终端这套架构需要在前端做 Canvas 重绘优化但做策略研究、回测分析、模拟盘这套组合的性价比远高于从头写一个桌面客户端。2.3 数据模型与接口约定前后端靠什么对齐前后端能对上话靠的是约定的接口和数据格式。落库的表结构一般围绕三块核心数据设计K线数据、策略配置、成交记录。K线表按周期分表存储日线和分钟线分开字段至少包含时间戳、开盘、最高、最低、收盘、成交量策略表保存策略名称、参数 JSON、启用状态成交记录表保存每次模拟下单的标的、方向、价格、数量、触发原因方便复盘。-- K线表以分钟线为例 CREATE TABLE symbol_kline_1m ( symbol VARCHAR(32) NOT NULL, ts DATETIME NOT NULL, open DECIMAL(10,3), high DECIMAL(10,3), low DECIMAL(10,3), close DECIMAL(10,3), volume BIGINT, PRIMARY KEY (symbol, ts) );这里把 symbol 和 ts 做成联合主键是为了防止同一标的同一时间戳的 K 线被重复写入。很多数据源断线重连后会补推重复数据没有这个联合主键回测数据会越攒越脏。行情采集进程写入前还会做一次 upsert保证幂等。HTTP 接口负责低频操作比如拉历史K线、提交回测任务、获取策略列表WebSocket 负责高频推送比如实时K线更新、成交回报。WebSocket 消息格式一般是 JSON 包裹的事件类型前端根据 type 字段分发到不同处理函数后端新增推送类型时前端不用改架构只加一个 case 分支就能接住。2.4 启动顺序与配置中心先理顺依赖再敲命令项目能跑不是把 main.py 启动就完事它需要 Redis 和数据库的支持。配置文件里通常集中定义行情源类型、数据库连接串、WebSocket 推送频率、回测手续费率这些参数。核心配置项大致如下配置项作用常见默认值data_source行情源类型决定 collector 加载哪个实现类mock / brokerredis_url实时行情缓存地址localhost:6379backtest_fee_rate回测手续费率0.0003backtest_slippage滑点按最小变动价位计1 tickkline_push_intervalK线推送间隔秒1启动顺序常规是这样先起 Redis再初始化数据库然后启动行情采集进程最后才启动 Web 服务。如果顺序颠倒常见现象是后端起来了但图表数据永远在转圈因为前端连的 WebSocket 没有数据源在喂。我一般会先用 scripts 目录里的初始化脚本把环境一次拉起来再单独启动采集进程做调试。这样既能看完整链路也能在出问题时快速定位是采集层、缓存层还是推送层的问题。3. 核心模块拆解一个信号从计算到成交的完整链路3.1 行情模块先聚合 K 线再向后端广播行情模块做两件事从上游行情源拉取逐笔成交或分时数据然后按时间周期聚合成 K 线。聚合逻辑是这一段的小核心处理不好会导致 K 线缺根或错位# collector/kline_worker.py async def on_tick(self, tick: TickData): # 将逐笔数据累加到当前周期桶里 ts self.align_timestamp(tick.ts, periodself.period) # 对齐到分钟/日线边界 if ts ! self.current_bar.ts: # 周期切换先把上一根完整 bar 推出去 await self.broadcast(self.current_bar) self.current_bar Bar(symboltick.symbol, tsts) self.current_bar.update(tick.price, tick.vol)align_timestamp 是最容易出问题的方法它把原始时间戳对齐到当前周期的起点。以 5 分钟线为例标准做法是把时间戳按 5 分钟整除取整比如 09:33 对齐到 09:30。如果对齐规则不统一前端的 K 线时间轴会整体错位。period 参数支持 1m、5m、15m、1d 等不同粒度共用同一套对齐逻辑只是整除的单位不同。聚合完成后K 线会被推给所有订阅者。订阅者可能是策略引擎、前端图表也可能是回测数据记录器。这种一对多的广播模型保证了同一个 K 线数据能同时被多个模块消费而每个模块不需要自己重新聚合一遍。3.2 策略引擎把指标计算和信号判断拆开写策略是交易软件的「大脑」这项目把指标计算和信号判断拆开为的是同一个指标函数能同时喂给回测引擎和实盘引擎避免两套逻辑各写一遍导致结果不一致。以均线交叉策略为例# strategies/demo_ma.py class MaCrossStrategy(StrategyBase): def init(self, fast5, slow20): self.fast fast self.slow slow def on_bar(self, bar, indicators): fast_ma indicators.sma(bar.close, self.fast) slow_ma indicators.sma(bar.close, self.slow) if fast_ma slow_ma and self._last_fast self._last_slow: self.buy(volume100, reason金叉) elif fast_ma slow_ma: self.sell_all(volume100, reason死叉) self._last_fast, self._last_slow fast_ma, slow_ma策略类只做两件事初始化参数和响应 on_bar 事件。指标计算全部由 indicators 对象用缓存取用同一根 K 线上多次调用同一个指标不会重复计算全量序列这在回测几万根 K 线时能省下大量时间。buy 和 sell_all 并不直接下到券商而是把订单指令发往订单代理由回测撮合或模拟盘撮合分别解释执行这条边界是策略代码能一键复用到不同场景的关键。fast 和 slow 是均线窗口参数volume 是每笔下单数量reason 会被记录到成交明细里方便之后看回测报告时逐笔复盘。改策略时新手最容易漏掉的是 _last_fast 和 _last_slow 这两个状态变量它们记录上一根 K 线的均线值用来判断交叉动作如果只比较当前值策略会在均线持续多头排列时反复下单。3.3 回测引擎事件循环撮合把每一笔成交写成记录回测引擎是这套源码里最值得细读的部分它的核心是一个保持时间顺序推进的撮合循环# backend/engine/backtest.py def run(self, strategy: StrategyBase, start, end): for bar in self.data_loader.load(start, end): strategy.on_bar(bar, self.indicators) # 生成订单指令 for order in strategy.pending_orders: fill self.match(order, bar) # 按当前 bar 撮合成交 if fill: self.portfolio.apply(fill) # 更新持仓与资产曲线 self.trades.append(fill) return self.portfolio.snapshot(), self.trades这个循环的关键约束是单线程按时间推进bar 来一根处理一根不允许策略提前看到下一根 K 线从根上掐断未来函数。match 撮合时默认按当期收盘价成交也可以配置成下一期开盘价用来模拟跳空风险。portfolio 负责维护资金、持仓和手续费每次成交都会记一笔 trade之后画资金曲线就是把这些 trade 按时间累加。参数体系里手续费率、滑点、印花税在初始化时配置常见默认值手续费万分之三滑点一个最小变动价位印花税仅卖出时收取。这些参数看起来琐碎但对回测结果影响很大尤其是交易频率高的策略摩擦成本能吃掉大部分名义收益。回测时建议把这三项都显式写出来别用默认零成本否则实盘和回测的差距会让你怀疑人生。3.4 资金管理与风控别让策略把仓位买到天上很多开源回测框架只做撮合不做风控结果策略在回测里满仓梭哈实盘一跑就爆仓。策略为王的 portfolio 层里加了仓位和风险阀值校验每次订单在进入撮合前都要过一遍风控# backend/engine/portfolio.py def can_place(self, order) - bool: # 单笔订单金额不超过总资产的比例 cost order.price * order.volume if cost self.total_asset * self.max_single_order_ratio: return False # 当前持仓集中度超过上限也拒绝 if order.symbol in self.positions: pos_ratio self.positions[order.symbol].value / self.total_asset if pos_ratio self.max_position_ratio: return False return Truemax_single_order_ratio 默认 0.2意思是单笔订单金额不能超过总资产的 20%max_position_ratio 默认 0.5单标的持仓市值不能超过总资产的一半。这两个阀值是活参数在策略初始化时可以根据自己的资金体量调整。风控拒绝的订单不会悄悄丢弃而是写进风控日志前端风控面板能看到拒绝原因。这套设计解决了一个常见痛点策略代码只管「什么时候买」资金够不够、仓位会不会过重是风控层在把关。二次开发时如果你有更复杂的风控逻辑比如最大回撤熔断、单日亏损上限直接在这个模块里加规则就行。3.5 交易执行抽象层模拟盘和实盘共用同一套策略代码做完回测想上模拟盘策略代码不用改因为回测和模拟盘走的是同一个下单代理接口。交易执行层做了一层抽象把订单通道解耦成接口两种场景各自实现一份# backend/engine/broker.py class PaperBroker(BrokerBase): def place_order(self, order): # 模拟成交按最新价立即成交并扣手续费 result self.positions.apply(order) self.push_event(order.symbol, fill, result) return result模拟盘的成交逻辑默认是「最新价即时成交」反馈快但忽略了滑点和排队延迟。如果要把策略接真实券商只需要实现 BrokerBase 的 place_order 接口内部调券商提供的 SDK 就行。前端、策略、回测报告全部复用不动换的只是一个实现类。从架构角度讲这是整套源码里最值得抄作业的设计。交易通道是最容易变化的依赖接口一旦抽象好实盘接入成本就从「重构系统」降到了「写一个适配器」。4. 把源码跑起来环境准备、数据导入与首个策略上线4.1 环境准备Python 虚拟环境与依赖安装先说环境。后端对 Python 版本有要求通常建议 3.9 以上太老的版本跑不动 asyncio 那套异步逻辑。前端是标准的 Node 项目需要装 Node 和包管理器建议 Node 版本不低于 16。后端依赖安装一定要用虚拟环境避免和系统 Python 环境互相污染cd backend python -m venv .venv source .venv/bin/activate pip install -r requirements.txtrequirements.txt 里常见的有 fastapi、uvicorn、redis、sqlalchemy 这些。装完可以用python -c import fastapi; print(fastapi.__version__)验证一遍避免装到一半中断导致的环境残缺。前端依赖在 web 目录执行包管理器安装如果网络不稳可以把镜像源换到国内地址能省大量时间。装依赖时不要跳过 requirements.txt 里看起来没用的包某些序列化库会在运行到特定接口时才报 ImportError到时候排查更费力。4.2 初始化数据库与导入历史数据项目首次运行前需要建库建表。初始化脚本会创建策略表、成交记录表和 K 线缓存表。历史行情数据是现实问题回测没有数据跑不起来项目通常提供一个数据导入脚本支持从 CSV 导入日线或分钟线。CSV 格式一般要求日期、开盘、最高、最低、收盘、成交量这几列cd scripts python init_db.py python import_csv.py --file ../data/demo_daily.csv --symbol DEMO导入脚本会校验每根 K 线的时间戳格式和是否重复非法时间会被跳过并打印警告。这里的 DEMO 是本地符号 ID不绑定任何真实市场你可以随意命名只要前后端、策略里都统一用这个 ID。导入完成后查一下表里的行数确保数据量没丢。这里有一个容易被忽略的点历史数据建议提前做复权处理。如果导入的是未复权数据回测时遇到除权除息日会出现价格跳变均线、止盈止损全被带偏。我一般会用前复权数据做回测并且每次追加新数据后重新复权一遍保证价格口径一致。4.3 启动行情采集、后端服务与 Web 前端数据就位后启动顺序还是老话先 Redis再行情采集再后端最后前端。用两个终端分别起服务一个起行情采集和后端一个起前端开发服务器。后端服务默认挂在本地某个端口前端开发服务器负责代理 API 请求和 WebSocket 连接。启动成功的标志不是页面能打开而是行情页的 K 线图上有数据在滚动更新以及后端日志里能看到类似「websocket connected」的记录。可以用 curl 快速探一下健康检查接口curl http://localhost:8000/api/health返回{status: ok}说明后端活着。如果前端页面打开了但数据区域空白优先检查 WebSocket 连接是否被代理拦截再看后端日志是数据源没连上还是缓存没写入。这种「前后端都正常但中间断了」的情况十次有八次出在 WebSocket 的代理配置上。4.4 跑第一个回测提交策略、看报告、解读指标项目自带示例策略是均线交叉你可以先跑它验证整条链路再改自己的。策略接入流程很简单在 strategies 目录下新建一个 Python 文件继承 StrategyBase 并实现 init 和 on_bar然后通过回测接口提交任务。前端会返回回测报告核心指标大致如下指标含义重点关注年化收益率策略在回测期内的年化收益越高越好但结合回撤看最大回撤资产峰值到谷底的最大跌幅越小越好代表风险水平夏普比率每单位风险获得的超额收益大于 1 算合格胜率盈利交易占比高频策略胜率通常偏低盈亏比平均盈利 / 平均亏损和胜率互补着看第一次跑回测建议把时间范围设小一点比如最近三个月确认流程通顺后再拉长到三年五年。小范围回测跑得快能快速暴露策略里的异常逻辑比如频繁下单、仓位超出余额这类问题。跑完看资金曲线如果曲线平稳向上没有巨大回撤说明策略逻辑基本成立如果资金曲线大幅震荡优先检查是不是手续费和滑点设太低把摩擦成本调回真实水平再看。5. 避坑实录跑这套源码时最常翻车的 5 个位置5.1 现象K 线图时间轴错乱前后顺序颠倒原因行情采集进程和数据库时区不一致。默认情况下进程用本地时区数据库存的是 UTC 时间聚合 K 线时时间点被对齐到了错误的小时边界导致分段全部偏移。解决在启动配置里强制指定时区统一用同一个时区处理行情时间戳导入历史 CSV 时写明文件里是本地时间还是 UTC。这个坑查起来很费时间因为前后端日志都正常只有把数据库里的时间戳逐条打出来才看得出偏移。5.2 现象回测收益高得离谱上模拟盘立刻变形原因这是典型的未来函数。策略代码里用了不该用的全局变量或缓存让后面的 K 线信号受还没发生的数据影响还有一种常见写法是在 on_bar 里访问了数据加载器的完整数据集而不是只访问当前 bar。验证方式是把回测数据的时间顺序整体倒过来喂给策略正常策略因为失去时间因果会跑得一团糟如果倒着跑收益依然稳定大概率是策略偷看了未来。从根上解决的办法很简单策略逻辑只允许使用当前 bar 和 indicators 提供的历史累计值禁止直接触碰数据加载器。5.3 现象历史回测在某天收益突然断崖式下跌原因除权除息导致价格跳变。股票分红送股后价格会调整未复权数据在除权日会出现不真实的多根 K 线。均线、止盈止损这些价格敏感计算全部被带偏。解决数据导入时统一做前复权处理并注意一个追加陷阱——前复权后的历史价格是随最新价变动的每次导入新数据后都要对旧数据重新复权否则同一只股票不同时间段的价格口径不一致回测结果前后矛盾。5.4 现象前端图表数据量一大就卡拖动缩放掉帧原因有人把全量 K 线一次性塞给前端渲染几千根 K 线加几十个指标序列浏览器绘图效率再高也扛不住。解决后端做数据采样前端显示区域只加载当前视野范围内的数据缩放时向服务端请求补丁数据指标计算放在后端完成前端只消费算好的序列。调完还要加一层防抖避免快速缩放时连续触发几十个请求把后端打崩。5.5 现象服务启动正常跑几个小时后行情推送停了原因WebSocket 长连接被中间层掐断后没有自动重连后端进程也没有断线重发机制。服务端日志看不到报错因为断线发生在代理层而不是应用层。解决前端 WebSocket 客户端加心跳和自动重连逻辑断线后按指数退避策略重连服务端保留最近几分钟的行情缓存客户端重连成功后从缓存补增量数据比全量重发高效得多。我见过很多项目在这里偷懒结果线上跑一天就断用户看到的行情全是停格的旧数据。6. 二次开发路线把默认的均线策略换成你自己的交易逻辑6.1 新增策略文件先复制再改比从零写靠谱自定义策略的第一步是在 strategies 目录下新建一个 Python 文件。我建议先复制 demo_ma.py 再改而不是从零写因为基类里有很多约定俗成的回调方法默认实现为空但你不能漏掉。改的时候重点看 init 里的参数定义和 on_bar 里的信号生成条件这两个方法定义了策略的全部行为。你把均线交叉逻辑换成布林带突破、MACD 背离或者你自己的独门判断都是在 on_bar 里改几行判断回测引擎、资金管理、前端展示完全不用动。6.2 接指标、换行情、连实盘三处扩展点想清楚再动手如果你要加独门指标在指标模块里新增一个函数接收序列数据返回计算后的序列。注意新指标要支持两种调用方式回测时全量一次性计算实盘时滚动增量计算两种模式的结果必须一致这是最容易翻车的地方差一个点都会让策略在回测和实盘上表现不一致。换行情源的话实现 collector 里的数据源接口返回标准 Tick 即可聚合逻辑复用。连实盘则实现 BrokerBase 的 place_order把订单转发到券商 SDK策略代码一行不用改。改完策略不要急着上模拟盘我一般走一套固定验证流程先跑一次正向回测再把回测数据顺序打乱跑一次确认没有未来函数然后换一段不同时间范围的数据做样本外测试确认策略不是只在某一段行情里有效。这些都过了才开模拟盘跑两周观察信号触发频率和手续费是否和回测预期一致。模拟盘阶段重点不是看收益而是看策略按预期触发、交易成本没吞掉大部分收益。我从第一次跑通这个项目到现在每次接手新的交易类代码都会强制走一遍「乱序回测验未来函数 样本外数据验过拟合」的流程这套习惯就是从研究这个项目的回测引擎开始的。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询