尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Java AI开源量化平台:从回测到实盘的核心链路搭建指南

Java AI开源量化平台:从回测到实盘的核心链路搭建指南 简介这是一套基于Java的AI开源量化交易平台源码面向程序员与量化交易开发者覆盖期货、股票、外汇、数字货币等场景定位替代文华、MC、金字塔等商业软件内置历史回放、策略研发、模拟交易和实盘交易模块兼顾全自动与半自动操作尤其适合借助CTP接口开展程序化交易和策略研发。压缩包共578个文件约1.83MB主体是407个Java文件构成的核心交易逻辑62个JS与24个Vue搭建前端交互界面另有14个XML、10个JSON、7个yml及Dockerfile等配置辅助文件整体工程分层清晰便于二次开发与部署。已有195人学习下载。通过这份源码可完整了解行情接收、策略编写、模拟/实盘下单的落地链路获得前端界面、配置文件与自动化交易脚本既能作为个人量化系统的脚手架也为研究CTP对接和程序化交易提供了现成范例。1. Java AI开源量化平台凭什么敢喊“秒替文华、MC、金字塔”基于JAVA的AI开源量化交易平台最容易让人误会的地方在“AI”这两个字母。很多朋友以为把深度学习模型接进去就能自动赚钱真正做过的人都知道AI只占三成工作量剩下七成全是行情接入、历史回放、回测、柜台适配这些又碎又容易翻车的底座工程。我研究过不少国内开源量化项目凡是能被团队真正用起来的基本都有一个共性Java做调度和交易链路Python或Java混合做策略与模型并且把历史回放、策略研发、模拟交易、实盘交易四件事按顺序打通。这篇就按我搭这套底座的实际顺序把模块边界、配置参数、上线避坑一次说清楚给准备评估或改造开源量化平台的你参考。2. 先拆模块边界Java核心、行情采集、交易柜台各管哪一段2.1 替代文华/MC/金字塔的真正难点不在界面在多语言文华、MCMultiCharts、金字塔这类商业软件体验最好的是“开箱即用”最难受的也是“用完即锁”。它们的策略语言天生是脚本沙盒指标库由厂商维护回测报告格式固定柜台接口由平台封装你想在信号产生后加一道人工复核、想引入Python训练的AI因子、想把成交回报同步给公司内部OA都得看平台脸色。国内开源的Java量化平台替代的从来不是界面而是“交易链路的所有权”。行情模块自己接K线落库自己管策略引擎直接跑Java代码下单通过CTP柜台直连半自动和全自动只是信号路由上的一个开关。对一个有开发能力的团队来说这意味着能真正掌控从数据到成交的每一环而不是被商业软件的更新节奏拖着走。下面这张表是我在选型时习惯用来对照的模块边界模块文华/MC/金字塔的现状Java开源平台的开放点行情数据内置数据源格式封闭自行对接CTP/第三方行情tick与bar自由落库指标与策略平台自带公式语言Java或Python实现可复用常见指标库也能接AI模型历史回放多数有但速度与细节受限可控制回放倍速按bar或tick重放策略研发参数优化黑匣子居多回测逻辑开源可自己加滑点和手续费模型交易柜台平台内置下家通道直连CTP模拟盘和实盘前置可切换自动化全自动为主半自动不易定制信号先入队手动确认或自动直接成交按需切换2.2 用Spring Boot组装一套最小骨架行情、策略、柜台、控制台我习惯把工程切成四个Maven模块market负责行情接入与K线合成strategy负责指标计算和信号生产trader负责柜台适配与下单console负责人工复核和日常监控。这样拆的好处是行情断线不会拖垮下单线程策略升级不用碰柜台代码半自动复核界面也只依赖console模块对外暴露的HTTP接口。quant-platform/ ├── market/ # 行情接入、tick合成bar、历史数据落库 ├── strategy/ # 指标计算、策略信号、AI模型调用 ├── trader/ # CTP柜台适配、订单管理、风控检查 ├── console/ # 半自动复核页面、日志与监控接口 └── common/ # 公共的数据结构、工具类工程以Spring Boot作为粘合层很省事配置文件统一管理柜台参数和回测参数各模块之间通过Java接口交互不直接互相依赖实现类。一个最小骨架的依赖通常是spring-boot-starter-web提供HTTP接口H2或PostgreSQL存K线和成交记录另外再按需引入JSON库和定时任务组件。parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId !-- 版本按你项目当前的Spring Boot稳定版选 -- /parent dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.h2database/groupId artifactIdh2/artifactId scoperuntime/scope /dependency /dependenciesH2在开发阶段用来存一分钟bar和交易记录完全够用跑回放时数据量不大单机也不会卡。真正上实盘之后我建议把行情库切到PostgreSQL因为要同时存tick、bar、委托记录和成交记录H2的并发写性能在长时间运行时偏弱。这一段选型属于“先跑通再上量”的典型路径别一开始就上分布式存储平台跑没跑通都不一定。2.3 从Tick到Bar再到OrderSignal先把三个核心数据类定死数据结构的定义决定了后面所有代码好不好写。国内做期货和股票量化绕不开三个基本对象交易所原始推送的tick、合成K线时的bar、策略输出给交易模块的订单信号。我一般先把它们定义成Java record不可变对象在并发场景下最省心。public record Tick( String instrumentId, String exchange, long receiveNanos, double lastPrice, long lastVolume, long volume ) {} public record Bar( String instrumentId, String exchange, Instant closeTime, double open, double high, double low, double close, long volume, long turnover ) {} public record OrderSignal( String signalId, String instrumentId, Bar triggerBar, Side side, double price, int lots, String source, double confidence, boolean requiresConfirm ) {}Tick里保留receiveNanos是为了后续排查行情延迟尤其在做高频回放时这个字段能帮你判断本地处理耗时是不是把信号拖慢了。Bar用closeTime而不是openTime作为主时间戳后面会专门讲这是回放和实盘最容易差一根K线的大坑。OrderSignal里的confidence字段是给AI模型预留的requiresConfirm则是半自动模式的关键开关全自动模式下直接把它置为false半自动模式下置为true信号就会落到人工复核队列。3. 历史回放与策略研发怎么做从“倒放K线”到“不像印钞机的回测”3.1 历史回放的三种实现Bar回放、Tick回放、混合回放历史回放是策略研发的基础设施它的目标不是“能放几根K线”而是让策略在回放中走一遍和实盘完全相同的内存路径。常见做法有三种Bar回放、Tick回放、混合回放。Bar回放实现最简单把历史bar按时间间隔推送给策略引擎即可。优点是速度快适合验证日线或小时级的策略逻辑缺点是看不到盘口变化成交判断只能用bar的高低点近似精细度有限。Tick回放则把每一笔逐笔数据按时间顺序重放能模拟出更真实的成交环境但数据量成倍增长国内期货tick数据一个月就是几个GB存储和加载都要做优化。混合回放是我在中期研发阶段最常用的方案主线用bar推进策略状态遇到需要模拟开盘或收盘成交时再落到tick数据上去做细节撮合。实现一个基础的Bar回放器并不复杂关键是别把推进节奏写死ScheduledExecutorService scheduler Executors.newScheduledThreadPool(1); AtomicInteger idx new AtomicInteger(0); ListBar bars barRepository.findByInstrumentAndRange(symbol, start, end); scheduler.scheduleWithFixedDelay(() - { if (idx.get() bars.size()) { scheduler.shutdown(); return; } Bar bar bars.get(idx.getAndIncrement()); strategyEngine.onBar(bar); }, 0, 500, TimeUnit.MILLISECONDS);这里我用scheduleWithFixedDelay而不是scheduleAtFixedRate原因是策略处理bar时可能会卡顿固定延迟方式能保证上一根处理完再推下一根不会出现任务堆积。500毫秒是研发阶段的默认间隔改成50毫秒就是加速回放改成1秒就是慢放。实战中回放必须支持倍速切换否则你想在涨停板附近反复看策略行为却没有慢放能力等于没有回放。回放还有一个容易被忽视的细节数据里如果混入了未复权或停牌缺口策略在回放时看到的K线会跟实盘不一致。我一般会在回放前做一次数据校验把单根bar的时间差超过正常间隔的记录过滤或标记出来宁可少一段数据也不让脏数据污染策略研发。3.2 回测引擎必调的四个参数手续费、滑点、保证金、成交判断回测结果“像印钞机”几乎是每个新手都会遇到的场面原因通常不是策略好而是回测参数太善良。回测引擎至少要把下面四个参数明明白白暴露出来否则收益曲线没有任何参考价值。参数示例设置对结果的影响手续费按交易所官方费率双边收取费率设低了高频策略收益会被显著高估滑点按最小变动价位2倍低滑点会把不存在的利润算进曲线保证金按交易所标准比例保证金比例过小回撤和爆仓风险全被掩盖成交判断用bar高低点触发判断触发逻辑不同成交价差异可能超过滑点本身成交判断是最容易写错的一段。很多回测代码直接写成“信号产生后立刻以当前价成交”这在bar级回放中等于偷看未来。我常用的做法是信号在当根bar收盘后产生下一根bar开盘后判断是否成交并且在判断成交时用高低点做限价触发。if (signal.side() Side.BUY) { if (bar.high() signal.price()) { fillPrice Math.max(signal.price(), bar.open()); } else { return 0; // 本根bar没有触及限价信号失效 } }这里的关键逻辑是信号是上一根bar的收盘条件触发的所以不能在本根bar内部用收盘价去成交只能用下一根bar的开盘后高低点判断。fillPrice取max(限价, 开盘价)是为了模拟开盘跳空时你不可能买到比开盘价更低的价。把这套逻辑换成市价单也一样只是成交价会直接取bar.open加上滑点。3.3 策略研发不只看收益曲线样本外验证和参数归档策略研发阶段最容易犯的错是在同一段历史上反复调参调到曲线好看就以为策略成立。这属于典型的过拟合。我在做策略研发时会把历史数据切开样本内用来选参数样本外用来做一次性的验收测试。样本外测试只跑一次跑完无论结果好坏都记录归档绝不回头用样本外结果再调参。一个参数集配置通常长这样strategy: name: maCross params: fast: [5, 10, 15] slow: [20, 30, 60] split: inSample: 2023-01-01/2024-06-30 outSample: 2024-07-01/2025-06-30 slippageTicks: 2 commissionRate: 0.00023参数搜索阶段只允许跑inSample区段选出在样本内表现稳定的参数组合然后用outSample段做一次完整回测。如果样本外曲线和样本内差距巨大直接放弃这个策略方向不挽救。参数归档也要做每次回测完把参数集、样本区间、收益回撤数据以固定格式落库后面复盘时才能知道这个策略是“当时运气好”还是“真的有逻辑”。没有这层归档策略研发就是靠记忆过日子换台电脑连参数都找不回来。4. 模拟交易与实盘交易落地CTP适配、全自动与半自动怎么切换4.1 CTP连接与登录模拟前置和实盘前置的配置差异国内做期货量化绕不开CTP柜台。开源平台里看到的CTP接入基本都是封装了官方接口通过JNI或JNA让Java调用。模拟交易一般用simnow模拟柜台实盘则必须使用期货公司提供的实盘前置地址。两个环境的配置字段是一样的但地址和权限完全不同混在一起配置就是灾难。CTP登录通常需要这几项关键配置配置项模拟交易实盘交易交易前置地址simnow提供的公网地址期货公司分配的实盘地址行情前置地址simnow提供的行情地址期货公司对应的行情地址brokerId/investorId/passwordsimnow开户信息实盘开户信息appId/authCodesimnow申请的认证信息实盘认证信息权限受限CtpClient client new CtpClient(); client.setConfig( System.getenv(TRADE_BROKER_ID), System.getenv(TRADE_INVESTOR_ID), System.getenv(TRADE_PASSWORD), System.getenv(TRADE_APP_ID), System.getenv(TRADE_AUTH_CODE) ); client.connect(tcp://xxx.xxx.xxx.xxx:xxxxx, tcp://xxx.xxx.xxx.xxx:xxxxx);CTP认证状态不是一蹴而就的回调顺序一般是OnFrontConnected、OnRspAuthenticate、OnRspUserLogin三步都完成才算真正就绪。我见过不少人只盯着OnFrontConnected就急着下单结果报错说“未登录”这其实是对CTP状态机不熟悉。正确做法是封装一个状态变量只有登录成功状态才允许订单模块工作。simnow的appId和authCode是有有效期的后文避坑部分会专门提但配置这里就有必要强调别把模拟和实盘的连接配置写在同一个文件里开关切换很容易把实盘地址填进模拟环境导致一笔测试单进到真实柜台。4.2 信号路由全自动直接下单半自动先过人工复核全自动和半自动的本质区别不在策略代码而在信号提交给交易模块前的路由策略。我的实现方式很直接所有策略信号统一丢给一个SignalRouter由它决定是直接送单、进人工复核队列还是丢弃。ExecutorService tradeExecutor Executors.newFixedThreadPool(2); LinkedBlockingQueueOrderSignal pendingQueue new LinkedBlockingQueue(); public void accept(OrderSignal signal) { if (!riskPass(signal)) { reject(signal); return; } if (signal.requiresConfirm() || systemMode ! MODE_FULL_AUTO) { pendingQueue.offer(signal); return; } tradeExecutor.submit(() - placeOrder(signal)); }全自动模式会把requiresConfirm为false的信号直接提交到tradeExecutor下单。半自动模式则无论信号是否要求人工确认都先进pendingQueue由console模块通过HTTP接口把待确认信号展示出来人工点击确认后再提交送单。这个设计把“人要不要参与”变成策略参数而不是系统参数你可以让A策略全自动、B策略半自动互不干扰。还有一个必须处理的点订单幂等。每次提交订单时把signalId拼进客户端订单编号柜台重复回报时才能去重。实际交易中网络闪断会导致请求重发如果没有幂等保护一笔信号可能被提交两遍半自动复核区点了两次确认就开双倍仓位这就是事故了。4.3 实盘风控前置涨跌停过滤、撤单和仓位上限下单前一道风险过滤是整条链路里最不该省的部分。策略代码可能写错AI模型可能漂移人工确认也可能手滑但前置风控只要逻辑正确就能把大多数低级错误挡在交易所门外。public boolean riskPass(OrderSignal s) { if (!inTradingTime(s.instrumentId(), s.triggerBar().closeTime())) { return false; } if (s.price() limitUpPrice(s.instrumentId())) { return false; } if (account.position(s.instrumentId()) s.lots() lotLimit(s.instrumentId())) { return false; } return true; }涨跌停过滤是期货和股票都必须做的。商品期货虽然日常波动不至于天天涨停但极端行情里如果策略发出追单信号在涨停价开多很可能买在最高点而且无法成交然后被套。仓位上限则可以按合约维度设定最大手数这个值通常等于该策略最大允许占用保证金的1到2倍防止AI模型给出极端信号时把账户仓位打满。撤单逻辑也要提前想好。实盘中限价单如果长时间不成交要不要撤掉重发是个经典选择题。我通常给未成交单设置一个超时时间比如10秒没成交就撤单并记录日志是否重发由人工或策略端决定。这比无限等待安全得多也避免收盘时挂着废单被交易所清理。5. 上线与维护的5个常见坑从K线时间戳到CTP掉线5.1 回放时间戳与实盘差一根K线指标全部错位现象同一套策略在历史回放里信号很准切到模拟盘后信号总是慢一根K线怎么看都别扭。原因这类问题十有八九是bar时间戳定义不一致。K线数据可以用该根bar的起始时间作为时间戳也可以用结束时间作为时间戳。国内行情商提供的日线数据大多用自然日做标识但分钟线数据各家用得混乱回测时如果按开盘时间存bar实盘引擎在bar结束时才触发策略两者天然错位一根。解决统一以bar的closeTime作为唯一时间戳回测、回放、实盘全部遵守这个约定。数据入库时就把时间戳规格化不要等策略层再去猜。这个规范最好写进代码注释和接口文档里否则换个人维护数据模块分分钟又把openTime带进来。5.2 在行情回调里直接下单把数据线程堵死现象开盘瞬间行情剧烈程序突然卡顿下单延迟数百毫秒严重时行情推送直接断流。原因CTP或第三方行情的回调线程是单线程的你在回调里执行数据库写入、订单提交、HTTP调用这些耗时操作会阻塞整个行情线程。多品种同时推送tick时这种阻塞会被放大成雪崩效应。解决行情回调只做数据解析和入队所有业务逻辑交给独立线程池。具体做法是行情线程把tick放入一个无锁队列或LinkedBlockingQueue业务线程从队列里消费并合成bar、触发策略。下单则用单独的tradeExecutor绝不复用行情消费线程。判断这个坑有没有踩到可以打印行情线程的排队延迟超过100毫秒就要警惕。5.3 模拟盘成交太理想回测曲线像印钞机现象回测时资金曲线一路向上上模拟盘却连续亏损两个环境的同一策略结果完全对不上。原因回测是没有对手盘的市价单基本都能在理想价位成交模拟柜台如simnow虽然下单接口真实但撮合逻辑和实盘仍有差异尤其在高频场景下模拟盘的成交率远高于真实盘。另外回测手续费和滑点设置太乐观也放大了收益。解决回测阶段就按最小变动价位的两倍设置滑点商品期货多数据品种最小变动价位是1个tick滑点就按2个tick算。手续费也按交易所官网费率加一定比例计算不要用零费率。模拟盘验证阶段空开至少两周对比模拟盘成交价和回测成交价的差距如果差价稳定超过滑点设置说明回测引擎还有问题。5.4 行情断线重连后出现K线缺口策略静默运行现象盘中网络闪断十秒行情恢复后策略继续跑但总资产和持仓明明没变策略却没有新交易检查日志才发现本地K线缺了一段。原因断线期间行情没收到重连后从当前bar开始继续累积之前缺掉的几分钟数据没有补策略状态机判断出现断裂。更隐蔽的是重连后重复收到相同bar导致持仓或指标被重复计算。解决重连成功后先做时间戳检查本地最新bar的closeTime到当前时间之差超过一个bar周期就必须先补历史缺口。补数据可以通过行情商的历史接口拉取也可以用CTP的查询接口补齐。补完数据后要把未完成bar丢弃重建不要接着旧bar继续写否则指标计算会带着脏数据一路偏下去。5.5 密码与认证码进了git仓库开盘前才暴露现象团队协作时有人把simnow的investorPassword和authCode直接写在application.yml里提交到Git仓库等项目推到远程后才发现密码已经暴露只能连夜改密码。原因开发阶段为了方便配置文件和代码放在一起没人注意提交时把关。等真正要对接实盘时才意识到这个习惯有多危险。解决配置与代码强制分离。密码、认证码、前置地址全部通过环境变量或加密配置中心注入。我习惯用Jasypt对Spring Boot配置项做加密本地开发时通过环境变量传入解密密码生产环境则用独立的密钥管理。另外appId和authCode有有效期simnow的认证信息尤其明显建议在日历上设提醒过期前一周重新申请不要等到开盘时才发现认证失败。6. 把AI模型放进Java平台的最后一公里半自动复核台怎么搭6.1 Python训练、Java推理按置信度拆成三条路Java平台接AI模型现实落地方式是在Python侧训练和沉淀特征Java侧通过HTTP或消息队列调用推理服务。模型输出不能直接当圣旨我习惯把预测结果拆成三条路double confidence mlPredict(features); if (confidence 0.7) { orderMode MODE_FULL_AUTO; } else if (confidence 0.3) { discard(signal); } else { signal signal.requiresConfirm(true); pendingQueue.offer(signal); }置信度高于0.7视为高确信信号允许全自动送单低于0.3直接丢弃避免模型在噪声区域反复开仓中间区域全部转成人工复核半自动模式在这里才有真正的意义。AI agent承担的是识别和推荐而不是最终决定权。6.2 模型升级也按“回放→模拟→小仓实盘”三段上线每个新模型版本我都强制走一遍三段流程先拿至少六个月历史数据做回放看信号分布有没有异常再切到模拟柜台跑一到两周对比模拟成交与回放成交的偏差最后用小仓位实盘跑一周。三段都在观察信号质量而不是收益率。AI模型换参数太随意很容易在历史数据上自欺欺人把回放当成了许愿池。这套流程跑下来我对开源量化平台最深的体会是Java不是最快最时髦的语言但做交易链路它有足够的稳定性和工程生态AI最后真正起作用的不是模型多聪明而是信号从模型到柜台这段路多可靠。希望帮到你。本文还有配套的精品资源点击获取
返回列表