From 8d0fb20a969c6e72def72d19eeac063321c7d3fb Mon Sep 17 00:00:00 2001 From: KevinHuangwl Date: Mon, 29 Jan 2024 20:20:02 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E5=8D=87=E5=8D=95=E5=85=83=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E8=A6=86=E7=9B=96=E7=8E=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gateway/mktdata/MarketCenter.java | 8 +- .../gateway/playback/PlaybackContext.java | 8 +- .../playback/utils/ContractDataLoader.java | 2 +- .../sim/trade/SimTradeGatewayLocal.java | 2 +- northstar-main/pom.xml | 8 - .../northstar/event/BroadcastHandler.java | 10 +- .../org/dromara/northstar/CtaModuleTest.java | 265 ++++++++++++++++++ .../dromara/northstar/PlaybackModuleTest.java | 190 +++++++++++++ pom.xml | 2 +- 9 files changed, 477 insertions(+), 18 deletions(-) create mode 100644 northstar-main/src/test/java/org/dromara/northstar/CtaModuleTest.java create mode 100644 northstar-main/src/test/java/org/dromara/northstar/PlaybackModuleTest.java diff --git a/northstar-api/src/main/java/org/dromara/northstar/gateway/mktdata/MarketCenter.java b/northstar-api/src/main/java/org/dromara/northstar/gateway/mktdata/MarketCenter.java index 3a6cb3341..7c335dbc0 100644 --- a/northstar-api/src/main/java/org/dromara/northstar/gateway/mktdata/MarketCenter.java +++ b/northstar-api/src/main/java/org/dromara/northstar/gateway/mktdata/MarketCenter.java @@ -14,6 +14,7 @@ import org.apache.commons.lang3.StringUtils; import org.dromara.northstar.common.TickDataAware; import org.dromara.northstar.common.constant.ChannelType; +import org.dromara.northstar.common.constant.Constants; import org.dromara.northstar.common.event.FastEventEngine; import org.dromara.northstar.common.exception.NoSuchElementException; import org.dromara.northstar.common.model.Identifier; @@ -241,7 +242,12 @@ public List getContracts(ChannelType channelType) { */ @Override public void onTick(Tick tick) { - tickMap.put(tick.contract(), tick); + // 确保tickMap中仅保留最新数据,可以避免同时接收历史行情与实时行情时的数据混乱 + tickMap.compute(tick.contract(), (k,v) -> Objects.isNull(v) || v.actionTimestamp() < tick.actionTimestamp() ? tick : v); + + if(tick.contract().unifiedSymbol().contains(Constants.INDEX_SUFFIX)) { + return; // 直接忽略指数TICK的后续处理 + } // 更新普通合约 IContract contract = getContract(tick.channelType(), tick.contract().unifiedSymbol()); diff --git a/northstar-gateway-playback/src/main/java/org/dromara/northstar/gateway/playback/PlaybackContext.java b/northstar-gateway-playback/src/main/java/org/dromara/northstar/gateway/playback/PlaybackContext.java index ed3d4f729..bcae2fcf6 100644 --- a/northstar-gateway-playback/src/main/java/org/dromara/northstar/gateway/playback/PlaybackContext.java +++ b/northstar-gateway-playback/src/main/java/org/dromara/northstar/gateway/playback/PlaybackContext.java @@ -23,6 +23,7 @@ import org.dromara.northstar.common.utils.DateTimeUtils; import org.dromara.northstar.data.IPlaybackRuntimeRepository; import org.dromara.northstar.gateway.IContractManager; +import org.dromara.northstar.gateway.IMarketCenter; import org.dromara.northstar.gateway.playback.utils.ContractDataLoader; import lombok.extern.slf4j.Slf4j; @@ -49,6 +50,8 @@ public class PlaybackContext implements IPlaybackContext{ private Set loaders; + private IMarketCenter mktCenter; + private Runnable stopCallback; private boolean isRunning; @@ -58,6 +61,7 @@ public PlaybackContext(GatewayDescription gd, LocalDateTime currentTimeState, FastEventEngine feEngine, IPlaybackRuntimeRepository rtRepo, IContractManager contractMgr) { this.rtRepo = rtRepo; this.feEngine = feEngine; + this.mktCenter = (IMarketCenter) contractMgr; this.playbackTimeState = currentTimeState; this.gatewayId = gd.getGatewayId(); this.settings = (PlaybackGatewaySettings) gd.getSettings(); @@ -169,7 +173,9 @@ public void run() { if(loader.hasMoreTick()) { Tick tick = loader.nextTick(false); if(tick.actionTimestamp() <= bar.actionTimestamp()) { - feEngine.emitEvent(NorthstarEventType.TICK, loader.nextTick(true)); + Tick t = loader.nextTick(true); + mktCenter.onTick(t); + feEngine.emitEvent(NorthstarEventType.TICK, t); return; } } diff --git a/northstar-gateway-playback/src/main/java/org/dromara/northstar/gateway/playback/utils/ContractDataLoader.java b/northstar-gateway-playback/src/main/java/org/dromara/northstar/gateway/playback/utils/ContractDataLoader.java index 2f71de1ed..0633f4bff 100644 --- a/northstar-gateway-playback/src/main/java/org/dromara/northstar/gateway/playback/utils/ContractDataLoader.java +++ b/northstar-gateway-playback/src/main/java/org/dromara/northstar/gateway/playback/utils/ContractDataLoader.java @@ -118,7 +118,7 @@ public void loadBarsAndTicks(LocalDate tradingDay){ .actionDay(bar.actionDay()) .actionTime(ldt.toLocalTime()) .actionTimestamp(tickEntry.timestamp()) - .type(TickType.MARKET_TICK) + .type(TickType.PLAYBACK_TICK) .lastPrice(tickEntry.price()) .askPrice(List.of(tickEntry.askPrice0())) // 仅模拟卖一价 .bidPrice(List.of(tickEntry.bidPrice0())) // 仅模拟买一价 diff --git a/northstar-gateway-sim/src/main/java/org/dromara/northstar/gateway/sim/trade/SimTradeGatewayLocal.java b/northstar-gateway-sim/src/main/java/org/dromara/northstar/gateway/sim/trade/SimTradeGatewayLocal.java index 13fb6c291..2f441e582 100644 --- a/northstar-gateway-sim/src/main/java/org/dromara/northstar/gateway/sim/trade/SimTradeGatewayLocal.java +++ b/northstar-gateway-sim/src/main/java/org/dromara/northstar/gateway/sim/trade/SimTradeGatewayLocal.java @@ -87,7 +87,7 @@ public void run() { account.getPositionManager().positionFields().forEach(pf -> feEngine.emitEvent(NorthstarEventType.POSITION, pf)); } - }, 5000, 2000); + }, 0, 1000); } @Override diff --git a/northstar-main/pom.xml b/northstar-main/pom.xml index e3841c8c6..abd326b6a 100644 --- a/northstar-main/pom.xml +++ b/northstar-main/pom.xml @@ -168,14 +168,6 @@ org.jacoco jacoco-maven-plugin - - - **/org/dromara/northstar/* - **/org/dromara/northstar/config/* - **/org/dromara/northstar/web/handler/**/* - **/org/dromara/northstar/data/**/* - - diff --git a/northstar-main/src/main/java/org/dromara/northstar/event/BroadcastHandler.java b/northstar-main/src/main/java/org/dromara/northstar/event/BroadcastHandler.java index 0014b80f8..882e40d76 100644 --- a/northstar-main/src/main/java/org/dromara/northstar/event/BroadcastHandler.java +++ b/northstar-main/src/main/java/org/dromara/northstar/event/BroadcastHandler.java @@ -74,17 +74,17 @@ public void emitEvent(NorthstarEvent event) throws SecurityException, IllegalArg } else if(event.getData() instanceof Account acc) { AccountField account = acc.toAccountField(); log.trace("账户信息分发: [{} {} {}]", account.getAccountId(), account.getGatewayId(), account.getBalance()); - socketServer.getBroadcastOperations().sendEvent(event.getEvent().toString(), Base64.encode(account.toByteArray())); + socketServer.getBroadcastOperations().sendEvent(NorthstarEventType.ACCOUNT.toString(), Base64.encode(account.toByteArray())); } else if(event.getData() instanceof Position pos) { PositionField position = pos.toPositionField(); log.trace("持仓信息分发: [{} {} {}]", position.getAccountId(), position.getGatewayId(), position.getPositionId()); - socketServer.getBroadcastOperations().sendEvent(event.getEvent().toString(), Base64.encode(position.toByteArray())); + socketServer.getBroadcastOperations().sendEvent(NorthstarEventType.POSITION.toString(), Base64.encode(position.toByteArray())); } else if(event.getData() instanceof Order od) { - socketServer.getBroadcastOperations().sendEvent(event.getEvent().toString(), Base64.encode(od.toOrderField().toByteArray())); + socketServer.getBroadcastOperations().sendEvent(NorthstarEventType.ORDER.toString(), Base64.encode(od.toOrderField().toByteArray())); } else if(event.getData() instanceof Trade tr) { - socketServer.getBroadcastOperations().sendEvent(event.getEvent().toString(), Base64.encode(tr.toTradeField().toByteArray())); + socketServer.getBroadcastOperations().sendEvent(NorthstarEventType.TRADE.toString(), Base64.encode(tr.toTradeField().toByteArray())); } else if(event.getData() instanceof Notice note) { - socketServer.getBroadcastOperations().sendEvent(event.getEvent().toString(), Base64.encode(note.toNoticeField().toByteArray())); + socketServer.getBroadcastOperations().sendEvent(NorthstarEventType.NOTICE.toString(), Base64.encode(note.toNoticeField().toByteArray())); } } diff --git a/northstar-main/src/test/java/org/dromara/northstar/CtaModuleTest.java b/northstar-main/src/test/java/org/dromara/northstar/CtaModuleTest.java new file mode 100644 index 000000000..c3f47baa9 --- /dev/null +++ b/northstar-main/src/test/java/org/dromara/northstar/CtaModuleTest.java @@ -0,0 +1,265 @@ +package org.dromara.northstar; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.dromara.northstar.account.GatewayManager; +import org.dromara.northstar.common.constant.ChannelType; +import org.dromara.northstar.common.constant.ClosingPolicy; +import org.dromara.northstar.common.constant.GatewayUsage; +import org.dromara.northstar.common.constant.ModuleType; +import org.dromara.northstar.common.constant.ModuleUsage; +import org.dromara.northstar.common.event.FastEventEngine; +import org.dromara.northstar.common.event.NorthstarEventType; +import org.dromara.northstar.common.model.ComponentAndParamsPair; +import org.dromara.northstar.common.model.ComponentMetaInfo; +import org.dromara.northstar.common.model.ContractSimpleInfo; +import org.dromara.northstar.common.model.GatewayDescription; +import org.dromara.northstar.common.model.Identifier; +import org.dromara.northstar.common.model.ModuleAccountDescription; +import org.dromara.northstar.common.model.ModuleDealRecord; +import org.dromara.northstar.common.model.ModuleDescription; +import org.dromara.northstar.common.model.ModuleRuntimeDescription; +import org.dromara.northstar.common.model.ResultBean; +import org.dromara.northstar.common.model.core.Bar; +import org.dromara.northstar.common.model.core.Contract; +import org.dromara.northstar.common.model.core.Tick; +import org.dromara.northstar.common.utils.CommonUtils; +import org.dromara.northstar.gateway.IContractManager; +import org.dromara.northstar.gateway.IMarketCenter; +import org.dromara.northstar.gateway.sim.trade.SimTradeGateway; +import org.dromara.northstar.module.ModuleManager; +import org.dromara.northstar.strategy.IModule; +import org.dromara.northstar.strategy.example.BeginnerExampleStrategy; +import org.dromara.northstar.web.restful.GatewayManagementController; +import org.dromara.northstar.web.restful.LogController; +import org.dromara.northstar.web.restful.ModuleController; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.logging.LogLevel; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; + +import com.corundumstudio.socketio.BroadcastOperations; +import com.corundumstudio.socketio.SocketIOServer; + +import io.netty.util.internal.ThreadLocalRandom; +import lombok.extern.slf4j.Slf4j; + +/** + * 模组集成测试 + * @auth KevinHuangwl + */ +@Slf4j +@SpringBootTest(classes = NorthstarApplication.class, value="spring.profiles.active=unittest") +class CtaModuleTest { + + @Autowired + GatewayManagementController gatewayCtlr; + + @Autowired + ModuleController moduleCtlr; + + @MockBean + private SocketIOServer socketServer; + + @Autowired + IContractManager contractMgr; + + IMarketCenter mktCenter; + + @Autowired + FastEventEngine feEngine; + + @Autowired + LogController logCtlr; + + @Autowired + GatewayManager gatewayMgr; + + @Autowired + ModuleManager moduleMgr; + + List contracts = List.of(ContractSimpleInfo.builder() + .unifiedSymbol("sim9901@SHFE@FUTURES") + .value("sim9901@SHFE@FUTURES@SIM") + .channelType(ChannelType.SIM) + .build()); + + @BeforeEach + void prepare() throws Exception { + when(socketServer.getRoomOperations(anyString())).thenReturn(mock(BroadcastOperations.class)); + when(socketServer.getBroadcastOperations()).thenReturn(mock(BroadcastOperations.class)); + mktCenter = (IMarketCenter) contractMgr; + + // 创建一个SIM行情 + gatewayCtlr.create(GatewayDescription.builder() + .gatewayId("SIM") + .gatewayUsage(GatewayUsage.MARKET_DATA) + .channelType(ChannelType.SIM) + .autoConnect(false) + .subscribedContracts(contracts) + .settings(new Object()) + .build()); + + // 创建一个SIM账户 + gatewayCtlr.create(GatewayDescription.builder() + .gatewayId("模拟账户") + .gatewayUsage(GatewayUsage.TRADE) + .channelType(ChannelType.SIM) + .bindedMktGatewayId("SIM") + .autoConnect(true) + .settings(new Object()) + .build()); + + Thread.sleep(2000); + + gatewayCtlr.simMoneyIO("模拟账户", 1000000); + } + + @AfterEach + void cleanup() { + moduleCtlr.removeModule("CTA测试"); + moduleCtlr.removeModule("K线测试"); + gatewayCtlr.disconnect("模拟账户"); + gatewayCtlr.remove("模拟账户"); + gatewayCtlr.remove("SIM"); + } + + // SIM行情 + 简单策略(模拟盘) + @Test + void testCTAModule() throws Exception { + // 创建一个简单策略 + ComponentMetaInfo cmi = new ComponentMetaInfo(); + cmi.setName("示例-简单策略"); + cmi.setClassName(BeginnerExampleStrategy.class.getName()); + BeginnerExampleStrategy.InitParams params = new BeginnerExampleStrategy.InitParams(); + ComponentAndParamsPair strategySettings = ComponentAndParamsPair.builder() + .componentMeta(cmi) + .initParams(params.getMetaInfo().values().stream().toList()) + .build(); + ModuleAccountDescription mad = ModuleAccountDescription.builder() + .bindedContracts(contracts) + .accountGatewayId("模拟账户") + .build(); + moduleCtlr.createModule(ModuleDescription.builder() + .moduleName("CTA测试") + .initBalance(100000) + .closingPolicy(ClosingPolicy.FIRST_IN_FIRST_OUT) + .defaultVolume(1) + .moduleCacheDataSize(500) + .numOfMinPerBar(1) + .type(ModuleType.SPECULATION) + .usage(ModuleUsage.UAT) + .moduleAccountSettingsDescription(List.of(mad)) + .strategySetting(strategySettings) + .build()); + moduleCtlr.toggleModuleState("CTA测试"); + logCtlr.setModuleLogLevel("CTA测试", LogLevel.TRACE); + // 开始测试 + log.info("开始测试"); + SimTradeGateway simGateway = (SimTradeGateway) gatewayMgr.get(Identifier.of("模拟账户")); + IModule module = moduleMgr.get(Identifier.of("CTA测试")); + Contract c = contractMgr.getContract(ChannelType.SIM, "sim9901").contract(); + long t = System.currentTimeMillis(); + for(int i=0; i<4; i++) { + log.info("构造模拟TICK"); + Tick tick = Tick.builder() + .actionDay(CommonUtils.millsToLocalDateTime(t).toLocalDate()) + .actionTime(CommonUtils.millsToLocalDateTime(t).toLocalTime()) + .tradingDay(LocalDate.now()) + .actionTimestamp(t) + .lastPrice(5000) + .askPrice(List.of(5001D)) + .bidPrice(List.of(4999D)) + .askVolume(List.of(10000)) + .bidVolume(List.of(10000)) + .channelType(ChannelType.SIM) + .contract(c) + .gatewayId("SIM") + .build(); + mktCenter.onTick(tick); + module.getModuleContext().onTick(tick); + simGateway.onTick(tick); + t += TimeUnit.MINUTES.toMillis(2); + Thread.sleep(1500); + } + log.info("等待成交"); + + // 期望有正常成交 + ResultBean> dealRecordResult = moduleCtlr.getDealRecords("CTA测试"); + assertThat(dealRecordResult.getData()).isNotEmpty(); + } + + @Test + void testGetRuntime() throws Exception { + // 创建一个简单策略 + ComponentMetaInfo cmi = new ComponentMetaInfo(); + cmi.setName("示例-简单策略"); + cmi.setClassName(BeginnerExampleStrategy.class.getName()); + BeginnerExampleStrategy.InitParams params = new BeginnerExampleStrategy.InitParams(); + ComponentAndParamsPair strategySettings = ComponentAndParamsPair.builder() + .componentMeta(cmi) + .initParams(params.getMetaInfo().values().stream().toList()) + .build(); + ModuleAccountDescription mad = ModuleAccountDescription.builder() + .bindedContracts(contracts) + .accountGatewayId("模拟账户") + .build(); + moduleCtlr.createModule(ModuleDescription.builder() + .moduleName("K线测试") + .initBalance(100000) + .closingPolicy(ClosingPolicy.FIRST_IN_FIRST_OUT) + .defaultVolume(1) + .moduleCacheDataSize(500) + .numOfMinPerBar(1) + .type(ModuleType.SPECULATION) + .usage(ModuleUsage.PLAYBACK) + .moduleAccountSettingsDescription(List.of(mad)) + .strategySetting(strategySettings) + .build()); + logCtlr.setModuleLogLevel("K线测试", LogLevel.TRACE); + // 开始测试 + Contract c = contractMgr.getContract(ChannelType.SIM, "sim9901").contract(); + LocalTime time = LocalTime.now().withSecond(0).withNano(0); + double price = ThreadLocalRandom.current().nextDouble(5000); + for(int i=0; i<100; i++) { + double open = price; + double delta = ThreadLocalRandom.current().nextDouble(-50, 50); + double close = open + delta; + double high = open + ThreadLocalRandom.current().nextDouble(0, 50); + double low = open - ThreadLocalRandom.current().nextDouble(0, 50); + Bar bar = Bar.builder() + .actionDay(LocalDate.now()) + .actionTime(time) + .tradingDay(LocalDate.now()) + .actionTimestamp(CommonUtils.localDateTimeToMills(LocalDateTime.of(LocalDate.now(), time))) + .openPrice(open) + .closePrice(close) + .highPrice(high) + .lowPrice(low) + .channelType(ChannelType.SIM) + .contract(c) + .gatewayId("SIM") + .build(); + feEngine.emitEvent(NorthstarEventType.BAR, bar); + time = time.plusMinutes(1); + } + + Thread.sleep(1000); + + // 期望有K线数据 + ResultBean result = moduleCtlr.getModuleRealTimeInfo("K线测试"); + assertThat(result.getData().getDataMap().get("模拟合约9901")).isNotEmpty(); + } +} diff --git a/northstar-main/src/test/java/org/dromara/northstar/PlaybackModuleTest.java b/northstar-main/src/test/java/org/dromara/northstar/PlaybackModuleTest.java new file mode 100644 index 000000000..4f6f36078 --- /dev/null +++ b/northstar-main/src/test/java/org/dromara/northstar/PlaybackModuleTest.java @@ -0,0 +1,190 @@ +package org.dromara.northstar; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.time.LocalDate; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.dromara.northstar.account.GatewayManager; +import org.dromara.northstar.common.constant.ChannelType; +import org.dromara.northstar.common.constant.ClosingPolicy; +import org.dromara.northstar.common.constant.GatewayUsage; +import org.dromara.northstar.common.constant.ModuleType; +import org.dromara.northstar.common.constant.ModuleUsage; +import org.dromara.northstar.common.event.FastEventEngine; +import org.dromara.northstar.common.model.ComponentAndParamsPair; +import org.dromara.northstar.common.model.ComponentMetaInfo; +import org.dromara.northstar.common.model.ContractSimpleInfo; +import org.dromara.northstar.common.model.GatewayDescription; +import org.dromara.northstar.common.model.Identifier; +import org.dromara.northstar.common.model.ModuleAccountDescription; +import org.dromara.northstar.common.model.ModuleDealRecord; +import org.dromara.northstar.common.model.ModuleDescription; +import org.dromara.northstar.common.model.ResultBean; +import org.dromara.northstar.common.model.core.Contract; +import org.dromara.northstar.common.model.core.Tick; +import org.dromara.northstar.common.utils.CommonUtils; +import org.dromara.northstar.gateway.IContractManager; +import org.dromara.northstar.gateway.IMarketCenter; +import org.dromara.northstar.gateway.sim.trade.SimTradeGateway; +import org.dromara.northstar.module.ModuleManager; +import org.dromara.northstar.strategy.IModule; +import org.dromara.northstar.strategy.example.BeginnerExampleStrategy; +import org.dromara.northstar.web.restful.GatewayManagementController; +import org.dromara.northstar.web.restful.LogController; +import org.dromara.northstar.web.restful.ModuleController; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.logging.LogLevel; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; + +import com.corundumstudio.socketio.BroadcastOperations; +import com.corundumstudio.socketio.SocketIOServer; + +/** + * 模组集成测试 + * @auth KevinHuangwl + */ +@SpringBootTest(classes = NorthstarApplication.class, value="spring.profiles.active=unittest") +class PlaybackModuleTest { + + @Autowired + GatewayManagementController gatewayCtlr; + + @Autowired + ModuleController moduleCtlr; + + @MockBean + private SocketIOServer socketServer; + + @Autowired + IContractManager contractMgr; + + IMarketCenter mktCenter; + + @Autowired + FastEventEngine feEngine; + + @Autowired + LogController logCtlr; + + @Autowired + GatewayManager gatewayMgr; + + @Autowired + ModuleManager moduleMgr; + + List contracts = List.of(ContractSimpleInfo.builder() + .unifiedSymbol("sim9901@SHFE@FUTURES") + .value("sim9901@SHFE@FUTURES@SIM") + .channelType(ChannelType.SIM) + .build()); + + @BeforeEach + void prepare() throws Exception { + when(socketServer.getRoomOperations(anyString())).thenReturn(mock(BroadcastOperations.class)); + when(socketServer.getBroadcastOperations()).thenReturn(mock(BroadcastOperations.class)); + mktCenter = (IMarketCenter) contractMgr; + + // 创建一个SIM行情 + gatewayCtlr.create(GatewayDescription.builder() + .gatewayId("SIM") + .gatewayUsage(GatewayUsage.MARKET_DATA) + .channelType(ChannelType.SIM) + .autoConnect(false) + .subscribedContracts(contracts) + .settings(new Object()) + .build()); + + // 创建一个SIM账户 + gatewayCtlr.create(GatewayDescription.builder() + .gatewayId("模拟账户") + .gatewayUsage(GatewayUsage.TRADE) + .channelType(ChannelType.SIM) + .bindedMktGatewayId("SIM") + .autoConnect(true) + .settings(new Object()) + .build()); + + Thread.sleep(2000); + + gatewayCtlr.simMoneyIO("模拟账户", 1000000); + } + + @AfterEach + void cleanup() { + moduleCtlr.removeModule("回测测试"); + gatewayCtlr.disconnect("模拟账户"); + gatewayCtlr.remove("模拟账户"); + gatewayCtlr.remove("SIM"); + } + + // SIM行情 + 简单策略(回测盘) + @Test + void testPlaybackModule() throws Exception { + // 创建一个简单策略 + ComponentMetaInfo cmi = new ComponentMetaInfo(); + cmi.setName("示例-简单策略"); + cmi.setClassName(BeginnerExampleStrategy.class.getName()); + BeginnerExampleStrategy.InitParams params = new BeginnerExampleStrategy.InitParams(); + ComponentAndParamsPair strategySettings = ComponentAndParamsPair.builder() + .componentMeta(cmi) + .initParams(params.getMetaInfo().values().stream().toList()) + .build(); + ModuleAccountDescription mad = ModuleAccountDescription.builder() + .bindedContracts(contracts) + .accountGatewayId("模拟账户") + .build(); + moduleCtlr.createModule(ModuleDescription.builder() + .moduleName("回测测试") + .initBalance(100000) + .closingPolicy(ClosingPolicy.FIRST_IN_FIRST_OUT) + .defaultVolume(1) + .moduleCacheDataSize(500) + .numOfMinPerBar(1) + .type(ModuleType.SPECULATION) + .usage(ModuleUsage.PLAYBACK) + .moduleAccountSettingsDescription(List.of(mad)) + .strategySetting(strategySettings) + .build()); + moduleCtlr.toggleModuleState("回测测试"); + logCtlr.setModuleLogLevel("回测测试", LogLevel.TRACE); + // 开始测试 + SimTradeGateway simGateway = (SimTradeGateway) gatewayMgr.get(Identifier.of("模拟账户")); + IModule module = moduleMgr.get(Identifier.of("回测测试")); + Contract c = contractMgr.getContract(ChannelType.SIM, "sim9901").contract(); + long t = System.currentTimeMillis(); + for(int i=0; i<4; i++) { + Tick tick = Tick.builder() + .actionDay(CommonUtils.millsToLocalDateTime(t).toLocalDate()) + .actionTime(CommonUtils.millsToLocalDateTime(t).toLocalTime()) + .tradingDay(LocalDate.now()) + .actionTimestamp(t) + .lastPrice(5000) + .askPrice(List.of(5001D)) + .bidPrice(List.of(4999D)) + .askVolume(List.of(10000)) + .bidVolume(List.of(10000)) + .channelType(ChannelType.SIM) + .contract(c) + .gatewayId("SIM") + .build(); + mktCenter.onTick(tick); + module.getModuleContext().onTick(tick); + simGateway.onTick(tick); + t += TimeUnit.MINUTES.toMillis(2); + Thread.sleep(1500); + } + // 期望有正常成交 + ResultBean> dealRecordResult = moduleCtlr.getDealRecords("回测测试"); + assertThat(dealRecordResult.getData()).isNotEmpty(); + } + +} diff --git a/pom.xml b/pom.xml index 1aacc0053..797287aa1 100644 --- a/pom.xml +++ b/pom.xml @@ -631,7 +631,7 @@ LINE COVEREDRATIO - 0.5 + 0.6