以下是一个基于Java实现的简单网格交易回测程序框架,以证券ETF(512880)为例。代码包含历史数据加载、网格策略逻辑和基础统计指标:
import java.io.BufferedReader;
import java.io.FileReader;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
// K线数据对象
class KData {
Date date;
double open;
double high;
double low;
double close;
long volume;
public KData(String[] data) throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
this.date = sdf.parse(data[0]);
this.open = Double.parseDouble(data[1]);
this.high = Double.parseDouble(data[2]);
this.low = Double.parseDouble(data[3]);
this.close = Double.parseDouble(data[4]);
this.volume = Long.parseLong(data[5]);
}
}
// 网格策略回测引擎
class GridStrategyBacktest {
List<KData> historicalData;
double initialCapital = 100000; // 初始资金10万元
double cash = initialCapital;
int position = 0; // 持仓数量
double gridStep = 0.05; // 网格间距5%
int gridLevels = 10; // 网格层数
double basePrice; // 基准价(网格中心)
List<String> trades = new ArrayList<>(); // 交易记录
double totalReturn = 0;
int winCount = 0;
int tradeCount = 0;
public GridStrategyBacktest(List<KData> data, double basePrice) {
this.historicalData = data;
this.basePrice = basePrice;
}
// 执行回测
public void runBacktest() {
for (KData bar : historicalData) {
double price = bar.close;
// 计算当前价格对应的网格层级
int targetLevel = (int) ((price - basePrice) / (basePrice * gridStep));
// 计算应该持有的仓位
int targetPosition = Math.min(gridLevels, Math.max(-gridLevels, targetLevel)) * 100;
// 执行交易
int volumeToTrade = targetPosition - position;
if (volumeToTrade != 0) {
executeTrade(bar.date, price, volumeToTrade);
}
}
// 计算最终收益
totalReturn = (cash + position * historicalData.get(historicalData.size()-1).close - initialCapital) / initialCapital;
}
private void executeTrade(Date date, double price, int volume) {
double cost = Math.abs(volume) * price * 1.0003; // 包含0.03%的交易费用
if (volume > 0) { // 买入
if (cost > cash) return;
cash -= cost;
position += volume;
trades.add(String.format("%tF - 买入 %d股 @ %.4f", date, volume, price));
} else { // 卖出
if (-volume > position) return;
cash += (-volume) * price * 0.9997; // 扣除卖出手续费
position += volume;
trades.add(String.format("%tF - 卖出 %d股 @ %.4f", date, -volume, price));
}
tradeCount++;
if ((volume > 0 && price < basePrice) ||
(volume < 0 && price > basePrice)) {
winCount++;
}
}
// 输出统计结果
public void printResults() {
System.out.println("===== 回测结果 =====");
System.out.printf("初始资金: %.2f\n", initialCapital);
System.out.printf("最终资产: %.2f\n", cash + position * historicalData.get(historicalData.size()-1).close);
System.out.printf("总收益率: %.2f%%\n", totalReturn * 100);
System.out.printf("交易次数: %d\n胜率: %.2f%%\n",
tradeCount, (double)winCount/tradeCount*100);
System.out.println("\n最后5笔交易记录:");
trades.subList(Math.max(0, trades.size()-5), trades.size())
.forEach(System.out::println);
}
}
public class ETFBacktester {
public static void main(String[] args) {
// 1. 加载历史数据(示例数据格式:日期,开盘价,最高价,最低价,收盘价,成交量)
List<KData> history = loadCSV("512880.csv"); // 需替换为实际数据文件路径
// 2. 设置网格参数
double basePrice = 1.0; // 根据历史中位数设定网格基准价
// 3. 运行回测
GridStrategyBacktest backtester = new GridStrategyBacktest(history, basePrice);
backtester.runBacktest();
// 4. 输出结果
backtester.printResults();
}
private static List<KData> loadCSV(String filename) {
List<KData> data = new ArrayList<>();
try (BufferedReader br = new BufferedReader(new FileReader(filename))) {
String line;
br.readLine(); // 跳过标题行
while ((line = br.readLine()) != null) {
String[] values = line.split(",");
data.add(new KData(values));
}
} catch (Exception e) {
e.printStackTrace();
}
return data;
}
}
代码说明及使用步骤:
-
数据准备:
- 需要准备CSV格式的历史数据文件(示例文件名:512880.csv)
- 数据格式(按日期排序):
日期,开盘价,最高价,最低价,收盘价,成交量 2023-01-03,1.052,1.068,1.050,1.065,12345678 ...
-
核心逻辑:
- 网格生成:以基准价(basePrice)为中心,上下各生成gridLevels层网格
- 交易触发:当价格突破网格层级时调整仓位
- 费用计算:包含0.03%的买卖双边交易费用
-
关键参数:
double initialCapital = 100000; // 初始资金 double gridStep = 0.05; // 5%网格间距 int gridLevels = 10; // 网格层数
-
输出指标:
- 总收益率
- 交易次数
- 胜率(盈利交易占比)
- 详细交易记录
扩展建议(可根据需求添加):
-
增强统计指标:
// 在GridStrategyBacktest类中添加: double maxDrawdown = 0; // 最大回撤 double peak = initialCapital; // 在每次交易后更新: double currentValue = cash + position * price; if (currentValue > peak) { peak = currentValue; } else { double dd = (peak - currentValue)/peak; if (dd > maxDrawdown) maxDrawdown = dd; }
-
参数优化功能:
public void optimizeParameters() { for (double step = 0.03; step < 0.08; step += 0.01) { for (int levels = 5; levels <= 15; levels += 2) { GridStrategyBacktest test = new GridStrategyBacktest(history, basePrice); test.gridStep = step; test.gridLevels = levels; test.runBacktest(); System.out.printf("步长:%.2f 层数:%d 收益:%.2f%%\n", step, levels, test.totalReturn*100); } } }
-
可视化输出:
// 使用JFreeChart库生成收益曲线图 XYSeries series = new XYSeries("净值曲线"); for (int i = 0; i < historicalData.size(); i++) { double value = cash + position * historicalData.get(i).close; series.add(i, value / initialCapital); }
注意事项:
- 需要复权价格数据(建议使用后复权)
- 实际交易需考虑最小交易单位(A股ETF为100股整数倍)
- 可增加止盈止损逻辑:
// 在executeTrade方法中添加: if (totalReturn > 0.3) { // 收益率超过30%时清仓 int sellVolume = position; executeTrade(date, price, -sellVolume); }
如需完整实现,建议结合第三方库(如Ta4j用于技术指标计算)和数据库(存储历史数据)。