Skip to content

Commit

Permalink
提升单元测试覆盖率
Browse files Browse the repository at this point in the history
  • Loading branch information
kevinhuangwl committed Jan 29, 2024
1 parent 56893a2 commit 8d0fb20
Show file tree
Hide file tree
Showing 9 changed files with 477 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -241,7 +242,12 @@ public List<IContract> 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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -49,6 +50,8 @@ public class PlaybackContext implements IPlaybackContext{

private Set<ContractDataLoader> loaders;

private IMarketCenter mktCenter;

private Runnable stopCallback;

private boolean isRunning;
Expand All @@ -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();
Expand Down Expand Up @@ -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;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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())) // 仅模拟买一价
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ public void run() {
account.getPositionManager().positionFields().forEach(pf -> feEngine.emitEvent(NorthstarEventType.POSITION, pf));
}

}, 5000, 2000);
}, 0, 1000);
}

@Override
Expand Down
8 changes: 0 additions & 8 deletions northstar-main/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -168,14 +168,6 @@
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>**/org/dromara/northstar/*</exclude>
<exclude>**/org/dromara/northstar/config/*</exclude>
<exclude>**/org/dromara/northstar/web/handler/**/*</exclude>
<exclude>**/org/dromara/northstar/data/**/*</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()));
}
}

Expand Down
265 changes: 265 additions & 0 deletions northstar-main/src/test/java/org/dromara/northstar/CtaModuleTest.java
Original file line number Diff line number Diff line change
@@ -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<ContractSimpleInfo> 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<List<ModuleDealRecord>> 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<ModuleRuntimeDescription> result = moduleCtlr.getModuleRealTimeInfo("K线测试");
assertThat(result.getData().getDataMap().get("模拟合约9901")).isNotEmpty();
}
}
Loading

0 comments on commit 8d0fb20

Please sign in to comment.