Harden main contract refresh
This commit is contained in:
parent
10c434013b
commit
48079cea72
@ -32,7 +32,7 @@ public class InstrumentDictionary extends BaseEntity {
|
||||
/** 价格放大倍数 */
|
||||
private Integer priceScale;
|
||||
|
||||
/** 最小变动价位 */
|
||||
/** 合约最小变动价位(不参与行情价格缩放) */
|
||||
private BigDecimal priceTick;
|
||||
|
||||
/** 是否当前主力合约:0-否,1-是 */
|
||||
|
||||
@ -27,8 +27,9 @@ public interface InstrumentDictionaryMapper extends BaseMapper<InstrumentDiction
|
||||
FROM t_instrument_dictionary
|
||||
WHERE LOWER(contract_code) = LOWER(#{contractCode})
|
||||
AND is_deleted = 0
|
||||
AND is_main = 1
|
||||
ORDER BY id
|
||||
""")
|
||||
List<InstrumentDictionary> selectActiveByContractCodeIgnoreCase(
|
||||
List<InstrumentDictionary> selectMainByContractCodeIgnoreCase(
|
||||
@Param("contractCode") String contractCode);
|
||||
}
|
||||
|
||||
@ -49,14 +49,6 @@ public class CnQuotationClient {
|
||||
return parseKChartItems(body);
|
||||
}
|
||||
|
||||
public CnQuotationModels.ContractDetail getContractDetail(String contractCode) {
|
||||
String body = get(urlBuilder("contract/detail")
|
||||
.addQueryParameter("contractCode", contractCode)
|
||||
.build());
|
||||
JSONObject data = parseData(body);
|
||||
return data == null ? null : data.toJavaObject(CnQuotationModels.ContractDetail.class);
|
||||
}
|
||||
|
||||
public List<CnQuotationModels.KChartItem> getKChartByDate(String excode, String code, String period,
|
||||
long date, String direction) {
|
||||
Integer type = typeOf(period);
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
package com.yangwale.backtestify.service.market.client;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@ -23,12 +22,6 @@ public class CnQuotationModels {
|
||||
Integer isPrincipal) {
|
||||
}
|
||||
|
||||
public record ContractDetail(String excode,
|
||||
String contractCode,
|
||||
String productId,
|
||||
BigDecimal priceTick) {
|
||||
}
|
||||
|
||||
public record KChartResult(List<KChartItem> chats) {
|
||||
}
|
||||
|
||||
|
||||
@ -19,12 +19,11 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
@ -47,6 +46,7 @@ public class MarketDataSyncService {
|
||||
private final MarketDataSyncLogMapper syncLogMapper;
|
||||
private final KLineTableResolver tableResolver;
|
||||
private final IndicatorCalculationService indicatorCalculationService;
|
||||
private final TransactionTemplate transactionTemplate;
|
||||
|
||||
@Scheduled(cron = "${market-data.sync.cron:0 0 6 * * ?}", zone = "${market-data.sync.zone:Asia/Shanghai}")
|
||||
public void syncYesterdayMainContracts() {
|
||||
@ -78,7 +78,7 @@ public class MarketDataSyncService {
|
||||
|
||||
public void repairContractFrom(String contractCode, long startTimestamp) {
|
||||
List<InstrumentDictionary> matches =
|
||||
instrumentDictionaryMapper.selectActiveByContractCodeIgnoreCase(contractCode);
|
||||
instrumentDictionaryMapper.selectMainByContractCodeIgnoreCase(contractCode);
|
||||
InstrumentDictionary instrument = requireSingleContract(matches, contractCode);
|
||||
ZoneId zoneId = properties.getSync().zoneId();
|
||||
LocalDateTime now = LocalDateTime.now(zoneId);
|
||||
@ -109,25 +109,18 @@ public class MarketDataSyncService {
|
||||
int count = 0;
|
||||
try {
|
||||
List<CnQuotationModels.GoodsItem> goodsItems = cnQuotationClient.listMainContracts();
|
||||
List<ResolvedMainContract> resolvedContracts = new ArrayList<>();
|
||||
for (CnQuotationModels.GoodsItem item : goodsItems) {
|
||||
if (item.mainContractCode() == null || item.mainContractCode().isBlank()) {
|
||||
continue;
|
||||
List<CnQuotationModels.GoodsItem> mainContracts = goodsItems.stream()
|
||||
.filter(item -> item.mainContractCode() != null && !item.mainContractCode().isBlank())
|
||||
.toList();
|
||||
transactionTemplate.executeWithoutResult(status -> {
|
||||
instrumentDictionaryMapper.update(null, new LambdaUpdateWrapper<InstrumentDictionary>()
|
||||
.set(InstrumentDictionary::getIsMain, 0)
|
||||
.eq(InstrumentDictionary::getIsDeleted, 0));
|
||||
for (CnQuotationModels.GoodsItem item : mainContracts) {
|
||||
upsertInstrument(item);
|
||||
}
|
||||
CnQuotationModels.ContractDetail detail =
|
||||
cnQuotationClient.getContractDetail(item.mainContractCode());
|
||||
if (detail == null || detail.priceTick() == null || detail.priceTick().signum() <= 0) {
|
||||
throw new IllegalStateException("行情接口未返回有效最小变动价位: " + item.mainContractCode());
|
||||
}
|
||||
resolvedContracts.add(new ResolvedMainContract(item, detail.priceTick()));
|
||||
}
|
||||
instrumentDictionaryMapper.update(null, new LambdaUpdateWrapper<InstrumentDictionary>()
|
||||
.set(InstrumentDictionary::getIsMain, 0)
|
||||
.eq(InstrumentDictionary::getIsDeleted, 0));
|
||||
for (ResolvedMainContract resolved : resolvedContracts) {
|
||||
upsertInstrument(resolved.item(), resolved.priceTick());
|
||||
count++;
|
||||
}
|
||||
});
|
||||
count = mainContracts.size();
|
||||
saveLog("CONTRACT", null, null, LocalDate.now(), "SUCCESS", count, null, start);
|
||||
} catch (Exception e) {
|
||||
saveLog("CONTRACT", null, null, LocalDate.now(), "FAILED", count, e.getMessage(), start);
|
||||
@ -179,7 +172,7 @@ public class MarketDataSyncService {
|
||||
}
|
||||
}
|
||||
|
||||
private void upsertInstrument(CnQuotationModels.GoodsItem item, BigDecimal priceTick) {
|
||||
private void upsertInstrument(CnQuotationModels.GoodsItem item) {
|
||||
String contractCode = item.mainContractCode();
|
||||
List<InstrumentDictionary> matches =
|
||||
instrumentDictionaryMapper.selectByExchangeAndContractCodeIgnoreCase(
|
||||
@ -193,14 +186,12 @@ public class MarketDataSyncService {
|
||||
instrument.setSymbol(firstNonBlank(item.productId(), item.goodsCode(), productPrefix(contractCode)));
|
||||
instrument.setContractCode(contractCode);
|
||||
instrument.setPriceScale(DEFAULT_PRICE_SCALE);
|
||||
instrument.setPriceTick(priceTick);
|
||||
instrument.setIsMain(1);
|
||||
instrumentDictionaryMapper.insert(instrument);
|
||||
} else {
|
||||
existing.setExchangeId(item.excode());
|
||||
existing.setSymbol(firstNonBlank(item.productId(), item.goodsCode(), productPrefix(contractCode)));
|
||||
existing.setContractCode(contractCode);
|
||||
existing.setPriceTick(priceTick);
|
||||
existing.setIsMain(1);
|
||||
existing.setIsDeleted(0);
|
||||
instrumentDictionaryMapper.updateById(existing);
|
||||
@ -267,7 +258,4 @@ public class MarketDataSyncService {
|
||||
}
|
||||
return contractCode.replaceAll("\\d+$", "");
|
||||
}
|
||||
|
||||
private record ResolvedMainContract(CnQuotationModels.GoodsItem item, BigDecimal priceTick) {
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,8 +11,7 @@ USE backtestify;
|
||||
-- ----------------------------
|
||||
-- Table structure for bt_strategy_config
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `bt_strategy_config`;
|
||||
CREATE TABLE `bt_strategy_config` (
|
||||
CREATE TABLE IF NOT EXISTS `bt_strategy_config` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`user_id` bigint NOT NULL COMMENT '用户ID',
|
||||
`contract_code` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '合约代码',
|
||||
@ -40,8 +39,7 @@ CREATE TABLE `bt_strategy_config` (
|
||||
-- ----------------------------
|
||||
-- Table structure for bt_strategy_result
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `bt_strategy_result`;
|
||||
CREATE TABLE `bt_strategy_result` (
|
||||
CREATE TABLE IF NOT EXISTS `bt_strategy_result` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`strategy_id` bigint NOT NULL COMMENT '关联策略ID',
|
||||
`initial_capital` decimal(18, 2) NOT NULL COMMENT '初始资金',
|
||||
@ -66,8 +64,7 @@ CREATE TABLE `bt_strategy_result` (
|
||||
-- ----------------------------
|
||||
-- Table structure for bt_trade_detail
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `bt_trade_detail`;
|
||||
CREATE TABLE `bt_trade_detail` (
|
||||
CREATE TABLE IF NOT EXISTS `bt_trade_detail` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`strategy_id` bigint NOT NULL COMMENT '关联策略ID',
|
||||
`action` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '操作: BUY_OPEN/SELL_CLOSE/SELL_OPEN/BUY_CLOSE',
|
||||
@ -86,8 +83,7 @@ CREATE TABLE `bt_trade_detail` (
|
||||
-- ----------------------------
|
||||
-- Table structure for bt_user_signal
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `bt_user_signal`;
|
||||
CREATE TABLE `bt_user_signal` (
|
||||
CREATE TABLE IF NOT EXISTS `bt_user_signal` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`user_id` bigint NOT NULL COMMENT '用户ID',
|
||||
`strategy_id` bigint NOT NULL COMMENT '策略ID',
|
||||
@ -105,8 +101,7 @@ CREATE TABLE `bt_user_signal` (
|
||||
-- ----------------------------
|
||||
-- Table structure for t_indicator_15m
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `t_indicator_15m`;
|
||||
CREATE TABLE `t_indicator_15m` (
|
||||
CREATE TABLE IF NOT EXISTS `t_indicator_15m` (
|
||||
`instrument_id` smallint UNSIGNED NOT NULL COMMENT '合约字典ID',
|
||||
`k_time` int UNSIGNED NOT NULL COMMENT 'Unix时间戳 (秒级,对应K线起始点)',
|
||||
`ma5` decimal(18, 6) NULL DEFAULT NULL COMMENT 'MA5',
|
||||
@ -134,8 +129,7 @@ CREATE TABLE `t_indicator_15m` (
|
||||
-- ----------------------------
|
||||
-- Table structure for t_indicator_1d
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `t_indicator_1d`;
|
||||
CREATE TABLE `t_indicator_1d` (
|
||||
CREATE TABLE IF NOT EXISTS `t_indicator_1d` (
|
||||
`instrument_id` smallint UNSIGNED NOT NULL COMMENT '合约字典ID',
|
||||
`k_time` int UNSIGNED NOT NULL COMMENT 'Unix时间戳 (秒级,对应K线起始点)',
|
||||
`ma5` decimal(18, 6) NULL DEFAULT NULL COMMENT 'MA5',
|
||||
@ -163,8 +157,7 @@ CREATE TABLE `t_indicator_1d` (
|
||||
-- ----------------------------
|
||||
-- Table structure for t_indicator_1h
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `t_indicator_1h`;
|
||||
CREATE TABLE `t_indicator_1h` (
|
||||
CREATE TABLE IF NOT EXISTS `t_indicator_1h` (
|
||||
`instrument_id` smallint UNSIGNED NOT NULL COMMENT '合约字典ID',
|
||||
`k_time` int UNSIGNED NOT NULL COMMENT 'Unix时间戳 (秒级,对应K线起始点)',
|
||||
`ma5` decimal(18, 6) NULL DEFAULT NULL COMMENT 'MA5',
|
||||
@ -192,8 +185,7 @@ CREATE TABLE `t_indicator_1h` (
|
||||
-- ----------------------------
|
||||
-- Table structure for t_indicator_1m
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `t_indicator_1m`;
|
||||
CREATE TABLE `t_indicator_1m` (
|
||||
CREATE TABLE IF NOT EXISTS `t_indicator_1m` (
|
||||
`instrument_id` smallint UNSIGNED NOT NULL COMMENT '合约字典ID',
|
||||
`k_time` int UNSIGNED NOT NULL COMMENT 'Unix时间戳 (秒级)',
|
||||
`ma5` decimal(18, 6) NULL DEFAULT NULL,
|
||||
@ -221,8 +213,7 @@ CREATE TABLE `t_indicator_1m` (
|
||||
-- ----------------------------
|
||||
-- Table structure for t_indicator_1mo
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `t_indicator_1mo`;
|
||||
CREATE TABLE `t_indicator_1mo` (
|
||||
CREATE TABLE IF NOT EXISTS `t_indicator_1mo` (
|
||||
`instrument_id` smallint UNSIGNED NOT NULL COMMENT '合约字典ID',
|
||||
`k_time` int UNSIGNED NOT NULL COMMENT 'Unix时间戳 (秒级,对应K线起始点)',
|
||||
`ma5` decimal(18, 6) NULL DEFAULT NULL COMMENT 'MA5',
|
||||
@ -250,8 +241,7 @@ CREATE TABLE `t_indicator_1mo` (
|
||||
-- ----------------------------
|
||||
-- Table structure for t_indicator_1w
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `t_indicator_1w`;
|
||||
CREATE TABLE `t_indicator_1w` (
|
||||
CREATE TABLE IF NOT EXISTS `t_indicator_1w` (
|
||||
`instrument_id` smallint UNSIGNED NOT NULL COMMENT '合约字典ID',
|
||||
`k_time` int UNSIGNED NOT NULL COMMENT 'Unix时间戳 (秒级,对应K线起始点)',
|
||||
`ma5` decimal(18, 6) NULL DEFAULT NULL COMMENT 'MA5',
|
||||
@ -279,8 +269,7 @@ CREATE TABLE `t_indicator_1w` (
|
||||
-- ----------------------------
|
||||
-- Table structure for t_indicator_30m
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `t_indicator_30m`;
|
||||
CREATE TABLE `t_indicator_30m` (
|
||||
CREATE TABLE IF NOT EXISTS `t_indicator_30m` (
|
||||
`instrument_id` smallint UNSIGNED NOT NULL COMMENT '合约字典ID',
|
||||
`k_time` int UNSIGNED NOT NULL COMMENT 'Unix时间戳 (秒级,对应K线起始点)',
|
||||
`ma5` decimal(18, 6) NULL DEFAULT NULL COMMENT 'MA5',
|
||||
@ -308,8 +297,7 @@ CREATE TABLE `t_indicator_30m` (
|
||||
-- ----------------------------
|
||||
-- Table structure for t_indicator_3m
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `t_indicator_3m`;
|
||||
CREATE TABLE `t_indicator_3m` (
|
||||
CREATE TABLE IF NOT EXISTS `t_indicator_3m` (
|
||||
`instrument_id` smallint UNSIGNED NOT NULL COMMENT '合约字典ID',
|
||||
`k_time` int UNSIGNED NOT NULL COMMENT 'Unix时间戳 (秒级,对应K线起始点)',
|
||||
`ma5` decimal(18, 6) NULL DEFAULT NULL COMMENT 'MA5',
|
||||
@ -337,8 +325,7 @@ CREATE TABLE `t_indicator_3m` (
|
||||
-- ----------------------------
|
||||
-- Table structure for t_indicator_4h
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `t_indicator_4h`;
|
||||
CREATE TABLE `t_indicator_4h` (
|
||||
CREATE TABLE IF NOT EXISTS `t_indicator_4h` (
|
||||
`instrument_id` smallint UNSIGNED NOT NULL COMMENT '合约字典ID',
|
||||
`k_time` int UNSIGNED NOT NULL COMMENT 'Unix时间戳 (秒级,对应K线起始点)',
|
||||
`ma5` decimal(18, 6) NULL DEFAULT NULL COMMENT 'MA5',
|
||||
@ -366,8 +353,7 @@ CREATE TABLE `t_indicator_4h` (
|
||||
-- ----------------------------
|
||||
-- Table structure for t_indicator_5m
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `t_indicator_5m`;
|
||||
CREATE TABLE `t_indicator_5m` (
|
||||
CREATE TABLE IF NOT EXISTS `t_indicator_5m` (
|
||||
`instrument_id` smallint UNSIGNED NOT NULL COMMENT '合约字典ID',
|
||||
`k_time` int UNSIGNED NOT NULL COMMENT 'Unix时间戳 (秒级,对应K线起始点)',
|
||||
`ma5` decimal(18, 6) NULL DEFAULT NULL COMMENT 'MA5',
|
||||
@ -395,20 +381,20 @@ CREATE TABLE `t_indicator_5m` (
|
||||
-- ----------------------------
|
||||
-- Table structure for t_instrument_dictionary
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `t_instrument_dictionary`;
|
||||
CREATE TABLE `t_instrument_dictionary` (
|
||||
CREATE TABLE IF NOT EXISTS `t_instrument_dictionary` (
|
||||
`id` smallint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '合约自增ID',
|
||||
`exchange_id` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
|
||||
`symbol` varchar(10) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
|
||||
`contract_code` varchar(20) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
|
||||
`contract_code_normalized` varchar(20) CHARACTER SET ascii COLLATE ascii_general_ci GENERATED ALWAYS AS (lower(`contract_code`)) STORED,
|
||||
`price_scale` int UNSIGNED NOT NULL DEFAULT 1000 COMMENT '价格放大倍数(暂统一使用1000)',
|
||||
`price_tick` decimal(18, 6) NOT NULL COMMENT '最小变动价位;历史源tb_quotations_futures_contract.price_tick,增量源contract/detail.priceTick',
|
||||
`price_tick` decimal(18, 6) NOT NULL DEFAULT 1 COMMENT '合约最小变动价位,不参与行情价格缩放',
|
||||
`is_main` tinyint NOT NULL DEFAULT 0 COMMENT '是否当前主力合约: 0-否, 1-是',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
`is_deleted` tinyint NOT NULL DEFAULT 0 COMMENT '逻辑删除: 0-未删除, 1-已删除',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `uk_exchange_contract`(`exchange_id` ASC, `contract_code` ASC) USING BTREE,
|
||||
UNIQUE INDEX `uk_exchange_contract`(`exchange_id` ASC, `contract_code_normalized` ASC) USING BTREE,
|
||||
INDEX `idx_symbol_main`(`symbol` ASC, `is_main` ASC) USING BTREE,
|
||||
INDEX `idx_contract_code`(`contract_code` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 2048 CHARACTER SET = ascii COLLATE = ascii_general_ci COMMENT = '期货合约字典表' ROW_FORMAT = Dynamic;
|
||||
@ -416,8 +402,7 @@ CREATE TABLE `t_instrument_dictionary` (
|
||||
-- ----------------------------
|
||||
-- Table structure for t_kline_15m
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `t_kline_15m`;
|
||||
CREATE TABLE `t_kline_15m` (
|
||||
CREATE TABLE IF NOT EXISTS `t_kline_15m` (
|
||||
`instrument_id` smallint UNSIGNED NOT NULL COMMENT '合约字典ID',
|
||||
`k_time` int UNSIGNED NOT NULL COMMENT 'Unix时间戳 (秒级,每根K线起始点)',
|
||||
`open` int NOT NULL COMMENT '开盘价 (实际价格 * price_scale)',
|
||||
@ -434,8 +419,7 @@ CREATE TABLE `t_kline_15m` (
|
||||
-- ----------------------------
|
||||
-- Table structure for t_kline_1d
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `t_kline_1d`;
|
||||
CREATE TABLE `t_kline_1d` (
|
||||
CREATE TABLE IF NOT EXISTS `t_kline_1d` (
|
||||
`instrument_id` smallint UNSIGNED NOT NULL COMMENT '合约字典ID',
|
||||
`k_time` int UNSIGNED NOT NULL COMMENT 'Unix时间戳 (秒级,每根K线起始点)',
|
||||
`open` int NOT NULL COMMENT '开盘价 (实际价格 * price_scale)',
|
||||
@ -453,8 +437,7 @@ CREATE TABLE `t_kline_1d` (
|
||||
-- ----------------------------
|
||||
-- Table structure for t_kline_1h
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `t_kline_1h`;
|
||||
CREATE TABLE `t_kline_1h` (
|
||||
CREATE TABLE IF NOT EXISTS `t_kline_1h` (
|
||||
`instrument_id` smallint UNSIGNED NOT NULL COMMENT '合约字典ID',
|
||||
`k_time` int UNSIGNED NOT NULL COMMENT 'Unix时间戳 (秒级,每根K线起始点)',
|
||||
`open` int NOT NULL COMMENT '开盘价 (实际价格 * price_scale)',
|
||||
@ -471,8 +454,7 @@ CREATE TABLE `t_kline_1h` (
|
||||
-- ----------------------------
|
||||
-- Table structure for t_kline_1m
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `t_kline_1m`;
|
||||
CREATE TABLE `t_kline_1m` (
|
||||
CREATE TABLE IF NOT EXISTS `t_kline_1m` (
|
||||
`instrument_id` smallint UNSIGNED NOT NULL COMMENT '合约字典ID',
|
||||
`k_time` int UNSIGNED NOT NULL COMMENT 'Unix时间戳 (秒级,每根K线起始点)',
|
||||
`open` int NOT NULL COMMENT '开盘价 (实际价格 * price_scale)',
|
||||
@ -489,8 +471,7 @@ CREATE TABLE `t_kline_1m` (
|
||||
-- ----------------------------
|
||||
-- Table structure for t_kline_1mo
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `t_kline_1mo`;
|
||||
CREATE TABLE `t_kline_1mo` (
|
||||
CREATE TABLE IF NOT EXISTS `t_kline_1mo` (
|
||||
`instrument_id` smallint UNSIGNED NOT NULL COMMENT '合约字典ID',
|
||||
`k_time` int UNSIGNED NOT NULL COMMENT 'Unix时间戳 (秒级,每根K线起始点)',
|
||||
`open` int NOT NULL COMMENT '开盘价 (实际价格 * price_scale)',
|
||||
@ -507,8 +488,7 @@ CREATE TABLE `t_kline_1mo` (
|
||||
-- ----------------------------
|
||||
-- Table structure for t_kline_1w
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `t_kline_1w`;
|
||||
CREATE TABLE `t_kline_1w` (
|
||||
CREATE TABLE IF NOT EXISTS `t_kline_1w` (
|
||||
`instrument_id` smallint UNSIGNED NOT NULL COMMENT '合约字典ID',
|
||||
`k_time` int UNSIGNED NOT NULL COMMENT 'Unix时间戳 (秒级,每根K线起始点)',
|
||||
`open` int NOT NULL COMMENT '开盘价 (实际价格 * price_scale)',
|
||||
@ -525,8 +505,7 @@ CREATE TABLE `t_kline_1w` (
|
||||
-- ----------------------------
|
||||
-- Table structure for t_kline_30m
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `t_kline_30m`;
|
||||
CREATE TABLE `t_kline_30m` (
|
||||
CREATE TABLE IF NOT EXISTS `t_kline_30m` (
|
||||
`instrument_id` smallint UNSIGNED NOT NULL COMMENT '合约字典ID',
|
||||
`k_time` int UNSIGNED NOT NULL COMMENT 'Unix时间戳 (秒级,每根K线起始点)',
|
||||
`open` int NOT NULL COMMENT '开盘价 (实际价格 * price_scale)',
|
||||
@ -543,8 +522,7 @@ CREATE TABLE `t_kline_30m` (
|
||||
-- ----------------------------
|
||||
-- Table structure for t_kline_3m
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `t_kline_3m`;
|
||||
CREATE TABLE `t_kline_3m` (
|
||||
CREATE TABLE IF NOT EXISTS `t_kline_3m` (
|
||||
`instrument_id` smallint UNSIGNED NOT NULL COMMENT '合约字典ID',
|
||||
`k_time` int UNSIGNED NOT NULL COMMENT 'Unix时间戳 (秒级,每根K线起始点)',
|
||||
`open` int NOT NULL COMMENT '开盘价 (实际价格 * price_scale)',
|
||||
@ -561,8 +539,7 @@ CREATE TABLE `t_kline_3m` (
|
||||
-- ----------------------------
|
||||
-- Table structure for t_kline_4h
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `t_kline_4h`;
|
||||
CREATE TABLE `t_kline_4h` (
|
||||
CREATE TABLE IF NOT EXISTS `t_kline_4h` (
|
||||
`instrument_id` smallint UNSIGNED NOT NULL COMMENT '合约字典ID',
|
||||
`k_time` int UNSIGNED NOT NULL COMMENT 'Unix时间戳 (秒级,每根K线起始点)',
|
||||
`open` int NOT NULL COMMENT '开盘价 (实际价格 * price_scale)',
|
||||
@ -579,8 +556,7 @@ CREATE TABLE `t_kline_4h` (
|
||||
-- ----------------------------
|
||||
-- Table structure for t_kline_5m
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `t_kline_5m`;
|
||||
CREATE TABLE `t_kline_5m` (
|
||||
CREATE TABLE IF NOT EXISTS `t_kline_5m` (
|
||||
`instrument_id` smallint UNSIGNED NOT NULL COMMENT '合约字典ID',
|
||||
`k_time` int UNSIGNED NOT NULL COMMENT 'Unix时间戳 (秒级,每根K线起始点)',
|
||||
`open` int NOT NULL COMMENT '开盘价 (实际价格 * price_scale)',
|
||||
@ -597,8 +573,7 @@ CREATE TABLE `t_kline_5m` (
|
||||
-- ----------------------------
|
||||
-- Table structure for t_market_data_sync_log
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `t_market_data_sync_log`;
|
||||
CREATE TABLE `t_market_data_sync_log` (
|
||||
CREATE TABLE IF NOT EXISTS `t_market_data_sync_log` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`sync_type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '同步类型: CONTRACT/KLINE',
|
||||
`f_period` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT 'K线周期',
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
package com.yangwale.backtestify.service.market.sync;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
||||
import com.baomidou.mybatisplus.core.MybatisConfiguration;
|
||||
import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
||||
import com.yangwale.backtestify.config.MarketDataProperties;
|
||||
import com.yangwale.backtestify.entity.InstrumentDictionary;
|
||||
@ -14,16 +14,21 @@ import com.yangwale.backtestify.service.market.client.CnQuotationClient;
|
||||
import com.yangwale.backtestify.service.market.client.CnQuotationModels;
|
||||
import com.yangwale.backtestify.service.market.indicator.IndicatorCalculationService;
|
||||
import com.yangwale.backtestify.service.market.repository.KLineTableResolver;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.apache.ibatis.builder.MapperBuilderAssistant;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
import org.springframework.transaction.TransactionStatus;
|
||||
import org.springframework.transaction.support.SimpleTransactionStatus;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyList;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
@ -78,7 +83,8 @@ class MarketDataSyncServiceTest {
|
||||
instrument.setExchangeId("CZCE");
|
||||
instrument.setContractCode("AP610");
|
||||
instrument.setPriceScale(1000);
|
||||
when(instrumentMapper.selectActiveByContractCodeIgnoreCase("ap610"))
|
||||
instrument.setIsMain(1);
|
||||
when(instrumentMapper.selectMainByContractCodeIgnoreCase("ap610"))
|
||||
.thenReturn(List.of(instrument));
|
||||
CapturingRepairQuotationClient quotationClient = new CapturingRepairQuotationClient(properties);
|
||||
|
||||
@ -91,6 +97,21 @@ class MarketDataSyncServiceTest {
|
||||
verify(indicatorService).rebuild("1d", 7);
|
||||
}
|
||||
|
||||
@Test
|
||||
void repairRejectsContractThatIsNotCurrentMain() {
|
||||
MarketDataProperties properties = new MarketDataProperties();
|
||||
InstrumentDictionaryMapper instrumentMapper = mock(InstrumentDictionaryMapper.class);
|
||||
when(instrumentMapper.selectMainByContractCodeIgnoreCase("AP610"))
|
||||
.thenReturn(List.of());
|
||||
|
||||
MarketDataSyncService service = service(properties,
|
||||
new EmptyQuotationClient(properties), instrumentMapper, mock(KLineMapper.class),
|
||||
mock(MarketDataSyncLogMapper.class), mock(IndicatorCalculationService.class));
|
||||
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> service.repairContractFrom("AP610", 100L));
|
||||
}
|
||||
|
||||
@Test
|
||||
void refreshPreservesExistingPriceScale() {
|
||||
MarketDataProperties properties = new MarketDataProperties();
|
||||
@ -109,12 +130,11 @@ class MarketDataSyncServiceTest {
|
||||
new EmptyQuotationClient(properties), instrumentMapper, kLineMapper,
|
||||
syncLogMapper, indicatorService);
|
||||
|
||||
ReflectionTestUtils.invokeMethod(service, "upsertInstrument", goodsItem(), new BigDecimal("0.5"));
|
||||
ReflectionTestUtils.invokeMethod(service, "upsertInstrument", goodsItem());
|
||||
|
||||
ArgumentCaptor<InstrumentDictionary> updated = ArgumentCaptor.forClass(InstrumentDictionary.class);
|
||||
verify(instrumentMapper).updateById(updated.capture());
|
||||
assertEquals(500, updated.getValue().getPriceScale());
|
||||
assertEquals(new BigDecimal("0.5"), updated.getValue().getPriceTick());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -129,12 +149,11 @@ class MarketDataSyncServiceTest {
|
||||
new EmptyQuotationClient(properties), instrumentMapper, kLineMapper,
|
||||
syncLogMapper, indicatorService);
|
||||
|
||||
ReflectionTestUtils.invokeMethod(service, "upsertInstrument", goodsItem(), new BigDecimal("0.5"));
|
||||
ReflectionTestUtils.invokeMethod(service, "upsertInstrument", goodsItem());
|
||||
|
||||
ArgumentCaptor<InstrumentDictionary> inserted = ArgumentCaptor.forClass(InstrumentDictionary.class);
|
||||
verify(instrumentMapper).insert(inserted.capture());
|
||||
assertEquals(1000, inserted.getValue().getPriceScale());
|
||||
assertEquals(new BigDecimal("0.5"), inserted.getValue().getPriceTick());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -164,7 +183,8 @@ class MarketDataSyncServiceTest {
|
||||
kLineMapper,
|
||||
syncLogMapper,
|
||||
new KLineTableResolver(),
|
||||
indicatorService) {
|
||||
indicatorService,
|
||||
transactionTemplate()) {
|
||||
@Override
|
||||
public void refreshMainContracts() {
|
||||
// Contract refresh is outside this test's synchronization seam.
|
||||
@ -203,7 +223,7 @@ class MarketDataSyncServiceTest {
|
||||
when(kLineMapper.selectLatestTimestamp("t_kline_1d", 7)).thenReturn(100L);
|
||||
MarketDataSyncService service = new MarketDataSyncService(
|
||||
properties, quotationClient, instrumentMapper, kLineMapper, syncLogMapper,
|
||||
new KLineTableResolver(), indicatorService) {
|
||||
new KLineTableResolver(), indicatorService, transactionTemplate()) {
|
||||
@Override
|
||||
public void refreshMainContracts() {
|
||||
// Contract refresh is outside this test's synchronization seam.
|
||||
@ -234,7 +254,8 @@ class MarketDataSyncServiceTest {
|
||||
|
||||
MarketDataSyncService service = new MarketDataSyncService(
|
||||
properties, new RepairQuotationClient(properties), instrumentMapper,
|
||||
kLineMapper, syncLogMapper, new KLineTableResolver(), indicatorService) {
|
||||
kLineMapper, syncLogMapper, new KLineTableResolver(), indicatorService,
|
||||
transactionTemplate()) {
|
||||
@Override
|
||||
public void refreshMainContracts() {
|
||||
// Contract refresh is outside this test's synchronization seam.
|
||||
@ -264,7 +285,8 @@ class MarketDataSyncServiceTest {
|
||||
instrument.setExchangeId("SHFE");
|
||||
instrument.setContractCode("rb2610");
|
||||
instrument.setPriceScale(1000);
|
||||
when(instrumentMapper.selectActiveByContractCodeIgnoreCase("rb2610"))
|
||||
instrument.setIsMain(1);
|
||||
when(instrumentMapper.selectMainByContractCodeIgnoreCase("rb2610"))
|
||||
.thenReturn(List.of(instrument));
|
||||
|
||||
MarketDataSyncService service = service(properties, new RepairQuotationClient(properties),
|
||||
@ -285,7 +307,24 @@ class MarketDataSyncServiceTest {
|
||||
IndicatorCalculationService indicatorService) {
|
||||
return new MarketDataSyncService(
|
||||
properties, quotationClient, instrumentMapper, kLineMapper, syncLogMapper,
|
||||
new KLineTableResolver(), indicatorService);
|
||||
new KLineTableResolver(), indicatorService, transactionTemplate());
|
||||
}
|
||||
|
||||
private TransactionTemplate transactionTemplate() {
|
||||
return new TransactionTemplate(new PlatformTransactionManager() {
|
||||
@Override
|
||||
public TransactionStatus getTransaction(TransactionDefinition definition) {
|
||||
return new SimpleTransactionStatus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commit(TransactionStatus status) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rollback(TransactionStatus status) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private CnQuotationModels.GoodsItem goodsItem() {
|
||||
@ -368,12 +407,6 @@ class MarketDataSyncServiceTest {
|
||||
return List.of(new CnQuotationModels.GoodsItem(
|
||||
"CZCE", "AP", "AP", "苹果", "AP610", 1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CnQuotationModels.ContractDetail getContractDetail(String contractCode) {
|
||||
return new CnQuotationModels.ContractDetail(
|
||||
"CZCE", contractCode, "AP", new BigDecimal("1"));
|
||||
}
|
||||
}
|
||||
|
||||
private static final class CapturingRepairQuotationClient extends EmptyQuotationClient {
|
||||
|
||||
@ -10,7 +10,7 @@ CREATE TABLE IF NOT EXISTS t_instrument_dictionary (
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
is_deleted TINYINT NOT NULL DEFAULT 0,
|
||||
UNIQUE (contract_code)
|
||||
UNIQUE (exchange_id, contract_code)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS t_kline_1m (
|
||||
|
||||
Loading…
Reference in New Issue
Block a user