以下是一個基于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用于技術指標計算)和數據庫(存儲歷史數據)。