简介这是一套面向金融软件开发者的JAVA开源量化交易框架源代码专为有Java/Kotlin基础、关注系统安全与架构精简的中高级开发者设计可用于构建低延迟、高可靠性的本地化交易系统。资源共1766个文件主体为1560个Java类与158个Kotlin源码承担核心逻辑与空安全增强辅以thosttraderapi_se.dll等13个Windows动态库及10个Linux共享库对接CTP期货柜台另有Gradle构建脚本、JavaFX GUI界面资源CSS/MD及JDK 17适配配置整体压缩包仅23.58MB轻量且结构清晰。已有761人学习下载体现其在实盘安全改造与技术栈现代化方面的实践参考价值。读者可直接获取完整可编译工程包含ZooKeeper数据3DES双重加密实现、RFC 6902差异化同步协议落地、JavaFX嵌入式管理GUI启动控制机制以及从Python客户端移除、Protobuf替换到历史功能裁剪的全量重构说明是理解金融级Java系统安全加固与架构演进的典型样本。1. 这不是又一个“Java写个for循环算均线”的玩具框架它是一套能跑通实盘数据流、支持策略热加载、带完整回测引擎与模拟交易网关的量化开发底座你见过太多标着“量化框架”的 Java 项目——点开一看Main.java里硬编码读 CSVStrategy.java继承AbstractStrategy后只重写了onTick()连时间序列对齐都靠System.currentTimeMillis()拼凑。而这个开源项目不同它把「策略逻辑」和「基础设施」真正解耦了。核心模块用 Kotlin 写兼顾表达力与 JVM 兼容性但对外暴露纯 Java 接口自带基于 Netty 的低延迟行情订阅器支持 WebSocket TCP 双模接入回测引擎不是简单遍历 K 线而是按真实撮合规则模拟逐笔成交连滑点、委托状态机、订单簿快照回放都做了抽象。它不承诺“一键实盘”但把从本地回测 → 模拟交易 → 实盘对接的每层胶水代码都拆成了可替换组件。适合两类人一是想脱离 Python 生态、用强类型语言构建高可靠策略服务的中后台开发者二是高校金融工程方向学生需要一套结构清晰、能讲清楚“为什么这么设计”的教学级参考实现。它解决的不是“能不能跑”而是“怎么让策略代码不被基础设施细节污染”。2. 从源码结构看设计意图为什么用 Kotlin 写核心、却用 Java 做策略入口这个框架的源码包不是扁平堆砌而是按“运行时契约”分层组织。我拉下最新 release tagv2.3.1后先没急着编译而是用tree -L 2扫了一眼目录骨架src/ ├── main/ │ ├── kotlin/ # 核心引擎EventBus、OrderBook、BacktestEngine、GatewayAdapter │ ├── java/ # 策略接口层Strategy、Indicator、Context、TradeRequest │ └── resources/ # 配置模板gateway.yml, backtest-config.json └── test/ └── kotlin/ # 回测断言用 Kotest非 JUnit提示Kotlin 目录下没有strategy/包Java 目录下也没有engine/包——这是刻意为之的隔离。策略开发者永远只依赖java/下的 interface永远不 importkotlin/里的任何类。2.1 Kotlin 层用协程密封类解决事件驱动的“状态爆炸”问题量化系统本质是事件流处理行情到达、订单回报、定时触发…传统 Java 用ExecutorServiceBlockingQueue容易写出状态混乱的回调地狱。而本框架在kotlin/event/下定义了sealed interface MarketEvent { val timestamp: Long data class Tick(val symbol: String, val price: BigDecimal, val volume: Long) : MarketEvent data class Bar(val symbol: String, val open: BigDecimal, val close: BigDecimal, val period: Duration) : MarketEvent object Heartbeat : MarketEvent } class EventBus { private val channel ChannelMarketEvent(Channel.UNLIMITED) fun publish(event: MarketEvent) runBlocking { channel.send(event) } // 策略模块通过此方法注册监听内部用协程流做背压控制 fun T : MarketEvent subscribe( type: KClassT, handler: suspend (T) - Unit ): Job GlobalScope.launch { for (event in channel) { if (event::class type) handler(event as T) } } }参数说明Channel.UNLIMITED不是拍脑袋设的——实测在 5000 TPS 行情下若用Channel(CONFLATED)会丢 tickUNLIMITED配合runBlocking发送由下游协程消费速度自然限流suspend (T) - Unit是关键策略里的onTick()不再是void onTick(Tick)而是挂起函数可直接delay(10)模拟计算耗时不会阻塞整个事件环GlobalScope.launch被严格限制在EventBus内部外部策略无法创建任意协程避免内存泄漏。2.2 Java 层用抽象工厂模式屏蔽 Kotlin 实现细节策略开发者看到的Strategy.java长这样public abstract class Strategy { protected final Context context; // 纯 Java 接口无 Kotlin 类型 public Strategy(Context context) { this.context context; } // 所有钩子方法都是 Java 签名 public abstract void onStart(); public abstract void onTick(TickEvent event); public abstract void onOrderFilled(OrderFillEvent event); // 关键下单方法返回 Java Future不暴露 Kotlin Deferred public FutureOrderResponse placeOrder(OrderRequest request) { return context.getExecutionService().placeOrder(request); } }而Context的具体实现如DefaultContext.kt里getExecutionService()返回的是JavaExecutionService的实例该类是 Kotlin 写的但实现了ExecutionServiceJava 接口并用JvmBridge注解确保字节码兼容。这种设计让策略代码可以在 IntelliJ 里用 Java 语法写享受完整 LSP 支持编译成strategy.jar后被 Kotlin 主程序通过ServiceLoader动态加载升级框架 Kotlin 引擎时只要ExecutionService接口不变策略 jar 无需重编译。2.3 配置即契约YAML 文件如何决定数据流拓扑resources/gateway.yml不是简单的参数列表而是声明式定义组件连接关系# gateway.yml>import pandas as pd import numpy as np # 生成 1000 条 1分钟K线 dates pd.date_range(2023-01-01, periods1000, freq1T) timestamps (dates.astype(np.int64) // 10**6).astype(int) # 转毫秒 df pd.DataFrame({ timestamp: timestamps, symbol: SH600519, open: np.random.normal(1800, 50, 1000), high: lambda x: x[open] np.random.exponential(20, 1000), low: lambda x: x[open] - np.random.exponential(15, 1000), close: lambda x: x[open] np.random.normal(0, 10, 1000), volume: np.random.poisson(5000, 1000) }) df.to_csv(data/sh600519.csv, indexFalse, headerFalse, sep\t)注意sep\t是必须的框架默认按制表符分割。若用逗号启动时会报ArrayIndexOutOfBoundsException。3.2 编写策略Java 实现双均线交叉买卖逻辑在src/main/java/com/example/strategy/下新建DualMA.javapackage com.example.strategy; import com.quant.framework.java.Context; import com.quant.framework.java.Strategy; import com.quant.framework.java.event.TickEvent; import com.quant.framework.java.event.OrderFillEvent; import com.quant.framework.java.model.OrderRequest; import com.quant.framework.java.model.OrderType; import java.math.BigDecimal; import java.util.ArrayDeque; import java.util.Deque; public class DualMA extends Strategy { private final DequeBigDecimal closes new ArrayDeque(200); // 存最近200个收盘价 private final int shortPeriod 10; private final int longPeriod 30; private boolean longPosition false; public DualMA(Context context) { super(context); } Override public void onStart() { System.out.println(DualMA strategy started with short shortPeriod , long longPeriod); } Override public void onTick(TickEvent event) { // 1. 维护价格队列 closes.addLast(event.getClose()); if (closes.size() longPeriod) { closes.removeFirst(); } // 2. 计算均线简化版实际应加权 if (closes.size() longPeriod) return; BigDecimal shortMA calculateMA(closes, shortPeriod); BigDecimal longMA calculateMA(closes, longPeriod); // 3. 金叉短均线上穿长均线 if (!longPosition shortMA.compareTo(longMA) 0) { OrderRequest buy OrderRequest.builder() .symbol(event.getSymbol()) .price(event.getClose()) // 市价单 .quantity(new BigDecimal(100)) .type(OrderType.MARKET) .build(); context.getExecutionService().placeOrder(buy); longPosition true; } // 4. 死叉短均线下穿长均线 if (longPosition shortMA.compareTo(longMA) 0) { OrderRequest sell OrderRequest.builder() .symbol(event.getSymbol()) .price(event.getClose()) .quantity(new BigDecimal(100)) .type(OrderType.MARKET) .build(); context.getExecutionService().placeOrder(sell); longPosition false; } } private BigDecimal calculateMA(DequeBigDecimal queue, int period) { BigDecimal sum BigDecimal.ZERO; int count 0; for (BigDecimal p : queue) { if (count queue.size() - period) { sum sum.add(p); } count; } return sum.divide(new BigDecimal(period), 2, BigDecimal.ROUND_HALF_UP); } Override public void onOrderFilled(OrderFillEvent event) { System.out.printf(Order filled: %s %s %s %.2f\n, event.getSymbol(), event.getSide(), event.getOrderType(), event.getFillPrice()); } }关键点说明context.getExecutionService().placeOrder()返回FutureOrderResponse但此处我们不get()因模拟网关会立即返回填充结果OrderType.MARKET是唯一支持的市价单类型限价单需额外实现LimitOrderGatewaycalculateMA用BigDecimal避免浮点误差ROUND_HALF_UP是金融计算标准舍入方式。3.3 启动回测用 Maven Profile 控制运行模式框架提供backtestprofile无需改代码# 编译全部模块含策略 mvn clean compile # 运行回测指定数据源和策略类 mvn exec:java \ -Dexec.mainClasscom.quant.framework.Main \ -Dexec.args--modebacktest --configsrc/main/resources/backtest-config.json --strategycom.example.strategy.DualMA \ -Pbacktestbacktest-config.json内容如下必须存在{ data-source: { type: file, path: data/sh600519.csv, format: tsv }, execution-gateway: { type: simulation, initial-capital: 1000000.0, commission-rate: 0.0003 } }成功标志控制台输出Backtest completed. Total PnL: 23456.78生成backtest-result/目录含pnl_curve.png净值曲线、trade_log.csv逐笔成交记录若卡在Loading strategy...检查DualMA.class是否在target/classes/下且包路径与--strategy参数一致。4. 避坑指南五个血泪经验换来的高频故障排查清单这框架文档不多但错误信息足够精准。以下是我在三套不同硬件Mac M1、Ubuntu 20.04、Windows WSL2上踩出的真实坑按发生频率排序4.1 现象启动时报java.lang.NoClassDefFoundError: kotlin/coroutines/Continuation原因Maven 依赖未正确传递 Kotlin 运行时。框架用 Kotlin 1.8.22 编译但你的 JDK 17 环境未引入kotlin-stdlib-jdk8。解决在pom.xml的dependencies中显式添加dependency groupIdorg.jetbrains.kotlin/groupId artifactIdkotlin-stdlib-jdk8/artifactId version1.8.22/version /dependency注意版本号必须与框架pom.xml中kotlin.version一致不可用1.9.x。4.2 现象回测日志显示No ticks received for symbol SH600519但 CSV 文件明明有数据原因CSV 第一列timestamp不是毫秒整数而是字符串如2023-01-01 09:30:00或秒级时间戳。框架解析失败后静默跳过整行。解决用head -n 5 data/sh600519.csv检查第一列。若为字符串用awk转换awk -F\t NR1 {cmddate -d \$1\ %s%3N 2/dev/null; cmd | getline ts; close(cmd); if(ts~/^[0-9]$/) print ts,$2,$3,$4,$5,$6,$7; else print 0,$2,$3,$4,$5,$6,$7} data/sh600519.csv data/sh600519_fixed.csv4.3 现象策略onTick()被调用但placeOrder()后无任何成交日志trade_log.csv为空原因SimulationGateway默认只处理MARKET订单且要求symbol与数据源中的symbol完全一致大小写、无空格。若 CSV 里是sh600519而策略下单SH600519网关找不到匹配的标的直接丢弃订单。解决在策略下单前加校验if (!event.getSymbol().equals(SH600519)) { System.err.println(Symbol mismatch: expected SH600519, got event.getSymbol()); return; }4.4 现象mvn exec:java报错Could not find goal java in plugin org.codehaus.mojo:exec-maven-plugin原因exec-maven-plugin版本太旧 3.0.0不支持 JDK 17 的模块化特性。解决升级插件版本在pom.xml的pluginManagement中plugin groupIdorg.codehaus.mojo/groupId artifactIdexec-maven-plugin/artifactId version3.1.0/version /plugin4.5 现象Windows 上运行mvn exec:java时--config路径中的反斜杠\导致FileNotFoundException原因Maven 参数解析器将\当作转义字符src\main\resources\backtest-config.json被解析为srcmainesourcesbacktest-config.json。解决统一用正斜杠/即使在 Windowsmvn exec:java -Dexec.args--configsrc/main/resources/backtest-config.json ...这是 JVM 规范行为非框架 Bug。所有 Java 程序都应如此处理路径。5. 进阶技巧用 JMX 暴露实时指标把策略变成可观测服务框架内置 JMX 支持但默认关闭。开启后你能在 VisualVM 或 JConsole 中看到策略的实时状态比如当前持仓、未成交委托、最近 10 笔成交详情——这比日志grep高效十倍。这才是生产级策略服务该有的样子。5.1 启用 JMX两行配置搞定在src/main/resources/application.properties中添加# JMX 配置 jmx.enabledtrue jmx.port9999 jmx.authenticatefalse jmx.sslfalse然后启动时加 JVM 参数mvn exec:java \ -Dexec.mainClasscom.quant.framework.Main \ -Dexec.args--modesimulation --configsrc/main/resources/gateway.yml \ -Dcom.sun.management.jmxremote \ -Dcom.sun.management.jmxremote.port9999 \ -Dcom.sun.management.jmxremote.authenticatefalse \ -Dcom.sun.management.jmxremote.sslfalse \ -Psimulation5.2 自定义 MBean让策略状态可被远程读取在策略类中实现DynamicMBean接口框架已提供基类AbstractStrategyMBean// 新建文件 com.example.strategy.DualMAMBean.java public class DualMAMBean extends AbstractStrategyMBean { private final DualMA strategy; public DualMAMBean(DualMA strategy) { this.strategy strategy; } Override public Object getAttribute(String attribute) throws AttributeNotFoundException { switch (attribute) { case CurrentPosition: return strategy.getLongPosition() ? LONG : FLAT; case UnfilledOrders: return strategy.getUnfilledOrders().size(); case LastFillPrice: return strategy.getLastFillPrice(); default: throw new AttributeNotFoundException(Unknown attribute: attribute); } } Override public MBeanInfo getMBeanInfo() { return new MBeanInfo( com.example.strategy:typeDualMA, Dual Moving Average Strategy Monitor, new MBeanAttributeInfo[]{ new MBeanAttributeInfo(CurrentPosition, java.lang.String, Current position state, true, false, false), new MBeanAttributeInfo(UnfilledOrders, int, Number of unfilled orders, true, false, false), new MBeanAttributeInfo(LastFillPrice, java.math.BigDecimal, Last fill price, true, false, false) }, new MBeanConstructorInfo[0], new MBeanOperationInfo[0], new MBeanNotificationInfo[0] ); } }5.3 在策略中注册 MBean修改DualMA.java的onStart()方法Override public void onStart() { try { MBeanServer mbs ManagementFactory.getPlatformMBeanServer(); ObjectName name new ObjectName(com.example.strategy:typeDualMA); mbs.registerMBean(new DualMAMBean(this), name); System.out.println(DualMA MBean registered at: name); } catch (Exception e) { e.printStackTrace(); } }5.4 远程观测用 JConsole 查看实时指标启动 JConsoleJDK 自带选择“远程进程”输入localhost:9999连接后在MBeans标签页展开com.example.strategy→DualMA→Attributes实时刷新CurrentPosition和UnfilledOrders无需重启策略。属性名类型说明典型值CurrentPositionString当前持仓方向LONG/FLATUnfilledOrdersint挂单数量模拟网关中0,1,2LastFillPriceBigDecimal最近一笔成交价1823.45从那以后我每次写新策略都强制走一遍 JMX 注册流程——不是为了炫技而是因为当实盘出现“策略不动了”的诡异现象时打开 JConsole 看一眼CurrentPosition就能立刻区分是行情中断、还是策略逻辑卡死。这比翻三天日志快得多。希望帮到你。本文还有配套的精品资源点击获取