From b5e34b6d186a485386e4092e58db00a3da78b68e Mon Sep 17 00:00:00 2001 From: Lee <1633292@qq.com> Date: Fri, 24 Jul 2026 12:56:20 +0800 Subject: [PATCH] Add persisted technical indicator calculation --- .../backtestify/entity/KLineIndicator.java | 49 ++++ .../backtestify/entity/KLineRecord.java | 2 +- .../backtestify/mapper/IndicatorMapper.java | 77 +++++ .../backtestify/mapper/KLineMapper.java | 46 ++- .../market/MysqlMarketDataProvider.java | 2 +- .../IndicatorCalculationService.java | 21 ++ .../market/indicator/IndicatorCalculator.java | 19 ++ .../TechnicalIndicatorCalculator.java | 275 ++++++++++++++++++ .../impl/IndicatorCalculationServiceImpl.java | 92 ++++++ .../market/repository/KLineTableResolver.java | 9 +- .../market/sync/MarketDataSyncService.java | 5 +- .../IndicatorCalculationServiceImplTest.java | 125 ++++++++ .../IndicatorPersistenceIntegrationTest.java | 82 ++++++ .../TechnicalIndicatorCalculatorTest.java | 159 ++++++++++ .../repository/KLineTableResolverTest.java | 10 +- .../sync/MarketDataSyncServiceTest.java | 85 ++++++ src/test/resources/schema-test.sql | 64 +++- 17 files changed, 1095 insertions(+), 27 deletions(-) create mode 100644 src/main/java/com/yangwale/backtestify/entity/KLineIndicator.java create mode 100644 src/main/java/com/yangwale/backtestify/mapper/IndicatorMapper.java create mode 100644 src/main/java/com/yangwale/backtestify/service/market/indicator/IndicatorCalculationService.java create mode 100644 src/main/java/com/yangwale/backtestify/service/market/indicator/IndicatorCalculator.java create mode 100644 src/main/java/com/yangwale/backtestify/service/market/indicator/TechnicalIndicatorCalculator.java create mode 100644 src/main/java/com/yangwale/backtestify/service/market/indicator/impl/IndicatorCalculationServiceImpl.java create mode 100644 src/test/java/com/yangwale/backtestify/service/market/indicator/IndicatorCalculationServiceImplTest.java create mode 100644 src/test/java/com/yangwale/backtestify/service/market/indicator/IndicatorPersistenceIntegrationTest.java create mode 100644 src/test/java/com/yangwale/backtestify/service/market/indicator/TechnicalIndicatorCalculatorTest.java create mode 100644 src/test/java/com/yangwale/backtestify/service/market/sync/MarketDataSyncServiceTest.java diff --git a/src/main/java/com/yangwale/backtestify/entity/KLineIndicator.java b/src/main/java/com/yangwale/backtestify/entity/KLineIndicator.java new file mode 100644 index 0000000..041160f --- /dev/null +++ b/src/main/java/com/yangwale/backtestify/entity/KLineIndicator.java @@ -0,0 +1,49 @@ +package com.yangwale.backtestify.entity; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.math.BigDecimal; + +/** + * K线指标记录,映射各周期同结构的 t_indicator_* 表。 + */ +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class KLineIndicator { + + private Integer instrumentId; + private Long kTime; + + private BigDecimal ma5; + private BigDecimal ma10; + private BigDecimal ma20; + private BigDecimal ma60; + + private BigDecimal bollMb; + private BigDecimal bollUp; + private BigDecimal bollDn; + + private BigDecimal ema6; + private BigDecimal ema12; + private BigDecimal ema20; + + private BigDecimal macdDif; + private BigDecimal macdDea; + private BigDecimal macdBar; + + private BigDecimal rsi6; + private BigDecimal rsi12; + + private BigDecimal kdjK; + private BigDecimal kdjD; + private BigDecimal kdjJ; + + private Long createAt; +} diff --git a/src/main/java/com/yangwale/backtestify/entity/KLineRecord.java b/src/main/java/com/yangwale/backtestify/entity/KLineRecord.java index 52a81e3..88212ee 100644 --- a/src/main/java/com/yangwale/backtestify/entity/KLineRecord.java +++ b/src/main/java/com/yangwale/backtestify/entity/KLineRecord.java @@ -15,7 +15,7 @@ public class KLineRecord { private Integer instrumentId; /** Unix时间戳,秒级 */ - private Long timestamp; + private Long kTime; private Integer open; diff --git a/src/main/java/com/yangwale/backtestify/mapper/IndicatorMapper.java b/src/main/java/com/yangwale/backtestify/mapper/IndicatorMapper.java new file mode 100644 index 0000000..96383fa --- /dev/null +++ b/src/main/java/com/yangwale/backtestify/mapper/IndicatorMapper.java @@ -0,0 +1,77 @@ +package com.yangwale.backtestify.mapper; + +import com.yangwale.backtestify.entity.KLineIndicator; +import org.apache.ibatis.annotations.Insert; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +import java.util.List; + +@Mapper +public interface IndicatorMapper { + + @Select(""" + SELECT instrument_id, k_time, + ma5, ma10, ma20, ma60, + boll_mb, boll_up, boll_dn, + ema6, ema12, ema20, + macd_dif, macd_dea, macd_bar, + rsi6, rsi12, + kdj_k, kdj_d, kdj_j, + create_at + FROM ${tableName} + WHERE instrument_id = #{instrumentId} + ORDER BY k_time DESC + LIMIT 1 + """) + KLineIndicator selectLatest(@Param("tableName") String tableName, + @Param("instrumentId") Integer instrumentId); + + @Insert(""" + + """) + int upsertBatch(@Param("tableName") String tableName, + @Param("records") List records); +} diff --git a/src/main/java/com/yangwale/backtestify/mapper/KLineMapper.java b/src/main/java/com/yangwale/backtestify/mapper/KLineMapper.java index 71fd18f..cc941cd 100644 --- a/src/main/java/com/yangwale/backtestify/mapper/KLineMapper.java +++ b/src/main/java/com/yangwale/backtestify/mapper/KLineMapper.java @@ -12,25 +12,61 @@ import java.util.List; public interface KLineMapper { @Select(""" - SELECT instrument_id, timestamp, open, high, low, close, + SELECT instrument_id, k_time, open, high, low, close, volume, turnover, open_interest FROM ${tableName} WHERE instrument_id = #{instrumentId} - AND timestamp BETWEEN #{startTimestamp} AND #{endTimestamp} - ORDER BY timestamp ASC + AND k_time BETWEEN #{startTimestamp} AND #{endTimestamp} + ORDER BY k_time ASC """) List selectRange(@Param("tableName") String tableName, @Param("instrumentId") Integer instrumentId, @Param("startTimestamp") long startTimestamp, @Param("endTimestamp") long endTimestamp); + @Select(""" + SELECT instrument_id, k_time, open, high, low, close, + volume, turnover, open_interest + FROM ${tableName} + WHERE instrument_id = #{instrumentId} + ORDER BY k_time ASC + """) + List selectAll(@Param("tableName") String tableName, + @Param("instrumentId") Integer instrumentId); + + @Select(""" + SELECT instrument_id, k_time, open, high, low, close, + volume, turnover, open_interest + FROM ${tableName} + WHERE instrument_id = #{instrumentId} + AND k_time > #{afterTimestamp} + ORDER BY k_time ASC + """) + List selectAfter(@Param("tableName") String tableName, + @Param("instrumentId") Integer instrumentId, + @Param("afterTimestamp") long afterTimestamp); + + @Select(""" + SELECT instrument_id, k_time, open, high, low, close, + volume, turnover, open_interest + FROM ${tableName} + WHERE instrument_id = #{instrumentId} + AND k_time < #{beforeTimestamp} + ORDER BY k_time DESC + LIMIT #{limit} + """) + List selectBefore(@Param("tableName") String tableName, + @Param("instrumentId") Integer instrumentId, + @Param("beforeTimestamp") long beforeTimestamp, + @Param("limit") int limit); + @Insert("""