diff --git a/BE/.eslintrc.js b/BE/.eslintrc.js index b54cc5a8..02a0ada0 100644 --- a/BE/.eslintrc.js +++ b/BE/.eslintrc.js @@ -35,5 +35,7 @@ module.exports = { '@typescript-eslint/no-unsafe-assignment': 'off', '@typescript-eslint/no-unsafe-member-access': 'off', '@typescript-eslint/naming-convention': 'off', + 'no-restricted-syntax': 'off', + 'no-await-in-loop': 'off' }, }; diff --git a/BE/src/asset/asset-service.spec.ts b/BE/src/asset/asset-service.spec.ts new file mode 100644 index 00000000..719ae277 --- /dev/null +++ b/BE/src/asset/asset-service.spec.ts @@ -0,0 +1,203 @@ +import { Test } from '@nestjs/testing'; +import { DeepPartial } from 'typeorm'; +import { AssetService } from './asset.service'; +import { UserStockRepository } from './user-stock.repository'; +import { AssetRepository } from './asset.repository'; +import { StockDetailService } from '../stock/detail/stock-detail.service'; +import { StockPriceSocketService } from '../stockSocket/stock-price-socket.service'; +import { TradeType } from '../stock/order/enum/trade-type'; +import { StatusType } from '../stock/order/enum/status-type'; +import { Asset } from './asset.entity'; +import { AssetResponseDto } from './dto/asset-response.dto'; +import { StockElementResponseDto } from './dto/stock-element-response.dto'; +import { MypageResponseDto } from './dto/mypage-response.dto'; + +describe('asset test', () => { + let assetService: AssetService; + let userStockRepository: UserStockRepository; + let assetRepository: AssetRepository; + let stockDetailService: StockDetailService; + + beforeEach(async () => { + const mockUserStockRepository = { + findOneBy: jest.fn(), + findUserStockWithNameByUserId: jest.fn(), + findAllDistinctCode: jest.fn(), + find: jest.fn(), + }; + const mockAssetRepository = { + findAllPendingOrders: jest.fn(), + findOneBy: jest.fn(), + save: jest.fn(), + }; + const mockStockDetailService = { getInquirePrice: jest.fn() }; + const mockStockPriceSocketService = { subscribeByCode: jest.fn() }; + + const module = await Test.createTestingModule({ + providers: [ + AssetService, + { provide: UserStockRepository, useValue: mockUserStockRepository }, + { provide: AssetRepository, useValue: mockAssetRepository }, + { + provide: StockDetailService, + useValue: mockStockDetailService, + }, + { + provide: StockPriceSocketService, + useValue: mockStockPriceSocketService, + }, + ], + }).compile(); + + assetService = module.get(AssetService); + userStockRepository = module.get(UserStockRepository); + assetRepository = module.get(AssetRepository); + stockDetailService = module.get(StockDetailService); + }); + + it('보유 주식과 미체결 주문을 모두 반영한 매도 가능 주식 수를 반환한다.', async () => { + jest.spyOn(userStockRepository, 'findOneBy').mockResolvedValue({ + id: 1, + user_id: 1, + stock_code: '005930', + quantity: 1, + avg_price: 1000, + last_updated: new Date(), + }); + + jest.spyOn(assetRepository, 'findAllPendingOrders').mockResolvedValue([ + { + id: 1, + user_id: 1, + stock_code: '005930', + trade_type: TradeType.SELL, + amount: 1, + price: 1000, + status: StatusType.PENDING, + created_at: new Date(), + }, + ]); + + expect(await assetService.getUserStockByCode(1, '005930')).toEqual({ + quantity: 0, + avg_price: 1000, + }); + }); + + it('보유 자산과 미체결 주문을 모두 반영한 매수 가능 금액을 반환한다.', async () => { + jest.spyOn(assetRepository, 'findOneBy').mockResolvedValue({ + id: 1, + user_id: 1, + stock_balance: 0, + cash_balance: 1000, + total_asset: 1000, + total_profit: 0, + total_profit_rate: 0, + }); + + jest.spyOn(assetRepository, 'findAllPendingOrders').mockResolvedValue([ + { + id: 1, + user_id: 1, + stock_code: '005930', + trade_type: TradeType.BUY, + amount: 1, + price: 1000, + status: StatusType.PENDING, + created_at: new Date(), + }, + ]); + + expect(await assetService.getCashBalance(1)).toEqual({ + cash_balance: 0, + }); + }); + + it('마이페이지 조회 시 종목의 현재가를 반영한 총 자산을 반환한다.', async () => { + jest + .spyOn(userStockRepository, 'findUserStockWithNameByUserId') + .mockResolvedValue([ + { + user_stocks_id: 1, + user_stocks_user_id: 1, + user_stocks_stock_code: '005930', + user_stocks_quantity: 1, + user_stocks_avg_price: '1000', + user_stocks_last_updated: new Date(), + stocks_code: '005930', + stocks_name: '삼성전자', + stocks_market: 'KOSPI', + }, + ]); + + jest.spyOn(assetRepository, 'findOneBy').mockResolvedValue({ + id: 1, + user_id: 1, + stock_balance: 0, + cash_balance: 1000, + total_asset: 1000, + total_profit: 0, + total_profit_rate: 0, + }); + + jest + .spyOn(userStockRepository, 'findAllDistinctCode') + .mockResolvedValue([{ stock_code: '005930' }]); + + jest.spyOn(stockDetailService, 'getInquirePrice').mockResolvedValue({ + hts_kor_isnm: '삼성전자', + stck_shrn_iscd: '005930', + stck_prpr: '53600', + prdy_vrss: '-600', + prdy_vrss_sign: '5', + prdy_ctrt: '-1.11', + hts_avls: '3199803', + per: '25.15', + stck_mxpr: '70400', + stck_llam: '38000', + is_bookmarked: false, + }); + + jest.spyOn(userStockRepository, 'find').mockResolvedValue([ + { + id: 1, + user_id: 1, + stock_code: '005930', + quantity: 1, + avg_price: 1000, + last_updated: new Date(), + }, + ]); + + jest + .spyOn(assetRepository, 'save') + .mockImplementation((updatedAsset) => + Promise.resolve(updatedAsset as DeepPartial & Asset), + ); + + const assetResponse = new AssetResponseDto( + 1000, + 53600, + 54600, + -9945400, + '-99.45', + false, + ); + const stockElementResponse = new StockElementResponseDto( + '삼성전자', + '005930', + 1, + 1000, + '53600', + '-600', + '5', + '-1.11', + ); + + const expected = new MypageResponseDto(); + expected.asset = assetResponse; + expected.stocks = [stockElementResponse]; + + expect(await assetService.getMyPage(1)).toEqual(expected); + }); +}); diff --git a/BE/src/asset/asset.service.ts b/BE/src/asset/asset.service.ts index a9c37c0d..f1727401 100644 --- a/BE/src/asset/asset.service.ts +++ b/BE/src/asset/asset.service.ts @@ -148,8 +148,10 @@ export class AssetService { const userStocks: UserStock[] = await this.userStockRepository.findAllDistinctCode(userId); - userStocks.map((userStock) => - this.stockPriceSocketService.subscribeByCode(userStock.stock_code), + await Promise.all( + userStocks.map((userStock) => + this.stockPriceSocketService.subscribeByCode(userStock.stock_code), + ), ); } @@ -157,8 +159,8 @@ export class AssetService { const userStocks: UserStock[] = await this.userStockRepository.findAllDistinctCode(userId); - userStocks.map((userStock) => - this.stockPriceSocketService.unsubscribeByCode(userStock.stock_code), + await this.stockPriceSocketService.unsubscribeByCode( + userStocks.map((userStock) => userStock.stock_code), ); } diff --git a/BE/src/common/redis/redis.domain-service.ts b/BE/src/common/redis/redis.domain-service.ts index 77700cc4..f25c7114 100644 --- a/BE/src/common/redis/redis.domain-service.ts +++ b/BE/src/common/redis/redis.domain-service.ts @@ -4,19 +4,20 @@ import Redis from 'ioredis'; @Injectable() export class RedisDomainService { constructor( - @Inject('REDIS_CLIENT') - private readonly redis: Redis, + @Inject('REDIS_CLIENT') private readonly redis: Redis, + @Inject('REDIS_PUBLISHER') private readonly publisher: Redis, + @Inject('REDIS_SUBSCRIBER') private readonly subscriber: Redis, ) {} async exists(key: string): Promise { return this.redis.exists(key); } - async get(key: string): Promise { + async get(key: string): Promise { return this.redis.get(key); } - async set(key: string, value: string, expires?: number): Promise<'OK'> { + async set(key: string, value: number, expires?: number): Promise<'OK'> { if (expires) { return this.redis.set(key, value, 'EX', expires); } @@ -62,4 +63,30 @@ export class RedisDomainService { async expire(key: string, seconds: number): Promise { return this.redis.expire(key, seconds); } + + async publish(channel: string, message: string) { + return this.publisher.publish(channel, message); + } + + async subscribe(channel: string) { + await this.subscriber.subscribe(channel); + } + + on(callback: (message: string) => void) { + this.redis.on('message', (message) => { + callback(message); + }); + } + + async unsubscribe(channel: string) { + return this.redis.unsubscribe(channel); + } + + async increment(key: string) { + return this.redis.incr(key); + } + + async decrement(key: string) { + return this.redis.decr(key); + } } diff --git a/BE/src/common/redis/redis.module.ts b/BE/src/common/redis/redis.module.ts index 863812b5..595b02ae 100644 --- a/BE/src/common/redis/redis.module.ts +++ b/BE/src/common/redis/redis.module.ts @@ -15,8 +15,31 @@ import { RedisDomainService } from './redis.domain-service'; }); }, }, + { + provide: 'REDIS_PUBLISHER', + useFactory: () => { + return new Redis({ + host: process.env.REDIS_HOST || 'redis', + port: Number(process.env.REDIS_PORT || 6379), + }); + }, + }, + { + provide: 'REDIS_SUBSCRIBER', + useFactory: () => { + return new Redis({ + host: process.env.REDIS_HOST || 'redis', + port: Number(process.env.REDIS_PORT || 6379), + }); + }, + }, + RedisDomainService, + ], + exports: [ RedisDomainService, + 'REDIS_CLIENT', + 'REDIS_PUBLISHER', + 'REDIS_SUBSCRIBER', ], - exports: [RedisDomainService, 'REDIS_CLIENT'], }) export class RedisModule {} diff --git a/BE/src/common/websocket/base-socket.domain-service.ts b/BE/src/common/websocket/base-socket.domain-service.ts index 5f77220f..f2158240 100644 --- a/BE/src/common/websocket/base-socket.domain-service.ts +++ b/BE/src/common/websocket/base-socket.domain-service.ts @@ -6,6 +6,7 @@ import { OnModuleInit, } from '@nestjs/common'; import { SocketTokenDomainService } from './socket-token.domain-service'; +import { RedisDomainService } from '../redis/redis.domain-service'; @Injectable() export class BaseSocketDomainService implements OnModuleInit { @@ -20,6 +21,7 @@ export class BaseSocketDomainService implements OnModuleInit { constructor( private readonly socketTokenDomainService: SocketTokenDomainService, + private readonly redisDomainService: RedisDomainService, ) {} async onModuleInit() { @@ -57,11 +59,19 @@ export class BaseSocketDomainService implements OnModuleInit { } const dataList = data[3].split('^'); - if (Number(dataList[1]) % 500 === 0) this.logger.log(`한국투자증권 데이터 수신 성공 (5분 단위)`, data[1]); - this.socketDataHandlers[data[1]](dataList); + if (data[1] === 'H0UPCNT0') { + this.socketDataHandlers.H0UPCNT0(dataList); + return; + } + + this.redisDomainService + .publish(`stock/${dataList[0]}`, data[3]) + .catch((err) => { + throw new InternalServerErrorException(err); + }); }; this.socket.onclose = () => { @@ -72,6 +82,11 @@ export class BaseSocketDomainService implements OnModuleInit { }); }, 60000); }; + + this.redisDomainService.on((message) => { + const dataList = message.split('^'); + this.socketDataHandlers.H0STCNT0(dataList); + }); } registerCode(trId: string, trKey: string) { diff --git a/BE/src/news/news.service.ts b/BE/src/news/news.service.ts index 3ec42496..2689e230 100644 --- a/BE/src/news/news.service.ts +++ b/BE/src/news/news.service.ts @@ -1,4 +1,5 @@ import { Injectable, InternalServerErrorException } from '@nestjs/common'; +import { Cron } from '@nestjs/schedule'; import { InjectDataSource } from '@nestjs/typeorm'; import { DataSource, In } from 'typeorm'; import { NaverApiDomianService } from './naver-api-domian.service'; @@ -47,7 +48,7 @@ export class NewsService { }; } - // @Cron('*/30 8-16 * * 1-5') + @Cron('*/1 * * * *') async cronNewsData() { const queryRunner = this.dataSource.createQueryRunner(); diff --git a/BE/src/stock/bookmark/stock-bookmark.service.spec.ts b/BE/src/stock/bookmark/stock-bookmark.service.spec.ts new file mode 100644 index 00000000..d0bb4197 --- /dev/null +++ b/BE/src/stock/bookmark/stock-bookmark.service.spec.ts @@ -0,0 +1,139 @@ +import { Test } from '@nestjs/testing'; +import { BadRequestException, NotFoundException } from '@nestjs/common'; +import { StockBookmarkRepository } from './stock-bookmark.repository'; +import { StockDetailService } from '../detail/stock-detail.service'; +import { StockBookmarkService } from './stock-bookmark.service'; +import { StockBookmarkResponseDto } from './dto/stock-bookmark-response,dto'; + +describe('stock bookmark test', () => { + let stockBookmarkService: StockBookmarkService; + let stockBookmarkRepository: StockBookmarkRepository; + let stockDetailService: StockDetailService; + + beforeEach(async () => { + const mockStockBookmarkRepository = { + existsBy: jest.fn(), + create: jest.fn(), + insert: jest.fn(), + findOneBy: jest.fn(), + remove: jest.fn(), + findBookmarkWithNameByUserId: jest.fn(), + }; + const mockStockDetailService = { getInquirePrice: jest.fn() }; + + const module = await Test.createTestingModule({ + providers: [ + StockBookmarkService, + { + provide: StockBookmarkRepository, + useValue: mockStockBookmarkRepository, + }, + { + provide: StockDetailService, + useValue: mockStockDetailService, + }, + ], + }).compile(); + + stockBookmarkService = module.get(StockBookmarkService); + stockBookmarkRepository = module.get(StockBookmarkRepository); + stockDetailService = module.get(StockDetailService); + }); + + it('즐겨찾기에 등록되지 않은 종목에 대해 즐겨찾기 등록할 경우, DB에 즐겨찾기가 추가된다.', async () => { + jest.spyOn(stockBookmarkRepository, 'existsBy').mockResolvedValue(false); + + const createMock = jest.fn(); + jest + .spyOn(stockBookmarkRepository, 'create') + .mockImplementation(createMock); + + const saveMock = jest.fn(); + jest.spyOn(stockBookmarkRepository, 'insert').mockImplementation(saveMock); + + await stockBookmarkService.registerBookmark(1, '005930'); + + expect(createMock).toHaveBeenCalledWith({ + user_id: 1, + stock_code: '005930', + }); + expect(saveMock).toHaveBeenCalled(); + }); + + it('즐겨찾기에 이미 등록된 종목에 대해 즐겨찾기 등록할 경우, BadRequest 예외가 발생한다.', async () => { + jest.spyOn(stockBookmarkRepository, 'existsBy').mockResolvedValue(true); + + await expect( + stockBookmarkService.registerBookmark(1, '005930'), + ).rejects.toThrow(BadRequestException); + }); + + it('존재하는 즐겨찾기를 취소할 경우, DB에서 해당 즐겨찾기가 삭제된다.', async () => { + jest.spyOn(stockBookmarkRepository, 'findOneBy').mockResolvedValue({ + id: 1, + stock_code: '005930', + user_id: 1, + }); + + const removeMock = jest.fn(); + jest + .spyOn(stockBookmarkRepository, 'remove') + .mockImplementation(removeMock); + + await stockBookmarkService.unregisterBookmark(1, '005930'); + + expect(removeMock).toHaveBeenCalled(); + }); + + it('존재하지 않는 즐겨찾기를 취소할 경우, NotFound 예외가 발생한다.', async () => { + jest + .spyOn(stockBookmarkRepository, 'findOneBy') + .mockResolvedValue(undefined); + + await expect( + stockBookmarkService.unregisterBookmark(1, '005930'), + ).rejects.toThrow(NotFoundException); + }); + + it('즐겨찾기를 조회할 경우, 해당 종목들의 현재가를 포함한 데이터가 반환된다.', async () => { + jest + .spyOn(stockBookmarkRepository, 'findBookmarkWithNameByUserId') + .mockResolvedValue([ + { + b_id: 1, + b_user_id: 1, + b_stock_code: '005930', + s_code: '005930', + s_name: '삼성전자', + s_market: 'KOSPI', + }, + ]); + + jest.spyOn(stockDetailService, 'getInquirePrice').mockResolvedValue({ + hts_kor_isnm: '삼성전자', + stck_shrn_iscd: '005930', + stck_prpr: '53600', + prdy_vrss: '-600', + prdy_vrss_sign: '5', + prdy_ctrt: '-1.11', + hts_avls: '3199803', + per: '25.15', + stck_mxpr: '70400', + stck_llam: '38000', + is_bookmarked: true, + }); + + const stockBookmarkResponse = new StockBookmarkResponseDto( + '삼성전자', + '005930', + '53600', + '-600', + '5', + '-1.11', + ); + + expect(await stockBookmarkService.getBookmarkList(1)).toEqual([ + stockBookmarkResponse, + ]); + }); +}); diff --git a/BE/src/stock/detail/mockdata/stock-detail-chart.mockdata.ts b/BE/src/stock/detail/mockdata/stock-detail-chart.mockdata.ts new file mode 100644 index 00000000..ef267f98 --- /dev/null +++ b/BE/src/stock/detail/mockdata/stock-detail-chart.mockdata.ts @@ -0,0 +1,1539 @@ +export const STOCK_DETAIL_CHART_MOCK = { + output1: { + prdy_vrss: '-600', + prdy_vrss_sign: '5', + prdy_ctrt: '-1.11', + stck_prdy_clpr: '54200', + acml_vol: '22044867', + acml_tr_pbmn: '1184225188140', + hts_kor_isnm: '삼성전자', + stck_prpr: '53600', + stck_shrn_iscd: '005930', + prdy_vol: '24513532', + stck_mxpr: '70400', + stck_llam: '38000', + stck_oprc: '54300', + stck_hgpr: '54400', + stck_lwpr: '53100', + stck_prdy_oprc: '55100', + stck_prdy_hgpr: '55300', + stck_prdy_lwpr: '53800', + askp: '53700', + bidp: '53600', + prdy_vrss_vol: '-2468665', + vol_tnrt: '0.37', + stck_fcam: '100', + lstn_stcn: '5969782550', + cpfn: '7780', + hts_avls: '3199803', + per: '25.15', + eps: '2131.00', + pbr: '1.03', + 'itewhol_loan_rmnd_ratem name': '0.26', + }, + output2: [ + { + stck_bsop_date: '20241202', + stck_clpr: '53600', + stck_oprc: '54300', + stck_hgpr: '54400', + stck_lwpr: '53100', + acml_vol: '22044868', + acml_tr_pbmn: '1184225188140', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '5', + prdy_vrss: '-600', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20241129', + stck_clpr: '54200', + stck_oprc: '55100', + stck_hgpr: '55300', + stck_lwpr: '53800', + acml_vol: '24513532', + acml_tr_pbmn: '1331023724400', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '5', + prdy_vrss: '-1300', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20241128', + stck_clpr: '55500', + stck_oprc: '56000', + stck_hgpr: '56400', + stck_lwpr: '55200', + acml_vol: '20001134', + acml_tr_pbmn: '1114564616052', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '5', + prdy_vrss: '-800', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20241127', + stck_clpr: '56300', + stck_oprc: '57700', + stck_hgpr: '57800', + stck_lwpr: '56000', + acml_vol: '21808388', + acml_tr_pbmn: '1236109255350', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '5', + prdy_vrss: '-2000', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20241126', + stck_clpr: '58300', + stck_oprc: '57900', + stck_hgpr: '58900', + stck_lwpr: '57500', + acml_vol: '23209404', + acml_tr_pbmn: '1350434011510', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '2', + prdy_vrss: '400', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20241125', + stck_clpr: '57900', + stck_oprc: '57400', + stck_hgpr: '57900', + stck_lwpr: '56700', + acml_vol: '36237324', + acml_tr_pbmn: '2086462735928', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '2', + prdy_vrss: '1900', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20241122', + stck_clpr: '56000', + stck_oprc: '56000', + stck_hgpr: '56700', + stck_lwpr: '55900', + acml_vol: '15281543', + acml_tr_pbmn: '860560563100', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '5', + prdy_vrss: '-400', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20241121', + stck_clpr: '56400', + stck_oprc: '54900', + stck_hgpr: '56900', + stck_lwpr: '54700', + acml_vol: '19096850', + acml_tr_pbmn: '1068199169900', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '2', + prdy_vrss: '1100', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20241120', + stck_clpr: '55300', + stck_oprc: '56100', + stck_hgpr: '56500', + stck_lwpr: '54800', + acml_vol: '20864668', + acml_tr_pbmn: '1156033483400', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '5', + prdy_vrss: '-1000', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20241119', + stck_clpr: '56300', + stck_oprc: '56500', + stck_hgpr: '57500', + stck_lwpr: '55900', + acml_vol: '31539632', + acml_tr_pbmn: '1786885263500', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '5', + prdy_vrss: '-400', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20241118', + stck_clpr: '56700', + stck_oprc: '57000', + stck_hgpr: '57500', + stck_lwpr: '55900', + acml_vol: '48095232', + acml_tr_pbmn: '2726095349068', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '2', + prdy_vrss: '3200', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20241115', + stck_clpr: '53500', + stck_oprc: '50300', + stck_hgpr: '54200', + stck_lwpr: '50300', + acml_vol: '46774484', + acml_tr_pbmn: '2464749360200', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '2', + prdy_vrss: '3600', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20241114', + stck_clpr: '49900', + stck_oprc: '50200', + stck_hgpr: '51800', + stck_lwpr: '49900', + acml_vol: '48510716', + acml_tr_pbmn: '2465304011525', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '5', + prdy_vrss: '-700', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20241113', + stck_clpr: '50600', + stck_oprc: '52000', + stck_hgpr: '53000', + stck_lwpr: '50500', + acml_vol: '52527996', + acml_tr_pbmn: '2704092634132', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '5', + prdy_vrss: '-2400', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20241112', + stck_clpr: '53000', + stck_oprc: '54600', + stck_hgpr: '54600', + stck_lwpr: '53000', + acml_vol: '37962880', + acml_tr_pbmn: '2037790866499', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '5', + prdy_vrss: '-2000', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20241111', + stck_clpr: '55000', + stck_oprc: '56700', + stck_hgpr: '56800', + stck_lwpr: '55000', + acml_vol: '29811326', + acml_tr_pbmn: '1654820869900', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '5', + prdy_vrss: '-2000', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20241108', + stck_clpr: '57000', + stck_oprc: '58000', + stck_hgpr: '58300', + stck_lwpr: '57000', + acml_vol: '13877396', + acml_tr_pbmn: '799664427482', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '5', + prdy_vrss: '-500', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20241107', + stck_clpr: '57500', + stck_oprc: '56900', + stck_hgpr: '58100', + stck_lwpr: '56800', + acml_vol: '17043102', + acml_tr_pbmn: '982114998400', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '2', + prdy_vrss: '200', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20241106', + stck_clpr: '57300', + stck_oprc: '57600', + stck_hgpr: '58000', + stck_lwpr: '56300', + acml_vol: '22092218', + acml_tr_pbmn: '1262148460900', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '5', + prdy_vrss: '-300', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20241105', + stck_clpr: '57600', + stck_oprc: '57800', + stck_hgpr: '58100', + stck_lwpr: '57200', + acml_vol: '17484474', + acml_tr_pbmn: '1007627262350', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '5', + prdy_vrss: '-1100', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20241104', + stck_clpr: '58700', + stck_oprc: '58600', + stck_hgpr: '59400', + stck_lwpr: '58400', + acml_vol: '15586947', + acml_tr_pbmn: '916941057220', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '2', + prdy_vrss: '400', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20241101', + stck_clpr: '58300', + stck_oprc: '59000', + stck_hgpr: '59600', + stck_lwpr: '58100', + acml_vol: '19083180', + acml_tr_pbmn: '1121093971420', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '5', + prdy_vrss: '-900', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20241031', + stck_clpr: '59200', + stck_oprc: '58500', + stck_hgpr: '61200', + stck_lwpr: '58300', + acml_vol: '35809196', + acml_tr_pbmn: '2141845557150', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '2', + prdy_vrss: '100', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20241030', + stck_clpr: '59100', + stck_oprc: '59100', + stck_hgpr: '59800', + stck_lwpr: '58600', + acml_vol: '19838512', + acml_tr_pbmn: '1173730435406', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '5', + prdy_vrss: '-500', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20241029', + stck_clpr: '59600', + stck_oprc: '58000', + stck_hgpr: '59600', + stck_lwpr: '57300', + acml_vol: '28369314', + acml_tr_pbmn: '1668409859850', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '2', + prdy_vrss: '1500', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20241028', + stck_clpr: '58100', + stck_oprc: '55700', + stck_hgpr: '58500', + stck_lwpr: '55700', + acml_vol: '27775008', + acml_tr_pbmn: '1597162892050', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '2', + prdy_vrss: '2200', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20241025', + stck_clpr: '55900', + stck_oprc: '56000', + stck_hgpr: '56900', + stck_lwpr: '55800', + acml_vol: '25829316', + acml_tr_pbmn: '1448167771502', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '5', + prdy_vrss: '-700', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20241024', + stck_clpr: '56600', + stck_oprc: '58200', + stck_hgpr: '58500', + stck_lwpr: '56600', + acml_vol: '31499922', + acml_tr_pbmn: '1809877767246', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '5', + prdy_vrss: '-2500', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20241023', + stck_clpr: '59100', + stck_oprc: '57500', + stck_hgpr: '60000', + stck_lwpr: '57100', + acml_vol: '27300780', + acml_tr_pbmn: '1598545283036', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '2', + prdy_vrss: '1400', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20241022', + stck_clpr: '57700', + stck_oprc: '58800', + stck_hgpr: '58900', + stck_lwpr: '57700', + acml_vol: '27582528', + acml_tr_pbmn: '1604866816800', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '5', + prdy_vrss: '-1300', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20241021', + stck_clpr: '59000', + stck_oprc: '59000', + stck_hgpr: '59600', + stck_lwpr: '58500', + acml_vol: '18514904', + acml_tr_pbmn: '1092561695060', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '5', + prdy_vrss: '-200', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20241018', + stck_clpr: '59200', + stck_oprc: '59900', + stck_hgpr: '60100', + stck_lwpr: '59100', + acml_vol: '14420260', + acml_tr_pbmn: '857377297200', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '5', + prdy_vrss: '-500', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20241017', + stck_clpr: '59700', + stck_oprc: '59400', + stck_hgpr: '60100', + stck_lwpr: '59100', + acml_vol: '23372872', + acml_tr_pbmn: '1391873389950', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '2', + prdy_vrss: '200', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20241016', + stck_clpr: '59500', + stck_oprc: '59400', + stck_hgpr: '60000', + stck_lwpr: '59200', + acml_vol: '23303268', + acml_tr_pbmn: '1389151960832', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '5', + prdy_vrss: '-1500', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20241015', + stck_clpr: '61000', + stck_oprc: '61100', + stck_hgpr: '61400', + stck_lwpr: '60100', + acml_vol: '22715240', + acml_tr_pbmn: '1381789051900', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '2', + prdy_vrss: '200', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20241014', + stck_clpr: '60800', + stck_oprc: '59500', + stck_hgpr: '61200', + stck_lwpr: '59400', + acml_vol: '20886248', + acml_tr_pbmn: '1262857492687', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '2', + prdy_vrss: '1500', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20241011', + stck_clpr: '59300', + stck_oprc: '59100', + stck_hgpr: '60100', + stck_lwpr: '59000', + acml_vol: '29623968', + acml_tr_pbmn: '1765728518988', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '2', + prdy_vrss: '400', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20241010', + stck_clpr: '58900', + stck_oprc: '60100', + stck_hgpr: '60200', + stck_lwpr: '58900', + acml_vol: '45262216', + acml_tr_pbmn: '2687253337620', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '5', + prdy_vrss: '-1400', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20241008', + stck_clpr: '60300', + stck_oprc: '60000', + stck_hgpr: '61000', + stck_lwpr: '59900', + acml_vol: '27411786', + acml_tr_pbmn: '1652788512596', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '5', + prdy_vrss: '-700', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20241007', + stck_clpr: '61000', + stck_oprc: '60200', + stck_hgpr: '61900', + stck_lwpr: '59500', + acml_vol: '35066532', + acml_tr_pbmn: '2118597389800', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '2', + prdy_vrss: '400', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20241004', + stck_clpr: '60600', + stck_oprc: '61000', + stck_hgpr: '61700', + stck_lwpr: '60500', + acml_vol: '24247578', + acml_tr_pbmn: '1480865292580', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '5', + prdy_vrss: '-700', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20241002', + stck_clpr: '61300', + stck_oprc: '60500', + stck_hgpr: '61900', + stck_lwpr: '59900', + acml_vol: '28473536', + acml_tr_pbmn: '1737678269615', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '5', + prdy_vrss: '-200', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20240930', + stck_clpr: '61500', + stck_oprc: '64200', + stck_hgpr: '64300', + stck_lwpr: '61500', + acml_vol: '32694164', + acml_tr_pbmn: '2043449921900', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '5', + prdy_vrss: '-2700', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20240927', + stck_clpr: '64200', + stck_oprc: '64700', + stck_hgpr: '65400', + stck_lwpr: '64200', + acml_vol: '28433030', + acml_tr_pbmn: '1842321896031', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '5', + prdy_vrss: '-500', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20240926', + stck_clpr: '64700', + stck_oprc: '63900', + stck_hgpr: '64900', + stck_lwpr: '63700', + acml_vol: '37566016', + acml_tr_pbmn: '2417513255958', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '2', + prdy_vrss: '2500', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20240925', + stck_clpr: '62200', + stck_oprc: '63800', + stck_hgpr: '64200', + stck_lwpr: '62200', + acml_vol: '28652438', + acml_tr_pbmn: '1816598498400', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '5', + prdy_vrss: '-1000', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20240924', + stck_clpr: '63200', + stck_oprc: '62800', + stck_hgpr: '63400', + stck_lwpr: '62400', + acml_vol: '26957500', + acml_tr_pbmn: '1695341224952', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '2', + prdy_vrss: '600', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20240923', + stck_clpr: '62600', + stck_oprc: '62300', + stck_hgpr: '63500', + stck_lwpr: '62200', + acml_vol: '28542376', + acml_tr_pbmn: '1787973967000', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '5', + prdy_vrss: '-400', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20240920', + stck_clpr: '63000', + stck_oprc: '63800', + stck_hgpr: '64700', + stck_lwpr: '63000', + acml_vol: '32746056', + acml_tr_pbmn: '2086233666488', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '5', + prdy_vrss: '-100', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20240919', + stck_clpr: '63100', + stck_oprc: '64000', + stck_hgpr: '64400', + stck_lwpr: '62200', + acml_vol: '49402712', + acml_tr_pbmn: '3115926466558', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '5', + prdy_vrss: '-1300', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20240913', + stck_clpr: '64400', + stck_oprc: '65000', + stck_hgpr: '65500', + stck_lwpr: '64300', + acml_vol: '25045136', + acml_tr_pbmn: '1621747052400', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '5', + prdy_vrss: '-1900', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20240912', + stck_clpr: '66300', + stck_oprc: '66000', + stck_hgpr: '66600', + stck_lwpr: '65200', + acml_vol: '35884104', + acml_tr_pbmn: '2369440968156', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '2', + prdy_vrss: '1400', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20240911', + stck_clpr: '64900', + stck_oprc: '65100', + stck_hgpr: '65500', + stck_lwpr: '64200', + acml_vol: '35809708', + acml_tr_pbmn: '2325181296712', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '5', + prdy_vrss: '-1300', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20240910', + stck_clpr: '66200', + stck_oprc: '67000', + stck_hgpr: '67300', + stck_lwpr: '66000', + acml_vol: '30651376', + acml_tr_pbmn: '2041161645484', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '5', + prdy_vrss: '-1300', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20240909', + stck_clpr: '67500', + stck_oprc: '66900', + stck_hgpr: '68200', + stck_lwpr: '66600', + acml_vol: '23263298', + acml_tr_pbmn: '1566504915737', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '5', + prdy_vrss: '-1400', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20240906', + stck_clpr: '68900', + stck_oprc: '69100', + stck_hgpr: '69700', + stck_lwpr: '68000', + acml_vol: '19022300', + acml_tr_pbmn: '1309833642247', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '5', + prdy_vrss: '-100', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20240905', + stck_clpr: '69000', + stck_oprc: '70100', + stck_hgpr: '71200', + stck_lwpr: '69000', + acml_vol: '25686768', + acml_tr_pbmn: '1795890039418', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '5', + prdy_vrss: '-1000', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20240904', + stck_clpr: '70000', + stck_oprc: '69800', + stck_hgpr: '71100', + stck_lwpr: '69800', + acml_vol: '27366564', + acml_tr_pbmn: '1923751777154', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '5', + prdy_vrss: '-2500', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20240903', + stck_clpr: '72500', + stck_oprc: '74100', + stck_hgpr: '74300', + stck_lwpr: '72500', + acml_vol: '16314599', + acml_tr_pbmn: '1195017081270', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '5', + prdy_vrss: '-1900', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20240902', + stck_clpr: '74400', + stck_oprc: '74500', + stck_hgpr: '74700', + stck_lwpr: '73500', + acml_vol: '12641376', + acml_tr_pbmn: '938130139986', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '2', + prdy_vrss: '100', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20240830', + stck_clpr: '74300', + stck_oprc: '74400', + stck_hgpr: '75000', + stck_lwpr: '74100', + acml_vol: '16358520', + acml_tr_pbmn: '1217838630548', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '2', + prdy_vrss: '300', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20240829', + stck_clpr: '74000', + stck_oprc: '73600', + stck_hgpr: '74700', + stck_lwpr: '73500', + acml_vol: '16884480', + acml_tr_pbmn: '1250517121400', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '5', + prdy_vrss: '-2400', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20240828', + stck_clpr: '76400', + stck_oprc: '75800', + stck_hgpr: '76400', + stck_lwpr: '75400', + acml_vol: '9794514', + acml_tr_pbmn: '743267480799', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '2', + prdy_vrss: '600', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20240827', + stck_clpr: '75800', + stck_oprc: '75700', + stck_hgpr: '76500', + stck_lwpr: '75600', + acml_vol: '11130145', + acml_tr_pbmn: '845521021636', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '5', + prdy_vrss: '-300', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20240826', + stck_clpr: '76100', + stck_oprc: '78100', + stck_hgpr: '78200', + stck_lwpr: '76000', + acml_vol: '15655938', + acml_tr_pbmn: '1200212317200', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '5', + prdy_vrss: '-1600', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20240823', + stck_clpr: '77700', + stck_oprc: '77700', + stck_hgpr: '78400', + stck_lwpr: '77500', + acml_vol: '9420306', + acml_tr_pbmn: '733115152500', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '5', + prdy_vrss: '-600', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20240822', + stck_clpr: '78300', + stck_oprc: '78700', + stck_hgpr: '78900', + stck_lwpr: '77800', + acml_vol: '8149101', + acml_tr_pbmn: '637688676000', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '3', + prdy_vrss: '0', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20240821', + stck_clpr: '78300', + stck_oprc: '77900', + stck_hgpr: '78600', + stck_lwpr: '77800', + acml_vol: '7805598', + acml_tr_pbmn: '610445256200', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '5', + prdy_vrss: '-600', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20240820', + stck_clpr: '78900', + stck_oprc: '79500', + stck_hgpr: '79800', + stck_lwpr: '78700', + acml_vol: '10683836', + acml_tr_pbmn: '846450905406', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '2', + prdy_vrss: '600', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20240819', + stck_clpr: '78300', + stck_oprc: '80100', + stck_hgpr: '80100', + stck_lwpr: '78000', + acml_vol: '14146565', + acml_tr_pbmn: '1112867011200', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '5', + prdy_vrss: '-1900', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20240816', + stck_clpr: '80200', + stck_oprc: '79400', + stck_hgpr: '80200', + stck_lwpr: '78700', + acml_vol: '22061478', + acml_tr_pbmn: '1749786266068', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '2', + prdy_vrss: '3000', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20240814', + stck_clpr: '77200', + stck_oprc: '77400', + stck_hgpr: '77800', + stck_lwpr: '77000', + acml_vol: '13246168', + acml_tr_pbmn: '1023945760936', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '2', + prdy_vrss: '1100', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20240813', + stck_clpr: '76100', + stck_oprc: '76500', + stck_hgpr: '76600', + stck_lwpr: '75500', + acml_vol: '10716261', + acml_tr_pbmn: '814879344944', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '2', + prdy_vrss: '600', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20240812', + stck_clpr: '75500', + stck_oprc: '75200', + stck_hgpr: '75900', + stck_lwpr: '74800', + acml_vol: '9839259', + acml_tr_pbmn: '742667828012', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '2', + prdy_vrss: '800', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20240809', + stck_clpr: '74700', + stck_oprc: '75700', + stck_hgpr: '75800', + stck_lwpr: '74200', + acml_vol: '16388222', + acml_tr_pbmn: '1226725116766', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '2', + prdy_vrss: '1300', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20240808', + stck_clpr: '73400', + stck_oprc: '73200', + stck_hgpr: '73900', + stck_lwpr: '72500', + acml_vol: '28414728', + acml_tr_pbmn: '2079007355535', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '5', + prdy_vrss: '-1300', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20240807', + stck_clpr: '74700', + stck_oprc: '73000', + stck_hgpr: '76000', + stck_lwpr: '72800', + acml_vol: '32710428', + acml_tr_pbmn: '2439049835003', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '2', + prdy_vrss: '2200', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20240806', + stck_clpr: '72500', + stck_oprc: '74900', + stck_hgpr: '75300', + stck_lwpr: '72300', + acml_vol: '47295224', + acml_tr_pbmn: '3482608290900', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '2', + prdy_vrss: '1100', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20240805', + stck_clpr: '71400', + stck_oprc: '76700', + stck_hgpr: '76900', + stck_lwpr: '70200', + acml_vol: '54608792', + acml_tr_pbmn: '4028468177890', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '5', + prdy_vrss: '-8200', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20240802', + stck_clpr: '79600', + stck_oprc: '81000', + stck_hgpr: '81400', + stck_lwpr: '79500', + acml_vol: '25800276', + acml_tr_pbmn: '2072583435468', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '5', + prdy_vrss: '-3500', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20240801', + stck_clpr: '83100', + stck_oprc: '86000', + stck_hgpr: '86100', + stck_lwpr: '83100', + acml_vol: '20900338', + acml_tr_pbmn: '1761066086600', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '5', + prdy_vrss: '-800', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20240731', + stck_clpr: '83900', + stck_oprc: '81200', + stck_hgpr: '83900', + stck_lwpr: '80900', + acml_vol: '20744324', + acml_tr_pbmn: '1709905651240', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '2', + prdy_vrss: '2900', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20240730', + stck_clpr: '81000', + stck_oprc: '80400', + stck_hgpr: '81000', + stck_lwpr: '80000', + acml_vol: '13169636', + acml_tr_pbmn: '1058509976884', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '5', + prdy_vrss: '-200', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20240729', + stck_clpr: '81200', + stck_oprc: '81600', + stck_hgpr: '82000', + stck_lwpr: '81100', + acml_vol: '12797136', + acml_tr_pbmn: '1042595012848', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '2', + prdy_vrss: '300', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20240726', + stck_clpr: '80900', + stck_oprc: '80700', + stck_hgpr: '81300', + stck_lwpr: '80400', + acml_vol: '14508334', + acml_tr_pbmn: '1171301827600', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '2', + prdy_vrss: '500', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20240725', + stck_clpr: '80400', + stck_oprc: '80400', + stck_hgpr: '81000', + stck_lwpr: '80100', + acml_vol: '20323812', + acml_tr_pbmn: '1634938043600', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '5', + prdy_vrss: '-1600', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20240724', + stck_clpr: '82000', + stck_oprc: '82900', + stck_hgpr: '83300', + stck_lwpr: '81900', + acml_vol: '16939084', + acml_tr_pbmn: '1397137102450', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '5', + prdy_vrss: '-1900', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20240723', + stck_clpr: '83900', + stck_oprc: '84200', + stck_hgpr: '84700', + stck_lwpr: '83400', + acml_vol: '15766389', + acml_tr_pbmn: '1325673625798', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '2', + prdy_vrss: '900', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20240722', + stck_clpr: '83000', + stck_oprc: '84400', + stck_hgpr: '84900', + stck_lwpr: '82600', + acml_vol: '18987560', + acml_tr_pbmn: '1581559180516', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '5', + prdy_vrss: '-1400', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20240719', + stck_clpr: '84400', + stck_oprc: '85600', + stck_hgpr: '86100', + stck_lwpr: '84100', + acml_vol: '18569122', + acml_tr_pbmn: '1574018492068', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '5', + prdy_vrss: '-2500', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20240718', + stck_clpr: '86900', + stck_oprc: '83800', + stck_hgpr: '86900', + stck_lwpr: '83800', + acml_vol: '24721790', + acml_tr_pbmn: '2104104629510', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '2', + prdy_vrss: '200', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20240717', + stck_clpr: '86700', + stck_oprc: '87100', + stck_hgpr: '88000', + stck_lwpr: '86400', + acml_vol: '18186490', + acml_tr_pbmn: '1585140028938', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '5', + prdy_vrss: '-1000', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20240716', + stck_clpr: '87700', + stck_oprc: '86900', + stck_hgpr: '88000', + stck_lwpr: '86700', + acml_vol: '16166688', + acml_tr_pbmn: '1413744130426', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '2', + prdy_vrss: '1000', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20240715', + stck_clpr: '86700', + stck_oprc: '84700', + stck_hgpr: '87300', + stck_lwpr: '84100', + acml_vol: '25193080', + acml_tr_pbmn: '2151182147147', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '2', + prdy_vrss: '2300', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20240712', + stck_clpr: '84400', + stck_oprc: '85900', + stck_hgpr: '86100', + stck_lwpr: '84100', + acml_vol: '26344386', + acml_tr_pbmn: '2234914589643', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '5', + prdy_vrss: '-3200', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20240711', + stck_clpr: '87600', + stck_oprc: '88500', + stck_hgpr: '88800', + stck_lwpr: '86700', + acml_vol: '24677608', + acml_tr_pbmn: '2164539095066', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '5', + prdy_vrss: '-200', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20240710', + stck_clpr: '87800', + stck_oprc: '87600', + stck_hgpr: '88000', + stck_lwpr: '87100', + acml_vol: '17813848', + acml_tr_pbmn: '1560911761680', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '3', + prdy_vrss: '0', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20240709', + stck_clpr: '87800', + stck_oprc: '87800', + stck_hgpr: '88200', + stck_lwpr: '86900', + acml_vol: '21336200', + acml_tr_pbmn: '1869414925283', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '2', + prdy_vrss: '400', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20240708', + stck_clpr: '87400', + stck_oprc: '87900', + stck_hgpr: '88600', + stck_lwpr: '86900', + acml_vol: '24035808', + acml_tr_pbmn: '2105162327800', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '2', + prdy_vrss: '300', + revl_issu_reas: '', + }, + { + stck_bsop_date: '20240705', + stck_clpr: '87100', + stck_oprc: '85600', + stck_hgpr: '87100', + stck_lwpr: '85200', + acml_vol: '45791192', + acml_tr_pbmn: '3951280237696', + flng_cls_code: '00', + prtt_rate: '0.00', + mod_yn: 'N', + prdy_vrss_sign: '2', + prdy_vrss: '2500', + revl_issu_reas: '', + }, + ], + rt_cd: '0', + msg_cd: 'MCA00000', + msg1: '정상처리 되었습니다.', +}; diff --git a/BE/src/stock/detail/mockdata/stock-detail.mockdata.ts b/BE/src/stock/detail/mockdata/stock-detail.mockdata.ts new file mode 100644 index 00000000..3da82286 --- /dev/null +++ b/BE/src/stock/detail/mockdata/stock-detail.mockdata.ts @@ -0,0 +1,86 @@ +export const STOCK_DETAIL_MOCK = { + output: { + iscd_stat_cls_code: '55', + marg_rate: '20.00', + rprs_mrkt_kor_name: 'KOSPI200', + bstp_kor_isnm: '전기.전자', + temp_stop_yn: 'N', + oprc_rang_cont_yn: 'N', + clpr_rang_cont_yn: 'N', + crdt_able_yn: 'Y', + grmn_rate_cls_code: '40', + elw_pblc_yn: 'Y', + stck_prpr: '53600', + prdy_vrss: '-600', + prdy_vrss_sign: '5', + prdy_ctrt: '-1.11', + acml_tr_pbmn: '1184225188140', + acml_vol: '22044867', + prdy_vrss_vol_rate: '89.93', + stck_oprc: '54300', + stck_hgpr: '54400', + stck_lwpr: '53100', + stck_mxpr: '70400', + stck_llam: '38000', + stck_sdpr: '54200', + wghn_avrg_stck_prc: '53721.32', + hts_frgn_ehrt: '51.30', + frgn_ntby_qty: '-3321712', + pgtr_ntby_qty: '-2256632', + pvt_scnd_dmrs_prc: '55933', + pvt_frst_dmrs_prc: '55066', + pvt_pont_val: '54433', + pvt_frst_dmsp_prc: '53566', + pvt_scnd_dmsp_prc: '52933', + dmrs_val: '54750', + dmsp_val: '53250', + cpfn: '7780', + rstc_wdth_prc: '16200', + stck_fcam: '100', + stck_sspr: '42270', + aspr_unit: '100', + hts_deal_qty_unit_val: '1', + lstn_stcn: '5969782550', + hts_avls: '3199803', + per: '25.15', + pbr: '1.03', + stac_month: '12', + vol_tnrt: '0.37', + eps: '2131.00', + bps: '52002.00', + d250_hgpr: '88800', + d250_hgpr_date: '20240711', + d250_hgpr_vrss_prpr_rate: '-39.64', + d250_lwpr: '49900', + d250_lwpr_date: '20241114', + d250_lwpr_vrss_prpr_rate: '7.41', + stck_dryy_hgpr: '88800', + dryy_hgpr_vrss_prpr_rate: '-39.64', + dryy_hgpr_date: '20240711', + stck_dryy_lwpr: '49900', + dryy_lwpr_vrss_prpr_rate: '7.41', + dryy_lwpr_date: '20241114', + w52_hgpr: '88800', + w52_hgpr_vrss_prpr_ctrt: '-39.64', + w52_hgpr_date: '20240711', + w52_lwpr: '49900', + w52_lwpr_vrss_prpr_ctrt: '7.41', + w52_lwpr_date: '20241114', + whol_loan_rmnd_rate: '0.26', + ssts_yn: 'N', + stck_shrn_iscd: '005930', + fcam_cnnm: '100', + cpfn_cnnm: '7,780 억', + frgn_hldn_qty: '3062328623', + vi_cls_code: 'N', + ovtm_vi_cls_code: 'N', + last_ssts_cntg_qty: '50856', + invt_caful_yn: 'N', + mrkt_warn_cls_code: '00', + short_over_yn: 'N', + sltr_yn: 'N', + }, + rt_cd: '0', + msg_cd: 'MCA00000', + msg1: '정상처리 되었습니다.', +}; diff --git a/BE/src/stock/detail/stock-detail.service.spec.ts b/BE/src/stock/detail/stock-detail.service.spec.ts new file mode 100644 index 00000000..cf8d5b1c --- /dev/null +++ b/BE/src/stock/detail/stock-detail.service.spec.ts @@ -0,0 +1,100 @@ +import { Test } from '@nestjs/testing'; +import { KoreaInvestmentDomainService } from '../../common/koreaInvestment/korea-investment.domain-service'; +import { StockDetailService } from './stock-detail.service'; +import { StockDetailRepository } from './stock-detail.repository'; +import { STOCK_DETAIL_MOCK } from './mockdata/stock-detail.mockdata'; +import { STOCK_DETAIL_CHART_MOCK } from './mockdata/stock-detail-chart.mockdata'; +import { Stocks } from './stock-detail.entity'; + +describe('stock detail test', () => { + let stockDetailService: StockDetailService; + let koreaInvestmentDomainService: KoreaInvestmentDomainService; + let stockDetailRepository: StockDetailRepository; + + beforeEach(async () => { + const module = await Test.createTestingModule({ + providers: [ + StockDetailService, + KoreaInvestmentDomainService, + { + provide: StockDetailRepository, + useValue: { + findOneByCode: jest.fn(), + }, + }, + ], + }).compile(); + + stockDetailService = module.get(StockDetailService); + koreaInvestmentDomainService = module.get(KoreaInvestmentDomainService); + stockDetailRepository = module.get(StockDetailRepository); + + jest + .spyOn(koreaInvestmentDomainService, 'getAccessToken') + .mockResolvedValue('accessToken'); + }); + + it('특정 주식의 현재가 체결 데이터를 반환한다.', async () => { + jest + .spyOn(koreaInvestmentDomainService, 'requestApi') + .mockResolvedValueOnce(STOCK_DETAIL_MOCK); + + jest + .spyOn(stockDetailRepository, 'findOneByCode') + .mockImplementation((code: string) => { + const stock = new Stocks(); + stock.code = code; + stock.name = '삼성전자'; + stock.market = 'KOSPI'; + + return Promise.resolve(stock); + }); + + const response = await stockDetailService.getInquirePrice('005930'); + + const expected = { + hts_kor_isnm: '삼성전자', + stck_shrn_iscd: '005930', + stck_prpr: '53600', + prdy_vrss: '-600', + prdy_vrss_sign: '5', + prdy_ctrt: '-1.11', + hts_avls: '3199803', + per: '25.15', + stck_mxpr: '70400', + stck_llam: '38000', + is_bookmarked: false, + }; + + expect(response).toEqual(expected); + }); + + it('특정 주식의 차트 데이터를 반환한다.', async () => { + jest + .spyOn(koreaInvestmentDomainService, 'requestApi') + .mockResolvedValueOnce(STOCK_DETAIL_CHART_MOCK); + + const response = await stockDetailService.getInquirePriceChart( + '005930', + 'D', + 30, + ); + + const expected = { + stck_bsop_date: '20241022', + stck_clpr: '57700', + stck_oprc: '58800', + stck_hgpr: '58900', + stck_lwpr: '57700', + acml_vol: '27582528', + prdy_vrss_sign: '5', + mov_avg_5: '59020.00', + mov_avg_20: '60985.00', + }; + + expect(response[0]).toEqual(expected); + expect(response[0].stck_bsop_date).toEqual('20241022'); + expect(response[1].stck_bsop_date).toEqual('20241023'); + expect(response[2].stck_bsop_date).toEqual('20241024'); + }); +}); diff --git a/BE/test/stock/index/mockdata/stock.index.list.mockdata.ts b/BE/src/stock/index/mockdata/stock-index-list.mockdata.ts similarity index 100% rename from BE/test/stock/index/mockdata/stock.index.list.mockdata.ts rename to BE/src/stock/index/mockdata/stock-index-list.mockdata.ts diff --git a/BE/test/stock/index/mockdata/stock.index.value.mockdata.ts b/BE/src/stock/index/mockdata/stock-index-value.mockdata.ts similarity index 100% rename from BE/test/stock/index/mockdata/stock.index.value.mockdata.ts rename to BE/src/stock/index/mockdata/stock-index-value.mockdata.ts diff --git a/BE/src/stock/index/stock-index.service.spec.ts b/BE/src/stock/index/stock-index.service.spec.ts new file mode 100644 index 00000000..091977e3 --- /dev/null +++ b/BE/src/stock/index/stock-index.service.spec.ts @@ -0,0 +1,86 @@ +import { Test } from '@nestjs/testing'; +import axios from 'axios'; +import { InternalServerErrorException } from '@nestjs/common'; +import { StockIndexService } from './stock-index.service'; +import { STOCK_INDEX_LIST_MOCK } from './mockdata/stock-index-list.mockdata'; +import { STOCK_INDEX_VALUE_MOCK } from './mockdata/stock-index-value.mockdata'; +import { SocketGateway } from '../../common/websocket/socket.gateway'; +import { KoreaInvestmentDomainService } from '../../common/koreaInvestment/korea-investment.domain-service'; +import { StockIndexListChartElementDto } from './dto/stock-index-list-chart.element.dto'; +import { StockIndexValueElementDto } from './dto/stock-index-value-element.dto'; +import { StockIndexResponseElementDto } from './dto/stock-index-response-element.dto'; +import { StockIndexResponseDto } from './dto/stock-index-response.dto'; + +jest.mock('axios'); + +describe('stock index list test', () => { + let stockIndexService: StockIndexService; + let koreaInvestmentDomainService: KoreaInvestmentDomainService; + + beforeEach(async () => { + const module = await Test.createTestingModule({ + providers: [ + StockIndexService, + SocketGateway, + KoreaInvestmentDomainService, + ], + }).compile(); + + stockIndexService = module.get(StockIndexService); + koreaInvestmentDomainService = module.get(KoreaInvestmentDomainService); + + jest + .spyOn(koreaInvestmentDomainService, 'getAccessToken') + .mockResolvedValue('accessToken'); + }); + + it('주가 지수 차트 조회 API에서 정상적인 데이터를 조회한 경우, 형식에 맞춰 정상적으로 반환한다.', async () => { + (axios.get as jest.Mock).mockImplementation((url: string) => { + if (url.includes('inquire-index-timeprice')) + return STOCK_INDEX_LIST_MOCK.VALID_DATA; + if (url.includes('inquire-index-price')) + return STOCK_INDEX_VALUE_MOCK.VALID_DATA; + return new Error(); + }); + + const stockIndexListValueElementDto = new StockIndexValueElementDto( + STOCK_INDEX_VALUE_MOCK.VALID_DATA.data.output.bstp_nmix_prpr, + STOCK_INDEX_VALUE_MOCK.VALID_DATA.data.output.bstp_nmix_prdy_vrss, + STOCK_INDEX_VALUE_MOCK.VALID_DATA.data.output.bstp_nmix_prdy_ctrt, + STOCK_INDEX_VALUE_MOCK.VALID_DATA.data.output.prdy_vrss_sign, + ); + const stockIndexListChartElementDto = new StockIndexListChartElementDto( + STOCK_INDEX_LIST_MOCK.VALID_DATA.data.output[0].bsop_hour, + STOCK_INDEX_LIST_MOCK.VALID_DATA.data.output[0].bstp_nmix_prpr, + STOCK_INDEX_LIST_MOCK.VALID_DATA.data.output[0].bstp_nmix_prdy_vrss, + ); + + const stockIndexResponseElementDto = new StockIndexResponseElementDto(); + stockIndexResponseElementDto.value = stockIndexListValueElementDto; + stockIndexResponseElementDto.chart = [stockIndexListChartElementDto]; + + const stockIndexResponseDto = new StockIndexResponseDto(); + stockIndexResponseDto.KOSPI = stockIndexResponseElementDto; + stockIndexResponseDto.KOSDAQ = stockIndexResponseElementDto; + stockIndexResponseDto.KOSPI200 = stockIndexResponseElementDto; + stockIndexResponseDto.KSQ150 = stockIndexResponseElementDto; + + expect(await stockIndexService.getDomesticStockIndexList()).toEqual( + stockIndexResponseDto, + ); + }); + + it('주가 지수 차트 조회 API에서 데이터를 조회하지 못한 경우, 에러를 발생시킨다.', async () => { + (axios.get as jest.Mock).mockImplementation((url: string) => { + if (url.includes('inquire-index-timeprice')) + return STOCK_INDEX_LIST_MOCK.INVALID_DATA; + if (url.includes('inquire-index-price')) + return STOCK_INDEX_VALUE_MOCK.INVALID_DATA; + return new Error(); + }); + + await expect(stockIndexService.getDomesticStockIndexList()).rejects.toThrow( + InternalServerErrorException, + ); + }); +}); diff --git a/BE/src/stock/index/stock-index.service.ts b/BE/src/stock/index/stock-index.service.ts index f68aa9d1..62dae6c1 100644 --- a/BE/src/stock/index/stock-index.service.ts +++ b/BE/src/stock/index/stock-index.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable, InternalServerErrorException } from '@nestjs/common'; import { StockIndexListChartElementDto } from './dto/stock-index-list-chart.element.dto'; import { StockIndexValueElementDto } from './dto/stock-index-value-element.dto'; import { @@ -87,6 +87,11 @@ export class StockIndexService { queryParams, ); + if (result.rt_cd !== '0') + throw new InternalServerErrorException( + '데이터를 정상적으로 조회하지 못했습니다.', + ); + return result.output.map((element) => { return new StockIndexListChartElementDto( element.bsop_hour, @@ -109,6 +114,11 @@ export class StockIndexService { queryParams, ); + if (result.rt_cd !== '0') + throw new InternalServerErrorException( + '데이터를 정상적으로 조회하지 못했습니다.', + ); + const data = result.output; return new StockIndexValueElementDto( diff --git a/BE/src/stock/order/stock-order.service.spec.ts b/BE/src/stock/order/stock-order.service.spec.ts new file mode 100644 index 00000000..78d0b2b6 --- /dev/null +++ b/BE/src/stock/order/stock-order.service.spec.ts @@ -0,0 +1,251 @@ +import { Test } from '@nestjs/testing'; +import { + BadRequestException, + ConflictException, + ForbiddenException, +} from '@nestjs/common'; +import { StockOrderService } from './stock-order.service'; +import { StockOrderRepository } from './stock-order.repository'; +import { StockPriceSocketService } from '../../stockSocket/stock-price-socket.service'; +import { UserStockRepository } from '../../asset/user-stock.repository'; +import { AssetRepository } from '../../asset/asset.repository'; +import { TradeType } from './enum/trade-type'; +import { StatusType } from './enum/status-type'; + +describe('stock order test', () => { + let stockOrderService: StockOrderService; + let stockOrderRepository: StockOrderRepository; + let assetRepository: AssetRepository; + let userStockRepository: UserStockRepository; + + beforeEach(async () => { + const mockStockOrderRepository = { + findBy: jest.fn(), + save: jest.fn(), + create: jest.fn(), + findOneBy: jest.fn(), + remove: jest.fn(), + existsBy: jest.fn(), + }; + const mockAssetRepository = { findOneBy: jest.fn() }; + const mockStockPriceSocketService = { + subscribeByCode: jest.fn(), + unsubscribeByCode: jest.fn(), + }; + const mockUserStockRepository = { findOneBy: jest.fn() }; + + const module = await Test.createTestingModule({ + providers: [ + StockOrderService, + { provide: StockOrderRepository, useValue: mockStockOrderRepository }, + { provide: AssetRepository, useValue: mockAssetRepository }, + { + provide: StockPriceSocketService, + useValue: mockStockPriceSocketService, + }, + { provide: UserStockRepository, useValue: mockUserStockRepository }, + ], + }).compile(); + + stockOrderService = module.get(StockOrderService); + stockOrderRepository = module.get(StockOrderRepository); + assetRepository = module.get(AssetRepository); + userStockRepository = module.get(UserStockRepository); + }); + + it('충분한 자산을 가지고 특정 주식에 대해 매수를 요청할 경우, 요청이 DB에 정상적으로 등록된다.', async () => { + jest.spyOn(assetRepository, 'findOneBy').mockResolvedValue({ + id: 1, + user_id: 1, + stock_balance: 0, + cash_balance: 1000, + total_asset: 1000, + total_profit: 0, + total_profit_rate: 0, + }); + + jest.spyOn(stockOrderRepository, 'findBy').mockResolvedValue([]); + + const createMock = jest.fn(); + jest.spyOn(stockOrderRepository, 'create').mockImplementation(createMock); + + const saveMock = jest.fn(); + jest.spyOn(stockOrderRepository, 'save').mockImplementation(saveMock); + + await stockOrderService.buy(1, { + stock_code: '005930', + price: 1000, + amount: 1, + }); + + expect(createMock).toHaveBeenCalledWith({ + user_id: 1, + stock_code: '005930', + trade_type: TradeType.BUY, + amount: 1, + price: 1000, + status: StatusType.PENDING, + }); + expect(saveMock).toHaveBeenCalled(); + }); + + it('자산이 부족한 상태로 특정 주식에 대해 매수를 요청할 경우, BadRequest 예외가 발생한다.', async () => { + jest.spyOn(assetRepository, 'findOneBy').mockResolvedValue({ + id: 1, + user_id: 1, + stock_balance: 0, + cash_balance: 1000, + total_asset: 1000, + total_profit: 0, + total_profit_rate: 0, + }); + + jest.spyOn(stockOrderRepository, 'findBy').mockResolvedValue([ + { + id: 1, + user_id: 1, + stock_code: '005930', + trade_type: TradeType.BUY, + amount: 1, + price: 1000, + status: StatusType.PENDING, + created_at: new Date(), + }, + ]); + + await expect( + stockOrderService.buy(1, { + stock_code: '005930', + price: 1000, + amount: 1, + }), + ).rejects.toThrow(BadRequestException); + }); + + it('충분한 주식을 가지고 특정 주식에 대해 매도를 요청할 경우, 요청이 DB에 정상적으로 등록된다.', async () => { + jest.spyOn(userStockRepository, 'findOneBy').mockResolvedValue({ + id: 1, + user_id: 1, + stock_code: '005930', + quantity: 1, + avg_price: 1000, + last_updated: new Date(), + }); + + jest.spyOn(stockOrderRepository, 'findBy').mockResolvedValue([]); + + const createMock = jest.fn(); + jest.spyOn(stockOrderRepository, 'create').mockImplementation(createMock); + + const saveMock = jest.fn(); + jest.spyOn(stockOrderRepository, 'save').mockImplementation(saveMock); + + await stockOrderService.sell(1, { + stock_code: '005930', + price: 1000, + amount: 1, + }); + + expect(createMock).toHaveBeenCalledWith({ + user_id: 1, + stock_code: '005930', + trade_type: TradeType.SELL, + amount: 1, + price: 1000, + status: StatusType.PENDING, + }); + expect(saveMock).toHaveBeenCalled(); + }); + + it('주식이 부족한 상태로 특정 주식에 대해 매도를 요청할 경우, BadRequest 예외가 발생한다.', async () => { + jest.spyOn(userStockRepository, 'findOneBy').mockResolvedValue({ + id: 1, + user_id: 1, + stock_code: '005930', + quantity: 1, + avg_price: 1000, + last_updated: new Date(), + }); + + jest.spyOn(stockOrderRepository, 'findBy').mockResolvedValue([ + { + id: 1, + user_id: 1, + stock_code: '005930', + trade_type: TradeType.SELL, + amount: 1, + price: 1000, + status: StatusType.PENDING, + created_at: new Date(), + }, + ]); + + await expect( + stockOrderService.sell(1, { + stock_code: '005930', + price: 1000, + amount: 1, + }), + ).rejects.toThrow(BadRequestException); + }); + + it('사용자 본인의 미체결된 주문에 대해 취소를 요청할 경우, 해당 주문이 DB에서 삭제된다.', async () => { + jest.spyOn(stockOrderRepository, 'findOneBy').mockResolvedValue({ + id: 1, + user_id: 1, + stock_code: '005930', + trade_type: TradeType.SELL, + amount: 1, + price: 1000, + status: StatusType.PENDING, + created_at: new Date(), + }); + + const removeMock = jest.fn(); + jest.spyOn(stockOrderRepository, 'remove').mockImplementation(removeMock); + + await stockOrderService.cancel(1, 1); + + expect(removeMock).toHaveBeenCalled(); + }); + + it('사용자 본인의 주문이 아닌 주문에 대해 취소를 요청할 경우, Forbidden 예외가 발생한다.', async () => { + jest.spyOn(stockOrderRepository, 'findOneBy').mockResolvedValue({ + id: 1, + user_id: 2, + stock_code: '005930', + trade_type: TradeType.SELL, + amount: 1, + price: 1000, + status: StatusType.PENDING, + created_at: new Date(), + }); + + const removeMock = jest.fn(); + jest.spyOn(stockOrderRepository, 'remove').mockImplementation(removeMock); + + await expect(stockOrderService.cancel(1, 1)).rejects.toThrow( + ForbiddenException, + ); + }); + + it('이미 체결된 주문에 대해 취소를 요청할 경우, Conflict 예외가 발생한다.', async () => { + jest.spyOn(stockOrderRepository, 'findOneBy').mockResolvedValue({ + id: 1, + user_id: 1, + stock_code: '005930', + trade_type: TradeType.SELL, + amount: 1, + price: 1000, + status: StatusType.COMPLETE, + created_at: new Date(), + }); + + const removeMock = jest.fn(); + jest.spyOn(stockOrderRepository, 'remove').mockImplementation(removeMock); + + await expect(stockOrderService.cancel(1, 1)).rejects.toThrow( + ConflictException, + ); + }); +}); diff --git a/BE/src/stock/order/stock-order.service.ts b/BE/src/stock/order/stock-order.service.ts index 73879bca..f1e146cb 100644 --- a/BE/src/stock/order/stock-order.service.ts +++ b/BE/src/stock/order/stock-order.service.ts @@ -54,7 +54,9 @@ export class StockOrderService { }); await this.stockOrderRepository.save(order); - this.stockPriceSocketService.subscribeByCode(stockOrderRequest.stock_code); + await this.stockPriceSocketService.subscribeByCode( + stockOrderRequest.stock_code, + ); } async sell(userId: number, stockOrderRequest: StockOrderRequestDto) { @@ -89,7 +91,9 @@ export class StockOrderService { }); await this.stockOrderRepository.save(order); - this.stockPriceSocketService.subscribeByCode(stockOrderRequest.stock_code); + await this.stockPriceSocketService.subscribeByCode( + stockOrderRequest.stock_code, + ); } async cancel(userId: number, orderId: number) { @@ -111,7 +115,7 @@ export class StockOrderService { status: StatusType.PENDING, })) ) - this.stockPriceSocketService.unsubscribeByCode(order.stock_code); + await this.stockPriceSocketService.unsubscribeByCode([order.stock_code]); } async getPendingListByUserId(userId: number) { @@ -135,10 +139,8 @@ export class StockOrderService { const orders: Order[] = await this.stockOrderRepository.findAllCodeByStatus(); - await Promise.all( - orders.map((order) => - this.stockPriceSocketService.unsubscribeByCode(order.stock_code), - ), + await this.stockPriceSocketService.unsubscribeByCode( + orders.map((order) => order.stock_code), ); await this.stockOrderRepository.delete({ status: StatusType.PENDING }); diff --git a/BE/src/stock/topfive/mockdata/stock-topfive-high.mockdata.ts b/BE/src/stock/topfive/mockdata/stock-topfive-high.mockdata.ts new file mode 100644 index 00000000..528c9cf7 --- /dev/null +++ b/BE/src/stock/topfive/mockdata/stock-topfive-high.mockdata.ts @@ -0,0 +1,787 @@ +export const STOCK_TOP_FIVE_HIGH_MOCK = { + output: [ + { + stck_shrn_iscd: '298000', + data_rank: '1', + hts_kor_isnm: '효성화학', + stck_prpr: '37500', + prdy_vrss: '8650', + prdy_vrss_sign: '1', + prdy_ctrt: '29.98', + acml_vol: '211979', + stck_hgpr: '37500', + hgpr_hour: '090336', + acml_hgpr_date: '20241202', + stck_lwpr: '29100', + lwpr_hour: '090018', + acml_lwpr_date: '20241202', + lwpr_vrss_prpr_rate: '28.87', + dsgt_date_clpr_vrss_prpr_rate: '29.98', + cnnt_ascn_dynu: '1', + hgpr_vrss_prpr_rate: '0.00', + cnnt_down_dynu: '1', + oprc_vrss_prpr_sign: '2', + oprc_vrss_prpr: '0', + oprc_vrss_prpr_rate: '0.00', + prd_rsfl: '0', + prd_rsfl_rate: '0.00', + }, + { + stck_shrn_iscd: '321370', + data_rank: '2', + hts_kor_isnm: '센서뷰', + stck_prpr: '1943', + prdy_vrss: '448', + prdy_vrss_sign: '1', + prdy_ctrt: '29.97', + acml_vol: '9466785', + stck_hgpr: '1943', + hgpr_hour: '110318', + acml_hgpr_date: '20241202', + stck_lwpr: '1420', + lwpr_hour: '093635', + acml_lwpr_date: '20241202', + lwpr_vrss_prpr_rate: '36.83', + dsgt_date_clpr_vrss_prpr_rate: '29.97', + cnnt_ascn_dynu: '1', + hgpr_vrss_prpr_rate: '0.00', + cnnt_down_dynu: '1', + oprc_vrss_prpr_sign: '2', + oprc_vrss_prpr: '0', + oprc_vrss_prpr_rate: '0.00', + prd_rsfl: '0', + prd_rsfl_rate: '0.00', + }, + { + stck_shrn_iscd: '002995', + data_rank: '3', + hts_kor_isnm: '금호건설우', + stck_prpr: '17480', + prdy_vrss: '4030', + prdy_vrss_sign: '1', + prdy_ctrt: '29.96', + acml_vol: '22768', + stck_hgpr: '17480', + hgpr_hour: '093009', + acml_hgpr_date: '20241202', + stck_lwpr: '16650', + lwpr_hour: '090013', + acml_lwpr_date: '20241202', + lwpr_vrss_prpr_rate: '4.98', + dsgt_date_clpr_vrss_prpr_rate: '29.96', + cnnt_ascn_dynu: '2', + hgpr_vrss_prpr_rate: '0.00', + cnnt_down_dynu: '0', + oprc_vrss_prpr_sign: '2', + oprc_vrss_prpr: '0', + oprc_vrss_prpr_rate: '0.00', + prd_rsfl: '0', + prd_rsfl_rate: '0.00', + }, + { + stck_shrn_iscd: '031860', + data_rank: '4', + hts_kor_isnm: '에스유홀딩스', + stck_prpr: '1211', + prdy_vrss: '279', + prdy_vrss_sign: '1', + prdy_ctrt: '29.94', + acml_vol: '1336451', + stck_hgpr: '1211', + hgpr_hour: '151828', + acml_hgpr_date: '20241202', + stck_lwpr: '932', + lwpr_hour: '090030', + acml_lwpr_date: '20241202', + lwpr_vrss_prpr_rate: '29.94', + dsgt_date_clpr_vrss_prpr_rate: '29.94', + cnnt_ascn_dynu: '5', + hgpr_vrss_prpr_rate: '0.00', + cnnt_down_dynu: '0', + oprc_vrss_prpr_sign: '2', + oprc_vrss_prpr: '0', + oprc_vrss_prpr_rate: '0.00', + prd_rsfl: '0', + prd_rsfl_rate: '0.00', + }, + { + stck_shrn_iscd: '005965', + data_rank: '5', + hts_kor_isnm: '동부건설우', + stck_prpr: '26100', + prdy_vrss: '6000', + prdy_vrss_sign: '1', + prdy_ctrt: '29.85', + acml_vol: '35862', + stck_hgpr: '26100', + hgpr_hour: '150000', + acml_hgpr_date: '20241202', + stck_lwpr: '20500', + lwpr_hour: '090030', + acml_lwpr_date: '20241202', + lwpr_vrss_prpr_rate: '27.32', + dsgt_date_clpr_vrss_prpr_rate: '29.85', + cnnt_ascn_dynu: '2', + hgpr_vrss_prpr_rate: '0.00', + cnnt_down_dynu: '0', + oprc_vrss_prpr_sign: '2', + oprc_vrss_prpr: '0', + oprc_vrss_prpr_rate: '0.00', + prd_rsfl: '0', + prd_rsfl_rate: '0.00', + }, + { + stck_shrn_iscd: '223310', + data_rank: '6', + hts_kor_isnm: '딥마인드', + stck_prpr: '2635', + prdy_vrss: '605', + prdy_vrss_sign: '1', + prdy_ctrt: '29.80', + acml_vol: '1476038', + stck_hgpr: '2635', + hgpr_hour: '102351', + acml_hgpr_date: '20241202', + stck_lwpr: '2005', + lwpr_hour: '091222', + acml_lwpr_date: '20241202', + lwpr_vrss_prpr_rate: '31.42', + dsgt_date_clpr_vrss_prpr_rate: '29.80', + cnnt_ascn_dynu: '1', + hgpr_vrss_prpr_rate: '0.00', + cnnt_down_dynu: '0', + oprc_vrss_prpr_sign: '2', + oprc_vrss_prpr: '0', + oprc_vrss_prpr_rate: '0.00', + prd_rsfl: '0', + prd_rsfl_rate: '0.00', + }, + { + stck_shrn_iscd: '396470', + data_rank: '7', + hts_kor_isnm: '워트', + stck_prpr: '8230', + prdy_vrss: '1580', + prdy_vrss_sign: '2', + prdy_ctrt: '23.76', + acml_vol: '6083212', + stck_hgpr: '8640', + hgpr_hour: '144834', + acml_hgpr_date: '20241202', + stck_lwpr: '6470', + lwpr_hour: '125727', + acml_lwpr_date: '20241202', + lwpr_vrss_prpr_rate: '27.20', + dsgt_date_clpr_vrss_prpr_rate: '23.76', + cnnt_ascn_dynu: '1', + hgpr_vrss_prpr_rate: '-4.75', + cnnt_down_dynu: '4', + oprc_vrss_prpr_sign: '2', + oprc_vrss_prpr: '0', + oprc_vrss_prpr_rate: '0.00', + prd_rsfl: '0', + prd_rsfl_rate: '0.00', + }, + { + stck_shrn_iscd: '466410', + data_rank: '8', + hts_kor_isnm: '사이냅소프트', + stck_prpr: '22950', + prdy_vrss: '4170', + prdy_vrss_sign: '2', + prdy_ctrt: '22.20', + acml_vol: '3836463', + stck_hgpr: '24400', + hgpr_hour: '135233', + acml_hgpr_date: '20241202', + stck_lwpr: '18300', + lwpr_hour: '091709', + acml_lwpr_date: '20241202', + lwpr_vrss_prpr_rate: '25.41', + dsgt_date_clpr_vrss_prpr_rate: '22.20', + cnnt_ascn_dynu: '1', + hgpr_vrss_prpr_rate: '-5.94', + cnnt_down_dynu: '1', + oprc_vrss_prpr_sign: '2', + oprc_vrss_prpr: '0', + oprc_vrss_prpr_rate: '0.00', + prd_rsfl: '0', + prd_rsfl_rate: '0.00', + }, + { + stck_shrn_iscd: '008830', + data_rank: '9', + hts_kor_isnm: '대동기어', + stck_prpr: '13630', + prdy_vrss: '2420', + prdy_vrss_sign: '2', + prdy_ctrt: '21.59', + acml_vol: '10130076', + stck_hgpr: '14270', + hgpr_hour: '122758', + acml_hgpr_date: '20241202', + stck_lwpr: '11250', + lwpr_hour: '090031', + acml_lwpr_date: '20241202', + lwpr_vrss_prpr_rate: '21.16', + dsgt_date_clpr_vrss_prpr_rate: '21.59', + cnnt_ascn_dynu: '1', + hgpr_vrss_prpr_rate: '-4.48', + cnnt_down_dynu: '2', + oprc_vrss_prpr_sign: '2', + oprc_vrss_prpr: '0', + oprc_vrss_prpr_rate: '0.00', + prd_rsfl: '0', + prd_rsfl_rate: '0.00', + }, + { + stck_shrn_iscd: '010130', + data_rank: '10', + hts_kor_isnm: '고려아연', + stck_prpr: '1411000', + prdy_vrss: '231000', + prdy_vrss_sign: '2', + prdy_ctrt: '19.58', + acml_vol: '133354', + stck_hgpr: '1534000', + hgpr_hour: '094011', + acml_hgpr_date: '20241202', + stck_lwpr: '1228000', + lwpr_hour: '090005', + acml_lwpr_date: '20241202', + lwpr_vrss_prpr_rate: '14.90', + dsgt_date_clpr_vrss_prpr_rate: '19.58', + cnnt_ascn_dynu: '5', + hgpr_vrss_prpr_rate: '-8.02', + cnnt_down_dynu: '0', + oprc_vrss_prpr_sign: '2', + oprc_vrss_prpr: '0', + oprc_vrss_prpr_rate: '0.00', + prd_rsfl: '0', + prd_rsfl_rate: '0.00', + }, + { + stck_shrn_iscd: '370090', + data_rank: '11', + hts_kor_isnm: '퓨런티어', + stck_prpr: '27800', + prdy_vrss: '3950', + prdy_vrss_sign: '2', + prdy_ctrt: '16.56', + acml_vol: '2128218', + stck_hgpr: '28050', + hgpr_hour: '151930', + acml_hgpr_date: '20241202', + stck_lwpr: '24550', + lwpr_hour: '090044', + acml_lwpr_date: '20241202', + lwpr_vrss_prpr_rate: '13.24', + dsgt_date_clpr_vrss_prpr_rate: '16.56', + cnnt_ascn_dynu: '1', + hgpr_vrss_prpr_rate: '-0.89', + cnnt_down_dynu: '4', + oprc_vrss_prpr_sign: '2', + oprc_vrss_prpr: '0', + oprc_vrss_prpr_rate: '0.00', + prd_rsfl: '0', + prd_rsfl_rate: '0.00', + }, + { + stck_shrn_iscd: '356680', + data_rank: '12', + hts_kor_isnm: '엑스게이트', + stck_prpr: '6060', + prdy_vrss: '780', + prdy_vrss_sign: '2', + prdy_ctrt: '14.77', + acml_vol: '16267816', + stck_hgpr: '6200', + hgpr_hour: '150044', + acml_hgpr_date: '20241202', + stck_lwpr: '5420', + lwpr_hour: '090935', + acml_lwpr_date: '20241202', + lwpr_vrss_prpr_rate: '11.81', + dsgt_date_clpr_vrss_prpr_rate: '14.77', + cnnt_ascn_dynu: '3', + hgpr_vrss_prpr_rate: '-2.26', + cnnt_down_dynu: '0', + oprc_vrss_prpr_sign: '2', + oprc_vrss_prpr: '0', + oprc_vrss_prpr_rate: '0.00', + prd_rsfl: '0', + prd_rsfl_rate: '0.00', + }, + { + stck_shrn_iscd: '036560', + data_rank: '13', + hts_kor_isnm: '영풍정밀', + stck_prpr: '16160', + prdy_vrss: '2010', + prdy_vrss_sign: '2', + prdy_ctrt: '14.20', + acml_vol: '1843700', + stck_hgpr: '18390', + hgpr_hour: '093626', + acml_hgpr_date: '20241202', + stck_lwpr: '14210', + lwpr_hour: '090030', + acml_lwpr_date: '20241202', + lwpr_vrss_prpr_rate: '13.72', + dsgt_date_clpr_vrss_prpr_rate: '14.20', + cnnt_ascn_dynu: '1', + hgpr_vrss_prpr_rate: '-12.13', + cnnt_down_dynu: '1', + oprc_vrss_prpr_sign: '2', + oprc_vrss_prpr: '0', + oprc_vrss_prpr_rate: '0.00', + prd_rsfl: '0', + prd_rsfl_rate: '0.00', + }, + { + stck_shrn_iscd: '437730', + data_rank: '14', + hts_kor_isnm: '삼현', + stck_prpr: '7800', + prdy_vrss: '910', + prdy_vrss_sign: '2', + prdy_ctrt: '13.21', + acml_vol: '11552979', + stck_hgpr: '8700', + hgpr_hour: '114723', + acml_hgpr_date: '20241202', + stck_lwpr: '6810', + lwpr_hour: '090040', + acml_lwpr_date: '20241202', + lwpr_vrss_prpr_rate: '14.54', + dsgt_date_clpr_vrss_prpr_rate: '13.21', + cnnt_ascn_dynu: '2', + hgpr_vrss_prpr_rate: '-10.34', + cnnt_down_dynu: '0', + oprc_vrss_prpr_sign: '2', + oprc_vrss_prpr: '0', + oprc_vrss_prpr_rate: '0.00', + prd_rsfl: '0', + prd_rsfl_rate: '0.00', + }, + { + stck_shrn_iscd: '065350', + data_rank: '15', + hts_kor_isnm: '신성델타테크', + stck_prpr: '73900', + prdy_vrss: '8500', + prdy_vrss_sign: '2', + prdy_ctrt: '13.00', + acml_vol: '1223714', + stck_hgpr: '75000', + hgpr_hour: '092127', + acml_hgpr_date: '20241202', + stck_lwpr: '68300', + lwpr_hour: '090032', + acml_lwpr_date: '20241202', + lwpr_vrss_prpr_rate: '8.20', + dsgt_date_clpr_vrss_prpr_rate: '13.00', + cnnt_ascn_dynu: '1', + hgpr_vrss_prpr_rate: '-1.47', + cnnt_down_dynu: '1', + oprc_vrss_prpr_sign: '2', + oprc_vrss_prpr: '0', + oprc_vrss_prpr_rate: '0.00', + prd_rsfl: '0', + prd_rsfl_rate: '0.00', + }, + { + stck_shrn_iscd: '033320', + data_rank: '16', + hts_kor_isnm: '제이씨현시스템', + stck_prpr: '5690', + prdy_vrss: '640', + prdy_vrss_sign: '2', + prdy_ctrt: '12.67', + acml_vol: '28376225', + stck_hgpr: '6170', + hgpr_hour: '094952', + acml_hgpr_date: '20241202', + stck_lwpr: '5050', + lwpr_hour: '090047', + acml_lwpr_date: '20241202', + lwpr_vrss_prpr_rate: '12.67', + dsgt_date_clpr_vrss_prpr_rate: '12.67', + cnnt_ascn_dynu: '6', + hgpr_vrss_prpr_rate: '-7.78', + cnnt_down_dynu: '0', + oprc_vrss_prpr_sign: '2', + oprc_vrss_prpr: '0', + oprc_vrss_prpr_rate: '0.00', + prd_rsfl: '0', + prd_rsfl_rate: '0.00', + }, + { + stck_shrn_iscd: '405920', + data_rank: '17', + hts_kor_isnm: '나라셀라', + stck_prpr: '3390', + prdy_vrss: '380', + prdy_vrss_sign: '2', + prdy_ctrt: '12.62', + acml_vol: '2823348', + stck_hgpr: '3830', + hgpr_hour: '143826', + acml_hgpr_date: '20241202', + stck_lwpr: '3005', + lwpr_hour: '090133', + acml_lwpr_date: '20241202', + lwpr_vrss_prpr_rate: '12.81', + dsgt_date_clpr_vrss_prpr_rate: '12.62', + cnnt_ascn_dynu: '1', + hgpr_vrss_prpr_rate: '-11.49', + cnnt_down_dynu: '2', + oprc_vrss_prpr_sign: '2', + oprc_vrss_prpr: '0', + oprc_vrss_prpr_rate: '0.00', + prd_rsfl: '0', + prd_rsfl_rate: '0.00', + }, + { + stck_shrn_iscd: '348370', + data_rank: '18', + hts_kor_isnm: '엔켐', + stck_prpr: '143000', + prdy_vrss: '15800', + prdy_vrss_sign: '2', + prdy_ctrt: '12.42', + acml_vol: '683974', + stck_hgpr: '152100', + hgpr_hour: '091426', + acml_hgpr_date: '20241202', + stck_lwpr: '130400', + lwpr_hour: '090030', + acml_lwpr_date: '20241202', + lwpr_vrss_prpr_rate: '9.66', + dsgt_date_clpr_vrss_prpr_rate: '12.42', + cnnt_ascn_dynu: '1', + hgpr_vrss_prpr_rate: '-5.98', + cnnt_down_dynu: '4', + oprc_vrss_prpr_sign: '2', + oprc_vrss_prpr: '0', + oprc_vrss_prpr_rate: '0.00', + prd_rsfl: '0', + prd_rsfl_rate: '0.00', + }, + { + stck_shrn_iscd: '950160', + data_rank: '19', + hts_kor_isnm: '코오롱티슈진', + stck_prpr: '19700', + prdy_vrss: '2150', + prdy_vrss_sign: '2', + prdy_ctrt: '12.25', + acml_vol: '2612892', + stck_hgpr: '22400', + hgpr_hour: '091857', + acml_hgpr_date: '20241202', + stck_lwpr: '18320', + lwpr_hour: '090147', + acml_lwpr_date: '20241202', + lwpr_vrss_prpr_rate: '7.53', + dsgt_date_clpr_vrss_prpr_rate: '12.25', + cnnt_ascn_dynu: '6', + hgpr_vrss_prpr_rate: '-12.05', + cnnt_down_dynu: '0', + oprc_vrss_prpr_sign: '2', + oprc_vrss_prpr: '0', + oprc_vrss_prpr_rate: '0.00', + prd_rsfl: '0', + prd_rsfl_rate: '0.00', + }, + { + stck_shrn_iscd: '234920', + data_rank: '20', + hts_kor_isnm: '자이글', + stck_prpr: '6100', + prdy_vrss: '660', + prdy_vrss_sign: '2', + prdy_ctrt: '12.13', + acml_vol: '2724461', + stck_hgpr: '6600', + hgpr_hour: '090643', + acml_hgpr_date: '20241202', + stck_lwpr: '5550', + lwpr_hour: '090029', + acml_lwpr_date: '20241202', + lwpr_vrss_prpr_rate: '9.91', + dsgt_date_clpr_vrss_prpr_rate: '12.13', + cnnt_ascn_dynu: '2', + hgpr_vrss_prpr_rate: '-7.58', + cnnt_down_dynu: '0', + oprc_vrss_prpr_sign: '2', + oprc_vrss_prpr: '0', + oprc_vrss_prpr_rate: '0.00', + prd_rsfl: '0', + prd_rsfl_rate: '0.00', + }, + { + stck_shrn_iscd: '032980', + data_rank: '21', + hts_kor_isnm: '바이온', + stck_prpr: '921', + prdy_vrss: '91', + prdy_vrss_sign: '2', + prdy_ctrt: '10.96', + acml_vol: '1860949', + stck_hgpr: '977', + hgpr_hour: '092052', + acml_hgpr_date: '20241202', + stck_lwpr: '835', + lwpr_hour: '090025', + acml_lwpr_date: '20241202', + lwpr_vrss_prpr_rate: '10.30', + dsgt_date_clpr_vrss_prpr_rate: '10.96', + cnnt_ascn_dynu: '2', + hgpr_vrss_prpr_rate: '-5.73', + cnnt_down_dynu: '0', + oprc_vrss_prpr_sign: '2', + oprc_vrss_prpr: '0', + oprc_vrss_prpr_rate: '0.00', + prd_rsfl: '0', + prd_rsfl_rate: '0.00', + }, + { + stck_shrn_iscd: '047310', + data_rank: '22', + hts_kor_isnm: '파워로직스', + stck_prpr: '5290', + prdy_vrss: '490', + prdy_vrss_sign: '2', + prdy_ctrt: '10.21', + acml_vol: '2266972', + stck_hgpr: '5500', + hgpr_hour: '101137', + acml_hgpr_date: '20241202', + stck_lwpr: '4855', + lwpr_hour: '090346', + acml_lwpr_date: '20241202', + lwpr_vrss_prpr_rate: '8.96', + dsgt_date_clpr_vrss_prpr_rate: '10.21', + cnnt_ascn_dynu: '1', + hgpr_vrss_prpr_rate: '-3.82', + cnnt_down_dynu: '3', + oprc_vrss_prpr_sign: '2', + oprc_vrss_prpr: '0', + oprc_vrss_prpr_rate: '0.00', + prd_rsfl: '0', + prd_rsfl_rate: '0.00', + }, + { + stck_shrn_iscd: '013720', + data_rank: '23', + hts_kor_isnm: 'CBI', + stck_prpr: '952', + prdy_vrss: '88', + prdy_vrss_sign: '2', + prdy_ctrt: '10.19', + acml_vol: '7202688', + stck_hgpr: '1115', + hgpr_hour: '091056', + acml_hgpr_date: '20241202', + stck_lwpr: '865', + lwpr_hour: '090047', + acml_lwpr_date: '20241202', + lwpr_vrss_prpr_rate: '10.06', + dsgt_date_clpr_vrss_prpr_rate: '10.19', + cnnt_ascn_dynu: '2', + hgpr_vrss_prpr_rate: '-14.62', + cnnt_down_dynu: '0', + oprc_vrss_prpr_sign: '2', + oprc_vrss_prpr: '0', + oprc_vrss_prpr_rate: '0.00', + prd_rsfl: '0', + prd_rsfl_rate: '0.00', + }, + { + stck_shrn_iscd: '257370', + data_rank: '24', + hts_kor_isnm: '피엔티엠에스', + stck_prpr: '4290', + prdy_vrss: '395', + prdy_vrss_sign: '2', + prdy_ctrt: '10.14', + acml_vol: '1093889', + stck_hgpr: '5060', + hgpr_hour: '091606', + acml_hgpr_date: '20241202', + stck_lwpr: '3900', + lwpr_hour: '090016', + acml_lwpr_date: '20241202', + lwpr_vrss_prpr_rate: '10.00', + dsgt_date_clpr_vrss_prpr_rate: '10.14', + cnnt_ascn_dynu: '1', + hgpr_vrss_prpr_rate: '-15.22', + cnnt_down_dynu: '1', + oprc_vrss_prpr_sign: '2', + oprc_vrss_prpr: '0', + oprc_vrss_prpr_rate: '0.00', + prd_rsfl: '0', + prd_rsfl_rate: '0.00', + }, + { + stck_shrn_iscd: '473980', + data_rank: '25', + hts_kor_isnm: '노머스', + stck_prpr: '21950', + prdy_vrss: '2000', + prdy_vrss_sign: '2', + prdy_ctrt: '10.03', + acml_vol: '1879247', + stck_hgpr: '24350', + hgpr_hour: '101950', + acml_hgpr_date: '20241202', + stck_lwpr: '20400', + lwpr_hour: '090014', + acml_lwpr_date: '20241202', + lwpr_vrss_prpr_rate: '7.60', + dsgt_date_clpr_vrss_prpr_rate: '10.03', + cnnt_ascn_dynu: '4', + hgpr_vrss_prpr_rate: '-9.86', + cnnt_down_dynu: '0', + oprc_vrss_prpr_sign: '2', + oprc_vrss_prpr: '0', + oprc_vrss_prpr_rate: '0.00', + prd_rsfl: '0', + prd_rsfl_rate: '0.00', + }, + { + stck_shrn_iscd: '464080', + data_rank: '26', + hts_kor_isnm: '에스오에스랩', + stck_prpr: '10250', + prdy_vrss: '880', + prdy_vrss_sign: '2', + prdy_ctrt: '9.39', + acml_vol: '9148097', + stck_hgpr: '10250', + hgpr_hour: '153001', + acml_hgpr_date: '20241202', + stck_lwpr: '9370', + lwpr_hour: '123437', + acml_lwpr_date: '20241202', + lwpr_vrss_prpr_rate: '9.39', + dsgt_date_clpr_vrss_prpr_rate: '9.39', + cnnt_ascn_dynu: '1', + hgpr_vrss_prpr_rate: '0.00', + cnnt_down_dynu: '1', + oprc_vrss_prpr_sign: '2', + oprc_vrss_prpr: '0', + oprc_vrss_prpr_rate: '0.00', + prd_rsfl: '0', + prd_rsfl_rate: '0.00', + }, + { + stck_shrn_iscd: '006660', + data_rank: '27', + hts_kor_isnm: '삼성공조', + stck_prpr: '9680', + prdy_vrss: '800', + prdy_vrss_sign: '2', + prdy_ctrt: '9.01', + acml_vol: '2626725', + stck_hgpr: '10760', + hgpr_hour: '143402', + acml_hgpr_date: '20241202', + stck_lwpr: '8850', + lwpr_hour: '123503', + acml_lwpr_date: '20241202', + lwpr_vrss_prpr_rate: '9.38', + dsgt_date_clpr_vrss_prpr_rate: '9.01', + cnnt_ascn_dynu: '1', + hgpr_vrss_prpr_rate: '-10.04', + cnnt_down_dynu: '1', + oprc_vrss_prpr_sign: '2', + oprc_vrss_prpr: '0', + oprc_vrss_prpr_rate: '0.00', + prd_rsfl: '0', + prd_rsfl_rate: '0.00', + }, + { + stck_shrn_iscd: '025950', + data_rank: '28', + hts_kor_isnm: '동신건설', + stck_prpr: '20950', + prdy_vrss: '1730', + prdy_vrss_sign: '2', + prdy_ctrt: '9.00', + acml_vol: '322771', + stck_hgpr: '21600', + hgpr_hour: '093133', + acml_hgpr_date: '20241202', + stck_lwpr: '19180', + lwpr_hour: '090021', + acml_lwpr_date: '20241202', + lwpr_vrss_prpr_rate: '9.23', + dsgt_date_clpr_vrss_prpr_rate: '9.00', + cnnt_ascn_dynu: '3', + hgpr_vrss_prpr_rate: '-3.01', + cnnt_down_dynu: '0', + oprc_vrss_prpr_sign: '2', + oprc_vrss_prpr: '0', + oprc_vrss_prpr_rate: '0.00', + prd_rsfl: '0', + prd_rsfl_rate: '0.00', + }, + { + stck_shrn_iscd: '001080', + data_rank: '29', + hts_kor_isnm: '만호제강', + stck_prpr: '35900', + prdy_vrss: '2950', + prdy_vrss_sign: '2', + prdy_ctrt: '8.95', + acml_vol: '15822', + stck_hgpr: '36150', + hgpr_hour: '145325', + acml_hgpr_date: '20241202', + stck_lwpr: '32350', + lwpr_hour: '090640', + acml_lwpr_date: '20241202', + lwpr_vrss_prpr_rate: '10.97', + dsgt_date_clpr_vrss_prpr_rate: '8.95', + cnnt_ascn_dynu: '3', + hgpr_vrss_prpr_rate: '-0.69', + cnnt_down_dynu: '0', + oprc_vrss_prpr_sign: '2', + oprc_vrss_prpr: '0', + oprc_vrss_prpr_rate: '0.00', + prd_rsfl: '0', + prd_rsfl_rate: '0.00', + }, + { + stck_shrn_iscd: 'Q510029', + data_rank: '30', + hts_kor_isnm: '대신 S&P 인버스 2X 천연가스 선물 ETN', + stck_prpr: '68200', + prdy_vrss: '5445', + prdy_vrss_sign: '2', + prdy_ctrt: '8.68', + acml_vol: '649', + stck_hgpr: '68565', + hgpr_hour: '150753', + acml_hgpr_date: '20241202', + stck_lwpr: '66050', + lwpr_hour: '091445', + acml_lwpr_date: '20241202', + lwpr_vrss_prpr_rate: '3.26', + dsgt_date_clpr_vrss_prpr_rate: '8.68', + cnnt_ascn_dynu: '1', + hgpr_vrss_prpr_rate: '-0.53', + cnnt_down_dynu: '1', + oprc_vrss_prpr_sign: '2', + oprc_vrss_prpr: '0', + oprc_vrss_prpr_rate: '0.00', + prd_rsfl: '0', + prd_rsfl_rate: '0.00', + }, + ], + rt_cd: '0', + msg_cd: 'MCA00000', + msg1: '정상처리 되었습니다.', +}; diff --git a/BE/src/stock/topfive/mockdata/stock-topfive-low.mockdata.ts b/BE/src/stock/topfive/mockdata/stock-topfive-low.mockdata.ts new file mode 100644 index 00000000..dee7888f --- /dev/null +++ b/BE/src/stock/topfive/mockdata/stock-topfive-low.mockdata.ts @@ -0,0 +1,787 @@ +export const STOCK_TOP_FIVE_LOW_MOCK = { + output: [ + { + stck_shrn_iscd: '004545', + data_rank: '1', + hts_kor_isnm: '깨끗한나라우', + stck_prpr: '11780', + prdy_vrss: '-2880', + prdy_vrss_sign: '5', + prdy_ctrt: '-19.65', + acml_vol: '71640', + stck_hgpr: '12900', + hgpr_hour: '090021', + acml_hgpr_date: '20241202', + stck_lwpr: '11780', + lwpr_hour: '153006', + acml_lwpr_date: '20241202', + lwpr_vrss_prpr_rate: '0.00', + dsgt_date_clpr_vrss_prpr_rate: '-19.65', + cnnt_ascn_dynu: '1', + hgpr_vrss_prpr_rate: '-8.68', + cnnt_down_dynu: '1', + oprc_vrss_prpr_sign: '2', + oprc_vrss_prpr: '0', + oprc_vrss_prpr_rate: '0.00', + prd_rsfl: '0', + prd_rsfl_rate: '0.00', + }, + { + stck_shrn_iscd: '191420', + data_rank: '2', + hts_kor_isnm: '테고사이언스', + stck_prpr: '12360', + prdy_vrss: '-2430', + prdy_vrss_sign: '5', + prdy_ctrt: '-16.43', + acml_vol: '154156', + stck_hgpr: '14990', + hgpr_hour: '090017', + acml_hgpr_date: '20241202', + stck_lwpr: '12050', + lwpr_hour: '142411', + acml_lwpr_date: '20241202', + lwpr_vrss_prpr_rate: '2.57', + dsgt_date_clpr_vrss_prpr_rate: '-16.43', + cnnt_ascn_dynu: '0', + hgpr_vrss_prpr_rate: '-17.55', + cnnt_down_dynu: '5', + oprc_vrss_prpr_sign: '2', + oprc_vrss_prpr: '0', + oprc_vrss_prpr_rate: '0.00', + prd_rsfl: '0', + prd_rsfl_rate: '0.00', + }, + { + stck_shrn_iscd: '004540', + data_rank: '3', + hts_kor_isnm: '깨끗한나라', + stck_prpr: '2165', + prdy_vrss: '-355', + prdy_vrss_sign: '5', + prdy_ctrt: '-14.09', + acml_vol: '7271564', + stck_hgpr: '2675', + hgpr_hour: '090021', + acml_hgpr_date: '20241202', + stck_lwpr: '2140', + lwpr_hour: '144403', + acml_lwpr_date: '20241202', + lwpr_vrss_prpr_rate: '1.17', + dsgt_date_clpr_vrss_prpr_rate: '-14.09', + cnnt_ascn_dynu: '1', + hgpr_vrss_prpr_rate: '-19.07', + cnnt_down_dynu: '1', + oprc_vrss_prpr_sign: '2', + oprc_vrss_prpr: '0', + oprc_vrss_prpr_rate: '0.00', + prd_rsfl: '0', + prd_rsfl_rate: '0.00', + }, + { + stck_shrn_iscd: '089890', + data_rank: '4', + hts_kor_isnm: '코세스', + stck_prpr: '6060', + prdy_vrss: '-940', + prdy_vrss_sign: '5', + prdy_ctrt: '-13.43', + acml_vol: '331350', + stck_hgpr: '7050', + hgpr_hour: '090437', + acml_hgpr_date: '20241202', + stck_lwpr: '5990', + lwpr_hour: '151841', + acml_lwpr_date: '20241202', + lwpr_vrss_prpr_rate: '1.17', + dsgt_date_clpr_vrss_prpr_rate: '-13.43', + cnnt_ascn_dynu: '0', + hgpr_vrss_prpr_rate: '-14.04', + cnnt_down_dynu: '2', + oprc_vrss_prpr_sign: '2', + oprc_vrss_prpr: '0', + oprc_vrss_prpr_rate: '0.00', + prd_rsfl: '0', + prd_rsfl_rate: '0.00', + }, + { + stck_shrn_iscd: '452280', + data_rank: '5', + hts_kor_isnm: '한선엔지니어링', + stck_prpr: '7960', + prdy_vrss: '-1160', + prdy_vrss_sign: '5', + prdy_ctrt: '-12.72', + acml_vol: '629702', + stck_hgpr: '8900', + hgpr_hour: '090007', + acml_hgpr_date: '20241202', + stck_lwpr: '7850', + lwpr_hour: '102538', + acml_lwpr_date: '20241202', + lwpr_vrss_prpr_rate: '1.40', + dsgt_date_clpr_vrss_prpr_rate: '-12.72', + cnnt_ascn_dynu: '0', + hgpr_vrss_prpr_rate: '-10.56', + cnnt_down_dynu: '4', + oprc_vrss_prpr_sign: '2', + oprc_vrss_prpr: '0', + oprc_vrss_prpr_rate: '0.00', + prd_rsfl: '0', + prd_rsfl_rate: '0.00', + }, + { + stck_shrn_iscd: '260930', + data_rank: '6', + hts_kor_isnm: '씨티케이', + stck_prpr: '5480', + prdy_vrss: '-730', + prdy_vrss_sign: '5', + prdy_ctrt: '-11.76', + acml_vol: '936890', + stck_hgpr: '6210', + hgpr_hour: '090014', + acml_hgpr_date: '20241202', + stck_lwpr: '4900', + lwpr_hour: '141305', + acml_lwpr_date: '20241202', + lwpr_vrss_prpr_rate: '11.84', + dsgt_date_clpr_vrss_prpr_rate: '-11.76', + cnnt_ascn_dynu: '0', + hgpr_vrss_prpr_rate: '-11.76', + cnnt_down_dynu: '2', + oprc_vrss_prpr_sign: '2', + oprc_vrss_prpr: '0', + oprc_vrss_prpr_rate: '0.00', + prd_rsfl: '0', + prd_rsfl_rate: '0.00', + }, + { + stck_shrn_iscd: '281820', + data_rank: '7', + hts_kor_isnm: '케이씨텍', + stck_prpr: '27400', + prdy_vrss: '-3600', + prdy_vrss_sign: '5', + prdy_ctrt: '-11.61', + acml_vol: '114217', + stck_hgpr: '31300', + hgpr_hour: '090026', + acml_hgpr_date: '20241202', + stck_lwpr: '27050', + lwpr_hour: '143232', + acml_lwpr_date: '20241202', + lwpr_vrss_prpr_rate: '1.29', + dsgt_date_clpr_vrss_prpr_rate: '-11.61', + cnnt_ascn_dynu: '0', + hgpr_vrss_prpr_rate: '-12.46', + cnnt_down_dynu: '2', + oprc_vrss_prpr_sign: '2', + oprc_vrss_prpr: '0', + oprc_vrss_prpr_rate: '0.00', + prd_rsfl: '0', + prd_rsfl_rate: '0.00', + }, + { + stck_shrn_iscd: '065060', + data_rank: '8', + hts_kor_isnm: '지엔코', + stck_prpr: '229', + prdy_vrss: '-29', + prdy_vrss_sign: '5', + prdy_ctrt: '-11.24', + acml_vol: '546204', + stck_hgpr: '258', + hgpr_hour: '090011', + acml_hgpr_date: '20241202', + stck_lwpr: '229', + lwpr_hour: '153030', + acml_lwpr_date: '20241202', + lwpr_vrss_prpr_rate: '0.00', + dsgt_date_clpr_vrss_prpr_rate: '-11.24', + cnnt_ascn_dynu: '0', + hgpr_vrss_prpr_rate: '-11.24', + cnnt_down_dynu: '4', + oprc_vrss_prpr_sign: '2', + oprc_vrss_prpr: '0', + oprc_vrss_prpr_rate: '0.00', + prd_rsfl: '0', + prd_rsfl_rate: '0.00', + }, + { + stck_shrn_iscd: '005257', + data_rank: '9', + hts_kor_isnm: '녹십자홀딩스2우', + stck_prpr: '24150', + prdy_vrss: '-3050', + prdy_vrss_sign: '5', + prdy_ctrt: '-11.21', + acml_vol: '8118', + stck_hgpr: '28000', + hgpr_hour: '090132', + acml_hgpr_date: '20241202', + stck_lwpr: '24100', + lwpr_hour: '145729', + acml_lwpr_date: '20241202', + lwpr_vrss_prpr_rate: '0.21', + dsgt_date_clpr_vrss_prpr_rate: '-11.21', + cnnt_ascn_dynu: '2', + hgpr_vrss_prpr_rate: '-13.75', + cnnt_down_dynu: '1', + oprc_vrss_prpr_sign: '2', + oprc_vrss_prpr: '0', + oprc_vrss_prpr_rate: '0.00', + prd_rsfl: '0', + prd_rsfl_rate: '0.00', + }, + { + stck_shrn_iscd: '071320', + data_rank: '10', + hts_kor_isnm: '지역난방공사', + stck_prpr: '52500', + prdy_vrss: '-6600', + prdy_vrss_sign: '5', + prdy_ctrt: '-11.17', + acml_vol: '69569', + stck_hgpr: '60400', + hgpr_hour: '090022', + acml_hgpr_date: '20241202', + stck_lwpr: '52300', + lwpr_hour: '130255', + acml_lwpr_date: '20241202', + lwpr_vrss_prpr_rate: '0.38', + dsgt_date_clpr_vrss_prpr_rate: '-11.17', + cnnt_ascn_dynu: '2', + hgpr_vrss_prpr_rate: '-13.08', + cnnt_down_dynu: '1', + oprc_vrss_prpr_sign: '2', + oprc_vrss_prpr: '0', + oprc_vrss_prpr_rate: '0.00', + prd_rsfl: '0', + prd_rsfl_rate: '0.00', + }, + { + stck_shrn_iscd: '039610', + data_rank: '11', + hts_kor_isnm: '화성밸브', + stck_prpr: '9930', + prdy_vrss: '-1230', + prdy_vrss_sign: '5', + prdy_ctrt: '-11.02', + acml_vol: '1165985', + stck_hgpr: '10750', + hgpr_hour: '090021', + acml_hgpr_date: '20241202', + stck_lwpr: '9840', + lwpr_hour: '112317', + acml_lwpr_date: '20241202', + lwpr_vrss_prpr_rate: '0.91', + dsgt_date_clpr_vrss_prpr_rate: '-11.02', + cnnt_ascn_dynu: '1', + hgpr_vrss_prpr_rate: '-7.63', + cnnt_down_dynu: '1', + oprc_vrss_prpr_sign: '2', + oprc_vrss_prpr: '0', + oprc_vrss_prpr_rate: '0.00', + prd_rsfl: '0', + prd_rsfl_rate: '0.00', + }, + { + stck_shrn_iscd: '315640', + data_rank: '12', + hts_kor_isnm: '딥노이드', + stck_prpr: '6740', + prdy_vrss: '-790', + prdy_vrss_sign: '5', + prdy_ctrt: '-10.49', + acml_vol: '1117435', + stck_hgpr: '7550', + hgpr_hour: '090246', + acml_hgpr_date: '20241202', + stck_lwpr: '6710', + lwpr_hour: '151640', + acml_lwpr_date: '20241202', + lwpr_vrss_prpr_rate: '0.45', + dsgt_date_clpr_vrss_prpr_rate: '-10.49', + cnnt_ascn_dynu: '1', + hgpr_vrss_prpr_rate: '-10.73', + cnnt_down_dynu: '1', + oprc_vrss_prpr_sign: '2', + oprc_vrss_prpr: '0', + oprc_vrss_prpr_rate: '0.00', + prd_rsfl: '0', + prd_rsfl_rate: '0.00', + }, + { + stck_shrn_iscd: '000520', + data_rank: '13', + hts_kor_isnm: '삼일제약', + stck_prpr: '10840', + prdy_vrss: '-1260', + prdy_vrss_sign: '5', + prdy_ctrt: '-10.41', + acml_vol: '1821330', + stck_hgpr: '12500', + hgpr_hour: '090008', + acml_hgpr_date: '20241202', + stck_lwpr: '10740', + lwpr_hour: '145300', + acml_lwpr_date: '20241202', + lwpr_vrss_prpr_rate: '0.93', + dsgt_date_clpr_vrss_prpr_rate: '-10.41', + cnnt_ascn_dynu: '0', + hgpr_vrss_prpr_rate: '-13.28', + cnnt_down_dynu: '2', + oprc_vrss_prpr_sign: '2', + oprc_vrss_prpr: '0', + oprc_vrss_prpr_rate: '0.00', + prd_rsfl: '0', + prd_rsfl_rate: '0.00', + }, + { + stck_shrn_iscd: '036460', + data_rank: '14', + hts_kor_isnm: '한국가스공사', + stck_prpr: '40050', + prdy_vrss: '-4600', + prdy_vrss_sign: '5', + prdy_ctrt: '-10.30', + acml_vol: '2467206', + stck_hgpr: '43500', + hgpr_hour: '090007', + acml_hgpr_date: '20241202', + stck_lwpr: '39800', + lwpr_hour: '102651', + acml_lwpr_date: '20241202', + lwpr_vrss_prpr_rate: '0.63', + dsgt_date_clpr_vrss_prpr_rate: '-10.30', + cnnt_ascn_dynu: '0', + hgpr_vrss_prpr_rate: '-7.93', + cnnt_down_dynu: '5', + oprc_vrss_prpr_sign: '2', + oprc_vrss_prpr: '0', + oprc_vrss_prpr_rate: '0.00', + prd_rsfl: '0', + prd_rsfl_rate: '0.00', + }, + { + stck_shrn_iscd: '062040', + data_rank: '15', + hts_kor_isnm: '산일전기', + stck_prpr: '55500', + prdy_vrss: '-6000', + prdy_vrss_sign: '5', + prdy_ctrt: '-9.76', + acml_vol: '786091', + stck_hgpr: '63000', + hgpr_hour: '090027', + acml_hgpr_date: '20241202', + stck_lwpr: '55400', + lwpr_hour: '150713', + acml_lwpr_date: '20241202', + lwpr_vrss_prpr_rate: '0.18', + dsgt_date_clpr_vrss_prpr_rate: '-9.76', + cnnt_ascn_dynu: '3', + hgpr_vrss_prpr_rate: '-11.90', + cnnt_down_dynu: '1', + oprc_vrss_prpr_sign: '2', + oprc_vrss_prpr: '0', + oprc_vrss_prpr_rate: '0.00', + prd_rsfl: '0', + prd_rsfl_rate: '0.00', + }, + { + stck_shrn_iscd: '267850', + data_rank: '16', + hts_kor_isnm: '아시아나IDT', + stck_prpr: '14380', + prdy_vrss: '-1520', + prdy_vrss_sign: '5', + prdy_ctrt: '-9.56', + acml_vol: '288308', + stck_hgpr: '15860', + hgpr_hour: '090016', + acml_hgpr_date: '20241202', + stck_lwpr: '14300', + lwpr_hour: '101845', + acml_lwpr_date: '20241202', + lwpr_vrss_prpr_rate: '0.56', + dsgt_date_clpr_vrss_prpr_rate: '-9.56', + cnnt_ascn_dynu: '0', + hgpr_vrss_prpr_rate: '-9.33', + cnnt_down_dynu: '2', + oprc_vrss_prpr_sign: '2', + oprc_vrss_prpr: '0', + oprc_vrss_prpr_rate: '0.00', + prd_rsfl: '0', + prd_rsfl_rate: '0.00', + }, + { + stck_shrn_iscd: '030350', + data_rank: '17', + hts_kor_isnm: '드래곤플라이', + stck_prpr: '1055', + prdy_vrss: '-110', + prdy_vrss_sign: '5', + prdy_ctrt: '-9.44', + acml_vol: '427949', + stck_hgpr: '1162', + hgpr_hour: '090019', + acml_hgpr_date: '20241202', + stck_lwpr: '1055', + lwpr_hour: '153030', + acml_lwpr_date: '20241202', + lwpr_vrss_prpr_rate: '0.00', + dsgt_date_clpr_vrss_prpr_rate: '-9.44', + cnnt_ascn_dynu: '0', + hgpr_vrss_prpr_rate: '-9.21', + cnnt_down_dynu: '2', + oprc_vrss_prpr_sign: '2', + oprc_vrss_prpr: '0', + oprc_vrss_prpr_rate: '0.00', + prd_rsfl: '0', + prd_rsfl_rate: '0.00', + }, + { + stck_shrn_iscd: 'Q700025', + data_rank: '18', + hts_kor_isnm: '하나 블룸버그 2X 천연가스 선물 ETN(H) B', + stck_prpr: '3945', + prdy_vrss: '-410', + prdy_vrss_sign: '5', + prdy_ctrt: '-9.41', + acml_vol: '6507', + stck_hgpr: '4080', + hgpr_hour: '090013', + acml_hgpr_date: '20241202', + stck_lwpr: '3945', + lwpr_hour: '153014', + acml_lwpr_date: '20241202', + lwpr_vrss_prpr_rate: '0.00', + dsgt_date_clpr_vrss_prpr_rate: '-9.41', + cnnt_ascn_dynu: '1', + hgpr_vrss_prpr_rate: '-3.31', + cnnt_down_dynu: '1', + oprc_vrss_prpr_sign: '2', + oprc_vrss_prpr: '0', + oprc_vrss_prpr_rate: '0.00', + prd_rsfl: '0', + prd_rsfl_rate: '0.00', + }, + { + stck_shrn_iscd: '217330', + data_rank: '19', + hts_kor_isnm: '싸이토젠', + stck_prpr: '6180', + prdy_vrss: '-640', + prdy_vrss_sign: '5', + prdy_ctrt: '-9.38', + acml_vol: '101146', + stck_hgpr: '6990', + hgpr_hour: '090021', + acml_hgpr_date: '20241202', + stck_lwpr: '6150', + lwpr_hour: '151952', + acml_lwpr_date: '20241202', + lwpr_vrss_prpr_rate: '0.49', + dsgt_date_clpr_vrss_prpr_rate: '-9.38', + cnnt_ascn_dynu: '0', + hgpr_vrss_prpr_rate: '-11.59', + cnnt_down_dynu: '3', + oprc_vrss_prpr_sign: '2', + oprc_vrss_prpr: '0', + oprc_vrss_prpr_rate: '0.00', + prd_rsfl: '0', + prd_rsfl_rate: '0.00', + }, + { + stck_shrn_iscd: '354200', + data_rank: '20', + hts_kor_isnm: '엔젠바이오', + stck_prpr: '2575', + prdy_vrss: '-265', + prdy_vrss_sign: '5', + prdy_ctrt: '-9.33', + acml_vol: '1524449', + stck_hgpr: '3195', + hgpr_hour: '130612', + acml_hgpr_date: '20241202', + stck_lwpr: '2565', + lwpr_hour: '150816', + acml_lwpr_date: '20241202', + lwpr_vrss_prpr_rate: '0.39', + dsgt_date_clpr_vrss_prpr_rate: '-9.33', + cnnt_ascn_dynu: '3', + hgpr_vrss_prpr_rate: '-19.41', + cnnt_down_dynu: '1', + oprc_vrss_prpr_sign: '2', + oprc_vrss_prpr: '0', + oprc_vrss_prpr_rate: '0.00', + prd_rsfl: '0', + prd_rsfl_rate: '0.00', + }, + { + stck_shrn_iscd: '043100', + data_rank: '21', + hts_kor_isnm: '알파녹스', + stck_prpr: '1987', + prdy_vrss: '-203', + prdy_vrss_sign: '5', + prdy_ctrt: '-9.27', + acml_vol: '68086', + stck_hgpr: '2200', + hgpr_hour: '090118', + acml_hgpr_date: '20241202', + stck_lwpr: '1987', + lwpr_hour: '153030', + acml_lwpr_date: '20241202', + lwpr_vrss_prpr_rate: '0.00', + dsgt_date_clpr_vrss_prpr_rate: '-9.27', + cnnt_ascn_dynu: '0', + hgpr_vrss_prpr_rate: '-9.68', + cnnt_down_dynu: '1', + oprc_vrss_prpr_sign: '2', + oprc_vrss_prpr: '0', + oprc_vrss_prpr_rate: '0.00', + prd_rsfl: '0', + prd_rsfl_rate: '0.00', + }, + { + stck_shrn_iscd: '215600', + data_rank: '22', + hts_kor_isnm: '신라젠', + stck_prpr: '2945', + prdy_vrss: '-300', + prdy_vrss_sign: '5', + prdy_ctrt: '-9.24', + acml_vol: '1314816', + stck_hgpr: '3255', + hgpr_hour: '090010', + acml_hgpr_date: '20241202', + stck_lwpr: '2945', + lwpr_hour: '153021', + acml_lwpr_date: '20241202', + lwpr_vrss_prpr_rate: '0.00', + dsgt_date_clpr_vrss_prpr_rate: '-9.24', + cnnt_ascn_dynu: '8', + hgpr_vrss_prpr_rate: '-9.52', + cnnt_down_dynu: '1', + oprc_vrss_prpr_sign: '2', + oprc_vrss_prpr: '0', + oprc_vrss_prpr_rate: '0.00', + prd_rsfl: '0', + prd_rsfl_rate: '0.00', + }, + { + stck_shrn_iscd: '074610', + data_rank: '23', + hts_kor_isnm: '이엔플러스', + stck_prpr: '1089', + prdy_vrss: '-110', + prdy_vrss_sign: '5', + prdy_ctrt: '-9.17', + acml_vol: '813175', + stck_hgpr: '1217', + hgpr_hour: '090203', + acml_hgpr_date: '20241202', + stck_lwpr: '1086', + lwpr_hour: '123409', + acml_lwpr_date: '20241202', + lwpr_vrss_prpr_rate: '0.28', + dsgt_date_clpr_vrss_prpr_rate: '-9.17', + cnnt_ascn_dynu: '1', + hgpr_vrss_prpr_rate: '-10.52', + cnnt_down_dynu: '1', + oprc_vrss_prpr_sign: '2', + oprc_vrss_prpr: '0', + oprc_vrss_prpr_rate: '0.00', + prd_rsfl: '0', + prd_rsfl_rate: '0.00', + }, + { + stck_shrn_iscd: 'Q610067', + data_rank: '24', + hts_kor_isnm: '메리츠 블룸버그 2X 천연가스선물 ETN(H) B', + stck_prpr: '3925', + prdy_vrss: '-395', + prdy_vrss_sign: '5', + prdy_ctrt: '-9.14', + acml_vol: '24354', + stck_hgpr: '4090', + hgpr_hour: '123158', + acml_hgpr_date: '20241202', + stck_lwpr: '3925', + lwpr_hour: '144937', + acml_lwpr_date: '20241202', + lwpr_vrss_prpr_rate: '0.00', + dsgt_date_clpr_vrss_prpr_rate: '-9.14', + cnnt_ascn_dynu: '1', + hgpr_vrss_prpr_rate: '-4.03', + cnnt_down_dynu: '1', + oprc_vrss_prpr_sign: '2', + oprc_vrss_prpr: '0', + oprc_vrss_prpr_rate: '0.00', + prd_rsfl: '0', + prd_rsfl_rate: '0.00', + }, + { + stck_shrn_iscd: '012690', + data_rank: '25', + hts_kor_isnm: '모나리자', + stck_prpr: '2800', + prdy_vrss: '-280', + prdy_vrss_sign: '5', + prdy_ctrt: '-9.09', + acml_vol: '2284793', + stck_hgpr: '2995', + hgpr_hour: '090027', + acml_hgpr_date: '20241202', + stck_lwpr: '2800', + lwpr_hour: '153014', + acml_lwpr_date: '20241202', + lwpr_vrss_prpr_rate: '0.00', + dsgt_date_clpr_vrss_prpr_rate: '-9.09', + cnnt_ascn_dynu: '2', + hgpr_vrss_prpr_rate: '-6.51', + cnnt_down_dynu: '1', + oprc_vrss_prpr_sign: '2', + oprc_vrss_prpr: '0', + oprc_vrss_prpr_rate: '0.00', + prd_rsfl: '0', + prd_rsfl_rate: '0.00', + }, + { + stck_shrn_iscd: 'Q500082', + data_rank: '26', + hts_kor_isnm: '신한 블룸버그 2X 천연가스 선물 ETN', + stck_prpr: '4985', + prdy_vrss: '-490', + prdy_vrss_sign: '5', + prdy_ctrt: '-8.95', + acml_vol: '160739', + stck_hgpr: '5180', + hgpr_hour: '123435', + acml_hgpr_date: '20241202', + stck_lwpr: '4985', + lwpr_hour: '150816', + acml_lwpr_date: '20241202', + lwpr_vrss_prpr_rate: '0.00', + dsgt_date_clpr_vrss_prpr_rate: '-8.95', + cnnt_ascn_dynu: '1', + hgpr_vrss_prpr_rate: '-3.76', + cnnt_down_dynu: '1', + oprc_vrss_prpr_sign: '2', + oprc_vrss_prpr: '0', + oprc_vrss_prpr_rate: '0.00', + prd_rsfl: '0', + prd_rsfl_rate: '0.00', + }, + { + stck_shrn_iscd: 'Q530111', + data_rank: '27', + hts_kor_isnm: '삼성 레버리지 천연가스 선물 ETN C', + stck_prpr: '3715', + prdy_vrss: '-365', + prdy_vrss_sign: '5', + prdy_ctrt: '-8.95', + acml_vol: '1345055', + stck_hgpr: '3865', + hgpr_hour: '123402', + acml_hgpr_date: '20241202', + stck_lwpr: '3715', + lwpr_hour: '153016', + acml_lwpr_date: '20241202', + lwpr_vrss_prpr_rate: '0.00', + dsgt_date_clpr_vrss_prpr_rate: '-8.95', + cnnt_ascn_dynu: '1', + hgpr_vrss_prpr_rate: '-3.88', + cnnt_down_dynu: '1', + oprc_vrss_prpr_sign: '2', + oprc_vrss_prpr: '0', + oprc_vrss_prpr_rate: '0.00', + prd_rsfl: '0', + prd_rsfl_rate: '0.00', + }, + { + stck_shrn_iscd: '348340', + data_rank: '28', + hts_kor_isnm: '뉴로메카', + stck_prpr: '24000', + prdy_vrss: '-2350', + prdy_vrss_sign: '5', + prdy_ctrt: '-8.92', + acml_vol: '466196', + stck_hgpr: '26250', + hgpr_hour: '090020', + acml_hgpr_date: '20241202', + stck_lwpr: '23750', + lwpr_hour: '124415', + acml_lwpr_date: '20241202', + lwpr_vrss_prpr_rate: '1.05', + dsgt_date_clpr_vrss_prpr_rate: '-8.92', + cnnt_ascn_dynu: '7', + hgpr_vrss_prpr_rate: '-8.57', + cnnt_down_dynu: '1', + oprc_vrss_prpr_sign: '2', + oprc_vrss_prpr: '0', + oprc_vrss_prpr_rate: '0.00', + prd_rsfl: '0', + prd_rsfl_rate: '0.00', + }, + { + stck_shrn_iscd: '028080', + data_rank: '29', + hts_kor_isnm: '휴맥스홀딩스', + stck_prpr: '2255', + prdy_vrss: '-220', + prdy_vrss_sign: '5', + prdy_ctrt: '-8.89', + acml_vol: '113944', + stck_hgpr: '2590', + hgpr_hour: '090024', + acml_hgpr_date: '20241202', + stck_lwpr: '2240', + lwpr_hour: '094918', + acml_lwpr_date: '20241202', + lwpr_vrss_prpr_rate: '0.67', + dsgt_date_clpr_vrss_prpr_rate: '-8.89', + cnnt_ascn_dynu: '0', + hgpr_vrss_prpr_rate: '-12.93', + cnnt_down_dynu: '7', + oprc_vrss_prpr_sign: '2', + oprc_vrss_prpr: '0', + oprc_vrss_prpr_rate: '0.00', + prd_rsfl: '0', + prd_rsfl_rate: '0.00', + }, + { + stck_shrn_iscd: 'Q550074', + data_rank: '30', + hts_kor_isnm: 'N2 블룸버그 2X 천연가스 선물 ETN(H)', + stck_prpr: '250', + prdy_vrss: '-24', + prdy_vrss_sign: '5', + prdy_ctrt: '-8.76', + acml_vol: '1306632', + stck_hgpr: '260', + hgpr_hour: '123417', + acml_hgpr_date: '20241202', + stck_lwpr: '249', + lwpr_hour: '145019', + acml_lwpr_date: '20241202', + lwpr_vrss_prpr_rate: '0.40', + dsgt_date_clpr_vrss_prpr_rate: '-8.76', + cnnt_ascn_dynu: '1', + hgpr_vrss_prpr_rate: '-3.85', + cnnt_down_dynu: '1', + oprc_vrss_prpr_sign: '2', + oprc_vrss_prpr: '0', + oprc_vrss_prpr_rate: '0.00', + prd_rsfl: '0', + prd_rsfl_rate: '0.00', + }, + ], + rt_cd: '0', + msg_cd: 'MCA00000', + msg1: '정상처리 되었습니다.', +}; diff --git a/BE/src/stock/topfive/stock-topfive.service.spec.ts b/BE/src/stock/topfive/stock-topfive.service.spec.ts new file mode 100644 index 00000000..1365d13f --- /dev/null +++ b/BE/src/stock/topfive/stock-topfive.service.spec.ts @@ -0,0 +1,46 @@ +import { Test } from '@nestjs/testing'; +import { KoreaInvestmentDomainService } from '../../common/koreaInvestment/korea-investment.domain-service'; +import { StockTopfiveService } from './stock-topfive.service'; +import { StockRankingDataDto } from './dto/stock-ranking-data.dto'; +import { STOCK_TOP_FIVE_HIGH_MOCK } from './mockdata/stock-topfive-high.mockdata'; +import { STOCK_TOP_FIVE_LOW_MOCK } from './mockdata/stock-topfive-low.mockdata'; +import { MarketType } from '../enum/market-type'; + +describe('stock topfive test', () => { + let stockTopfiveService: StockTopfiveService; + let koreaInvestmentDomainService: KoreaInvestmentDomainService; + let highResponse: StockRankingDataDto[]; + let lowResponse: StockRankingDataDto[]; + + beforeEach(async () => { + const module = await Test.createTestingModule({ + providers: [StockTopfiveService, KoreaInvestmentDomainService], + }).compile(); + + stockTopfiveService = module.get(StockTopfiveService); + koreaInvestmentDomainService = module.get(KoreaInvestmentDomainService); + + jest + .spyOn(koreaInvestmentDomainService, 'getAccessToken') + .mockResolvedValue('accessToken'); + + jest + .spyOn(koreaInvestmentDomainService, 'requestApi') + .mockResolvedValueOnce(STOCK_TOP_FIVE_HIGH_MOCK) + .mockResolvedValueOnce(STOCK_TOP_FIVE_LOW_MOCK); + + const response = await stockTopfiveService.getMarketRanking(MarketType.ALL); + highResponse = response.high; + lowResponse = response.low; + }); + + it('전체 종목에 대한 급상승/급하락 순위를 5개까지 받아온다.', () => { + expect(highResponse.length).toEqual(5); + expect(lowResponse.length).toEqual(5); + }); + + it('받아오는 순위 배열의 내부 데이터에는 종목명이 포함되어 있다.', () => { + expect(highResponse[0].hts_kor_isnm).toEqual('효성화학'); + expect(lowResponse[4].hts_kor_isnm).toEqual('한선엔지니어링'); + }); +}); diff --git a/BE/src/stock/trade/history/mockdata/stock-trade-history-daily.mockdata.ts b/BE/src/stock/trade/history/mockdata/stock-trade-history-daily.mockdata.ts new file mode 100644 index 00000000..4e80986e --- /dev/null +++ b/BE/src/stock/trade/history/mockdata/stock-trade-history-daily.mockdata.ts @@ -0,0 +1,487 @@ +export const STOCK_TRADE_HISTORY_DAILY_MOCK = { + output: [ + { + stck_bsop_date: '20241202', + stck_oprc: '54300', + stck_hgpr: '54400', + stck_lwpr: '53100', + stck_clpr: '53600', + acml_vol: '21924956', + prdy_vrss_vol_rate: '-10.56', + prdy_vrss: '-600', + prdy_vrss_sign: '5', + prdy_ctrt: '-1.11', + hts_frgn_ehrt: '51.35', + frgn_ntby_qty: '0', + flng_cls_code: '00', + acml_prtt_rate: '1.00', + }, + { + stck_bsop_date: '20241129', + stck_oprc: '55100', + stck_hgpr: '55300', + stck_lwpr: '53800', + stck_clpr: '54200', + acml_vol: '24513532', + prdy_vrss_vol_rate: '22.56', + prdy_vrss: '-1300', + prdy_vrss_sign: '5', + prdy_ctrt: '-2.34', + hts_frgn_ehrt: '51.35', + frgn_ntby_qty: '-4579840', + flng_cls_code: '00', + acml_prtt_rate: '1.00', + }, + { + stck_bsop_date: '20241128', + stck_oprc: '56000', + stck_hgpr: '56400', + stck_lwpr: '55200', + stck_clpr: '55500', + acml_vol: '20001134', + prdy_vrss_vol_rate: '-8.29', + prdy_vrss: '-800', + prdy_vrss_sign: '5', + prdy_ctrt: '-1.42', + hts_frgn_ehrt: '51.43', + frgn_ntby_qty: '-3350753', + flng_cls_code: '00', + acml_prtt_rate: '1.00', + }, + { + stck_bsop_date: '20241127', + stck_oprc: '57700', + stck_hgpr: '57800', + stck_lwpr: '56000', + stck_clpr: '56300', + acml_vol: '21808388', + prdy_vrss_vol_rate: '-6.04', + prdy_vrss: '-2000', + prdy_vrss_sign: '5', + prdy_ctrt: '-3.43', + hts_frgn_ehrt: '51.49', + frgn_ntby_qty: '-5105259', + flng_cls_code: '00', + acml_prtt_rate: '1.00', + }, + { + stck_bsop_date: '20241126', + stck_oprc: '57900', + stck_hgpr: '58900', + stck_lwpr: '57500', + stck_clpr: '58300', + acml_vol: '23209404', + prdy_vrss_vol_rate: '-35.95', + prdy_vrss: '400', + prdy_vrss_sign: '2', + prdy_ctrt: '0.69', + hts_frgn_ehrt: '51.57', + frgn_ntby_qty: '764317', + flng_cls_code: '00', + acml_prtt_rate: '1.00', + }, + { + stck_bsop_date: '20241125', + stck_oprc: '57400', + stck_hgpr: '57900', + stck_lwpr: '56700', + stck_clpr: '57900', + acml_vol: '36237324', + prdy_vrss_vol_rate: '137.13', + prdy_vrss: '1900', + prdy_vrss_sign: '2', + prdy_ctrt: '3.39', + hts_frgn_ehrt: '51.56', + frgn_ntby_qty: '-266783', + flng_cls_code: '00', + acml_prtt_rate: '1.00', + }, + { + stck_bsop_date: '20241122', + stck_oprc: '56000', + stck_hgpr: '56700', + stck_lwpr: '55900', + stck_clpr: '56000', + acml_vol: '15281543', + prdy_vrss_vol_rate: '-19.98', + prdy_vrss: '-400', + prdy_vrss_sign: '5', + prdy_ctrt: '-0.71', + hts_frgn_ehrt: '51.56', + frgn_ntby_qty: '-3588929', + flng_cls_code: '00', + acml_prtt_rate: '1.00', + }, + { + stck_bsop_date: '20241121', + stck_oprc: '54900', + stck_hgpr: '56900', + stck_lwpr: '54700', + stck_clpr: '56400', + acml_vol: '19096850', + prdy_vrss_vol_rate: '-8.47', + prdy_vrss: '1100', + prdy_vrss_sign: '2', + prdy_ctrt: '1.99', + hts_frgn_ehrt: '51.62', + frgn_ntby_qty: '-782949', + flng_cls_code: '00', + acml_prtt_rate: '1.00', + }, + { + stck_bsop_date: '20241120', + stck_oprc: '56100', + stck_hgpr: '56500', + stck_lwpr: '54800', + stck_clpr: '55300', + acml_vol: '20864668', + prdy_vrss_vol_rate: '-33.85', + prdy_vrss: '-1000', + prdy_vrss_sign: '5', + prdy_ctrt: '-1.78', + hts_frgn_ehrt: '51.64', + frgn_ntby_qty: '-1459649', + flng_cls_code: '00', + acml_prtt_rate: '1.00', + }, + { + stck_bsop_date: '20241119', + stck_oprc: '56500', + stck_hgpr: '57500', + stck_lwpr: '55900', + stck_clpr: '56300', + acml_vol: '31539632', + prdy_vrss_vol_rate: '-34.42', + prdy_vrss: '-400', + prdy_vrss_sign: '5', + prdy_ctrt: '-0.71', + hts_frgn_ehrt: '51.66', + frgn_ntby_qty: '-2601113', + flng_cls_code: '00', + acml_prtt_rate: '1.00', + }, + { + stck_bsop_date: '20241118', + stck_oprc: '57000', + stck_hgpr: '57500', + stck_lwpr: '55900', + stck_clpr: '56700', + acml_vol: '48095232', + prdy_vrss_vol_rate: '2.82', + prdy_vrss: '3200', + prdy_vrss_sign: '2', + prdy_ctrt: '5.98', + hts_frgn_ehrt: '51.70', + frgn_ntby_qty: '-3242824', + flng_cls_code: '00', + acml_prtt_rate: '1.00', + }, + { + stck_bsop_date: '20241115', + stck_oprc: '50300', + stck_hgpr: '54200', + stck_lwpr: '50300', + stck_clpr: '53500', + acml_vol: '46774484', + prdy_vrss_vol_rate: '-3.58', + prdy_vrss: '3600', + prdy_vrss_sign: '2', + prdy_ctrt: '7.21', + hts_frgn_ehrt: '51.76', + frgn_ntby_qty: '2488066', + flng_cls_code: '00', + acml_prtt_rate: '1.00', + }, + { + stck_bsop_date: '20241114', + stck_oprc: '50200', + stck_hgpr: '51800', + stck_lwpr: '49900', + stck_clpr: '49900', + acml_vol: '48510716', + prdy_vrss_vol_rate: '-7.65', + prdy_vrss: '-700', + prdy_vrss_sign: '5', + prdy_ctrt: '-1.38', + hts_frgn_ehrt: '51.72', + frgn_ntby_qty: '-8853732', + flng_cls_code: '00', + acml_prtt_rate: '1.00', + }, + { + stck_bsop_date: '20241113', + stck_oprc: '52000', + stck_hgpr: '53000', + stck_lwpr: '50500', + stck_clpr: '50600', + acml_vol: '52527996', + prdy_vrss_vol_rate: '38.37', + prdy_vrss: '-2400', + prdy_vrss_sign: '5', + prdy_ctrt: '-4.53', + hts_frgn_ehrt: '51.87', + frgn_ntby_qty: '-13742684', + flng_cls_code: '00', + acml_prtt_rate: '1.00', + }, + { + stck_bsop_date: '20241112', + stck_oprc: '54600', + stck_hgpr: '54600', + stck_lwpr: '53000', + stck_clpr: '53000', + acml_vol: '37962880', + prdy_vrss_vol_rate: '27.34', + prdy_vrss: '-2000', + prdy_vrss_sign: '5', + prdy_ctrt: '-3.64', + hts_frgn_ehrt: '52.10', + frgn_ntby_qty: '-7461438', + flng_cls_code: '00', + acml_prtt_rate: '1.00', + }, + { + stck_bsop_date: '20241111', + stck_oprc: '56700', + stck_hgpr: '56800', + stck_lwpr: '55000', + stck_clpr: '55000', + acml_vol: '29811326', + prdy_vrss_vol_rate: '114.82', + prdy_vrss: '-2000', + prdy_vrss_sign: '5', + prdy_ctrt: '-3.51', + hts_frgn_ehrt: '52.22', + frgn_ntby_qty: '-9424701', + flng_cls_code: '00', + acml_prtt_rate: '1.00', + }, + { + stck_bsop_date: '20241108', + stck_oprc: '58000', + stck_hgpr: '58300', + stck_lwpr: '57000', + stck_clpr: '57000', + acml_vol: '13877396', + prdy_vrss_vol_rate: '-18.57', + prdy_vrss: '-500', + prdy_vrss_sign: '5', + prdy_ctrt: '-0.87', + hts_frgn_ehrt: '52.38', + frgn_ntby_qty: '-1480774', + flng_cls_code: '00', + acml_prtt_rate: '1.00', + }, + { + stck_bsop_date: '20241107', + stck_oprc: '56900', + stck_hgpr: '58100', + stck_lwpr: '56800', + stck_clpr: '57500', + acml_vol: '17043102', + prdy_vrss_vol_rate: '-22.85', + prdy_vrss: '200', + prdy_vrss_sign: '2', + prdy_ctrt: '0.35', + hts_frgn_ehrt: '52.40', + frgn_ntby_qty: '-321775', + flng_cls_code: '00', + acml_prtt_rate: '1.00', + }, + { + stck_bsop_date: '20241106', + stck_oprc: '57600', + stck_hgpr: '58000', + stck_lwpr: '56300', + stck_clpr: '57300', + acml_vol: '22092218', + prdy_vrss_vol_rate: '26.35', + prdy_vrss: '-300', + prdy_vrss_sign: '5', + prdy_ctrt: '-0.52', + hts_frgn_ehrt: '52.41', + frgn_ntby_qty: '-2611198', + flng_cls_code: '00', + acml_prtt_rate: '1.00', + }, + { + stck_bsop_date: '20241105', + stck_oprc: '57800', + stck_hgpr: '58100', + stck_lwpr: '57200', + stck_clpr: '57600', + acml_vol: '17484474', + prdy_vrss_vol_rate: '12.17', + prdy_vrss: '-1100', + prdy_vrss_sign: '5', + prdy_ctrt: '-1.87', + hts_frgn_ehrt: '52.45', + frgn_ntby_qty: '-2848671', + flng_cls_code: '00', + acml_prtt_rate: '1.00', + }, + { + stck_bsop_date: '20241104', + stck_oprc: '58600', + stck_hgpr: '59400', + stck_lwpr: '58400', + stck_clpr: '58700', + acml_vol: '15586947', + prdy_vrss_vol_rate: '-18.32', + prdy_vrss: '400', + prdy_vrss_sign: '2', + prdy_ctrt: '0.69', + hts_frgn_ehrt: '52.50', + frgn_ntby_qty: '-2093510', + flng_cls_code: '00', + acml_prtt_rate: '1.00', + }, + { + stck_bsop_date: '20241101', + stck_oprc: '59000', + stck_hgpr: '59600', + stck_lwpr: '58100', + stck_clpr: '58300', + acml_vol: '19083180', + prdy_vrss_vol_rate: '-46.71', + prdy_vrss: '-900', + prdy_vrss_sign: '5', + prdy_ctrt: '-1.52', + hts_frgn_ehrt: '52.53', + frgn_ntby_qty: '-2063620', + flng_cls_code: '00', + acml_prtt_rate: '1.00', + }, + { + stck_bsop_date: '20241031', + stck_oprc: '58500', + stck_hgpr: '61200', + stck_lwpr: '58300', + stck_clpr: '59200', + acml_vol: '35809196', + prdy_vrss_vol_rate: '80.50', + prdy_vrss: '100', + prdy_vrss_sign: '2', + prdy_ctrt: '0.17', + hts_frgn_ehrt: '52.57', + frgn_ntby_qty: '-2059308', + flng_cls_code: '00', + acml_prtt_rate: '1.00', + }, + { + stck_bsop_date: '20241030', + stck_oprc: '59100', + stck_hgpr: '59800', + stck_lwpr: '58600', + stck_clpr: '59100', + acml_vol: '19838512', + prdy_vrss_vol_rate: '-30.07', + prdy_vrss: '-500', + prdy_vrss_sign: '5', + prdy_ctrt: '-0.84', + hts_frgn_ehrt: '52.60', + frgn_ntby_qty: '-3054026', + flng_cls_code: '00', + acml_prtt_rate: '1.00', + }, + { + stck_bsop_date: '20241029', + stck_oprc: '58000', + stck_hgpr: '59600', + stck_lwpr: '57300', + stck_clpr: '59600', + acml_vol: '28369314', + prdy_vrss_vol_rate: '2.14', + prdy_vrss: '1500', + prdy_vrss_sign: '2', + prdy_ctrt: '2.58', + hts_frgn_ehrt: '52.66', + frgn_ntby_qty: '314269', + flng_cls_code: '00', + acml_prtt_rate: '1.00', + }, + { + stck_bsop_date: '20241028', + stck_oprc: '55700', + stck_hgpr: '58500', + stck_lwpr: '55700', + stck_clpr: '58100', + acml_vol: '27775008', + prdy_vrss_vol_rate: '7.53', + prdy_vrss: '2200', + prdy_vrss_sign: '2', + prdy_ctrt: '3.94', + hts_frgn_ehrt: '52.65', + frgn_ntby_qty: '370214', + flng_cls_code: '00', + acml_prtt_rate: '1.00', + }, + { + stck_bsop_date: '20241025', + stck_oprc: '56000', + stck_hgpr: '56900', + stck_lwpr: '55800', + stck_clpr: '55900', + acml_vol: '25829316', + prdy_vrss_vol_rate: '-18.00', + prdy_vrss: '-700', + prdy_vrss_sign: '5', + prdy_ctrt: '-1.24', + hts_frgn_ehrt: '52.64', + frgn_ntby_qty: '-5811796', + flng_cls_code: '00', + acml_prtt_rate: '1.00', + }, + { + stck_bsop_date: '20241024', + stck_oprc: '58200', + stck_hgpr: '58500', + stck_lwpr: '56600', + stck_clpr: '56600', + acml_vol: '31499922', + prdy_vrss_vol_rate: '15.38', + prdy_vrss: '-2500', + prdy_vrss_sign: '5', + prdy_ctrt: '-4.23', + hts_frgn_ehrt: '52.74', + frgn_ntby_qty: '-11444663', + flng_cls_code: '00', + acml_prtt_rate: '1.00', + }, + { + stck_bsop_date: '20241023', + stck_oprc: '57500', + stck_hgpr: '60000', + stck_lwpr: '57100', + stck_clpr: '59100', + acml_vol: '27300780', + prdy_vrss_vol_rate: '-1.02', + prdy_vrss: '1400', + prdy_vrss_sign: '2', + prdy_ctrt: '2.43', + hts_frgn_ehrt: '52.93', + frgn_ntby_qty: '69322', + flng_cls_code: '00', + acml_prtt_rate: '1.00', + }, + { + stck_bsop_date: '20241022', + stck_oprc: '58800', + stck_hgpr: '58900', + stck_lwpr: '57700', + stck_clpr: '57700', + acml_vol: '27582528', + prdy_vrss_vol_rate: '48.97', + prdy_vrss: '-1300', + prdy_vrss_sign: '5', + prdy_ctrt: '-2.20', + hts_frgn_ehrt: '52.93', + frgn_ntby_qty: '-4832158', + flng_cls_code: '00', + acml_prtt_rate: '1.00', + }, + ], + rt_cd: '0', + msg_cd: 'MCA00000', + msg1: '정상처리 되었습니다.', +}; diff --git a/BE/src/stock/trade/history/mockdata/stock-trade-history-today.mockdata.ts b/BE/src/stock/trade/history/mockdata/stock-trade-history-today.mockdata.ts new file mode 100644 index 00000000..a19c08ab --- /dev/null +++ b/BE/src/stock/trade/history/mockdata/stock-trade-history-today.mockdata.ts @@ -0,0 +1,277 @@ +export const STOCK_TRADE_HISTORY_TODAY_MOCK = { + output: [ + { + stck_cntg_hour: '155958', + stck_prpr: '53600', + prdy_vrss: '-600', + prdy_vrss_sign: '5', + cntg_vol: '5', + tday_rltv: '71.47', + prdy_ctrt: '-1.11', + }, + { + stck_cntg_hour: '155958', + stck_prpr: '53600', + prdy_vrss: '-600', + prdy_vrss_sign: '5', + cntg_vol: '1', + tday_rltv: '71.47', + prdy_ctrt: '-1.11', + }, + { + stck_cntg_hour: '155957', + stck_prpr: '53600', + prdy_vrss: '-600', + prdy_vrss_sign: '5', + cntg_vol: '1', + tday_rltv: '71.47', + prdy_ctrt: '-1.11', + }, + { + stck_cntg_hour: '155951', + stck_prpr: '53600', + prdy_vrss: '-600', + prdy_vrss_sign: '5', + cntg_vol: '20', + tday_rltv: '71.47', + prdy_ctrt: '-1.11', + }, + { + stck_cntg_hour: '155949', + stck_prpr: '53600', + prdy_vrss: '-600', + prdy_vrss_sign: '5', + cntg_vol: '1', + tday_rltv: '71.47', + prdy_ctrt: '-1.11', + }, + { + stck_cntg_hour: '155948', + stck_prpr: '53600', + prdy_vrss: '-600', + prdy_vrss_sign: '5', + cntg_vol: '1', + tday_rltv: '71.47', + prdy_ctrt: '-1.11', + }, + { + stck_cntg_hour: '155945', + stck_prpr: '53600', + prdy_vrss: '-600', + prdy_vrss_sign: '5', + cntg_vol: '131', + tday_rltv: '71.47', + prdy_ctrt: '-1.11', + }, + { + stck_cntg_hour: '155945', + stck_prpr: '53600', + prdy_vrss: '-600', + prdy_vrss_sign: '5', + cntg_vol: '6', + tday_rltv: '71.47', + prdy_ctrt: '-1.11', + }, + { + stck_cntg_hour: '155943', + stck_prpr: '53600', + prdy_vrss: '-600', + prdy_vrss_sign: '5', + cntg_vol: '6', + tday_rltv: '71.47', + prdy_ctrt: '-1.11', + }, + { + stck_cntg_hour: '155939', + stck_prpr: '53600', + prdy_vrss: '-600', + prdy_vrss_sign: '5', + cntg_vol: '10', + tday_rltv: '71.47', + prdy_ctrt: '-1.11', + }, + { + stck_cntg_hour: '155937', + stck_prpr: '53600', + prdy_vrss: '-600', + prdy_vrss_sign: '5', + cntg_vol: '11', + tday_rltv: '71.47', + prdy_ctrt: '-1.11', + }, + { + stck_cntg_hour: '155937', + stck_prpr: '53600', + prdy_vrss: '-600', + prdy_vrss_sign: '5', + cntg_vol: '3', + tday_rltv: '71.47', + prdy_ctrt: '-1.11', + }, + { + stck_cntg_hour: '155936', + stck_prpr: '53600', + prdy_vrss: '-600', + prdy_vrss_sign: '5', + cntg_vol: '2', + tday_rltv: '71.47', + prdy_ctrt: '-1.11', + }, + { + stck_cntg_hour: '155936', + stck_prpr: '53600', + prdy_vrss: '-600', + prdy_vrss_sign: '5', + cntg_vol: '5', + tday_rltv: '71.47', + prdy_ctrt: '-1.11', + }, + { + stck_cntg_hour: '155935', + stck_prpr: '53600', + prdy_vrss: '-600', + prdy_vrss_sign: '5', + cntg_vol: '10', + tday_rltv: '71.47', + prdy_ctrt: '-1.11', + }, + { + stck_cntg_hour: '155933', + stck_prpr: '53600', + prdy_vrss: '-600', + prdy_vrss_sign: '5', + cntg_vol: '132', + tday_rltv: '71.47', + prdy_ctrt: '-1.11', + }, + { + stck_cntg_hour: '155933', + stck_prpr: '53600', + prdy_vrss: '-600', + prdy_vrss_sign: '5', + cntg_vol: '10', + tday_rltv: '71.47', + prdy_ctrt: '-1.11', + }, + { + stck_cntg_hour: '155933', + stck_prpr: '53600', + prdy_vrss: '-600', + prdy_vrss_sign: '5', + cntg_vol: '1', + tday_rltv: '71.47', + prdy_ctrt: '-1.11', + }, + { + stck_cntg_hour: '155931', + stck_prpr: '53600', + prdy_vrss: '-600', + prdy_vrss_sign: '5', + cntg_vol: '1', + tday_rltv: '71.47', + prdy_ctrt: '-1.11', + }, + { + stck_cntg_hour: '155930', + stck_prpr: '53600', + prdy_vrss: '-600', + prdy_vrss_sign: '5', + cntg_vol: '1', + tday_rltv: '71.47', + prdy_ctrt: '-1.11', + }, + { + stck_cntg_hour: '155930', + stck_prpr: '53600', + prdy_vrss: '-600', + prdy_vrss_sign: '5', + cntg_vol: '1', + tday_rltv: '71.47', + prdy_ctrt: '-1.11', + }, + { + stck_cntg_hour: '155930', + stck_prpr: '53600', + prdy_vrss: '-600', + prdy_vrss_sign: '5', + cntg_vol: '10', + tday_rltv: '71.47', + prdy_ctrt: '-1.11', + }, + { + stck_cntg_hour: '155928', + stck_prpr: '53600', + prdy_vrss: '-600', + prdy_vrss_sign: '5', + cntg_vol: '20', + tday_rltv: '71.47', + prdy_ctrt: '-1.11', + }, + { + stck_cntg_hour: '155927', + stck_prpr: '53600', + prdy_vrss: '-600', + prdy_vrss_sign: '5', + cntg_vol: '2', + tday_rltv: '71.47', + prdy_ctrt: '-1.11', + }, + { + stck_cntg_hour: '155926', + stck_prpr: '53600', + prdy_vrss: '-600', + prdy_vrss_sign: '5', + cntg_vol: '5', + tday_rltv: '71.47', + prdy_ctrt: '-1.11', + }, + { + stck_cntg_hour: '155926', + stck_prpr: '53600', + prdy_vrss: '-600', + prdy_vrss_sign: '5', + cntg_vol: '1', + tday_rltv: '71.47', + prdy_ctrt: '-1.11', + }, + { + stck_cntg_hour: '155926', + stck_prpr: '53600', + prdy_vrss: '-600', + prdy_vrss_sign: '5', + cntg_vol: '2', + tday_rltv: '71.47', + prdy_ctrt: '-1.11', + }, + { + stck_cntg_hour: '155921', + stck_prpr: '53600', + prdy_vrss: '-600', + prdy_vrss_sign: '5', + cntg_vol: '2', + tday_rltv: '71.47', + prdy_ctrt: '-1.11', + }, + { + stck_cntg_hour: '155921', + stck_prpr: '53600', + prdy_vrss: '-600', + prdy_vrss_sign: '5', + cntg_vol: '1', + tday_rltv: '71.47', + prdy_ctrt: '-1.11', + }, + { + stck_cntg_hour: '155920', + stck_prpr: '53600', + prdy_vrss: '-600', + prdy_vrss_sign: '5', + cntg_vol: '5', + tday_rltv: '71.47', + prdy_ctrt: '-1.11', + }, + ], + rt_cd: '0', + msg_cd: 'MCA00000', + msg1: '정상처리 되었습니다.', +}; diff --git a/BE/src/stock/trade/history/stock-trade-history.controller.ts b/BE/src/stock/trade/history/stock-trade-history.controller.ts index 4d17d6f9..18b8b3c2 100644 --- a/BE/src/stock/trade/history/stock-trade-history.controller.ts +++ b/BE/src/stock/trade/history/stock-trade-history.controller.ts @@ -1,10 +1,14 @@ -import { Observable } from 'rxjs'; -import { Controller, Get, Param, Sse } from '@nestjs/common'; -import { ApiOperation, ApiParam, ApiResponse, ApiTags } from '@nestjs/swagger'; +import { Controller, Get, Param, Query } from '@nestjs/common'; +import { + ApiOperation, + ApiParam, + ApiQuery, + ApiResponse, + ApiTags, +} from '@nestjs/swagger'; import { StockTradeHistoryService } from './stock-trade-history.service'; import { TodayStockTradeHistoryDataDto } from './dto/today-stock-trade-history-data.dto'; import { DailyStockTradeHistoryDataDto } from './dto/daily-stock-trade-history-data.dto'; -import { SseEvent } from './interface/sse-event'; import { StockPriceSocketService } from '../../../stockSocket/stock-price-socket.service'; @ApiTags('주식현재가 체결 조회 API') @@ -51,50 +55,27 @@ export class StockTradeHistoryController { return this.stockTradeHistoryService.getDailyStockTradeHistory(stockCode); } - @Sse(':stockCode/today-sse') - @ApiOperation({ summary: '단일 주식 종목에 대한 실시간 체결 데이터 스트림' }) - @ApiParam({ - name: 'stockCode', - required: true, - description: - '종목 코드\n\n' + - '(ex) 005930 삼성전자 / 005380 현대차 / 001500 현대차증권', - }) - @ApiResponse({ - status: 200, - description: - '단일 주식 종목에 대한 주식현재가 체결값 실시간 데이터 조회 성공', - type: TodayStockTradeHistoryDataDto, - }) - streamTradeHistory(@Param('stockCode') stockCode: string) { - this.stockPriceSocketService.subscribeByCode(stockCode); - - return new Observable((subscriber) => { - const subscription = this.stockPriceSocketService - .getTradeDataStream(stockCode) - .subscribe(subscriber); - - return () => { - this.stockPriceSocketService.unsubscribeByCode(stockCode); - subscription.unsubscribe(); - }; - }); - } - - @Get(':stockCode/unsubscribe') + @Get('/unsubscribe') @ApiOperation({ summary: '페이지를 벗어날 때 구독을 취소하기 위한 API' }) - @ApiParam({ + @ApiQuery({ name: 'stockCode', required: true, description: '종목 코드\n\n' + '(ex) 005930 삼성전자 / 005380 현대차 / 001500 현대차증권', + schema: { + type: 'array', + items: { + type: 'string', + }, + }, }) @ApiResponse({ status: 200, description: '구독 취소 성공', }) - unsubscribeCode(@Param('stockCode') stockCode: string) { - this.stockPriceSocketService.unsubscribeByCode(stockCode); + async unsubscribeCode(@Query('stockCode') stockCode: string | string[]) { + const stockCodeArray = Array.isArray(stockCode) ? stockCode : [stockCode]; + await this.stockPriceSocketService.unsubscribeByCode(stockCodeArray); } } diff --git a/BE/src/stock/trade/history/stock-trade-history.service.spec.ts b/BE/src/stock/trade/history/stock-trade-history.service.spec.ts new file mode 100644 index 00000000..f8477862 --- /dev/null +++ b/BE/src/stock/trade/history/stock-trade-history.service.spec.ts @@ -0,0 +1,80 @@ +import { Test } from '@nestjs/testing'; +import { KoreaInvestmentDomainService } from '../../../common/koreaInvestment/korea-investment.domain-service'; +import { StockTradeHistoryService } from './stock-trade-history.service'; +import { StockPriceSocketService } from '../../../stockSocket/stock-price-socket.service'; +import { STOCK_TRADE_HISTORY_TODAY_MOCK } from './mockdata/stock-trade-history-today.mockdata'; +import { STOCK_TRADE_HISTORY_DAILY_MOCK } from './mockdata/stock-trade-history-daily.mockdata'; + +describe('stock trade history test', () => { + let stockTradeHistoryService: StockTradeHistoryService; + let koreaInvestmentDomainService: KoreaInvestmentDomainService; + let stockPriceSocketService: StockPriceSocketService; + + beforeEach(async () => { + const module = await Test.createTestingModule({ + providers: [ + StockTradeHistoryService, + KoreaInvestmentDomainService, + { + provide: StockPriceSocketService, + useValue: { + subscribeByCode: jest.fn(), // 이 메서드만 모킹합니다. + }, + }, + ], + }).compile(); + + stockTradeHistoryService = module.get(StockTradeHistoryService); + koreaInvestmentDomainService = module.get(KoreaInvestmentDomainService); + stockPriceSocketService = module.get(StockPriceSocketService); + + jest + .spyOn(koreaInvestmentDomainService, 'getAccessToken') + .mockResolvedValue('accessToken'); + }); + + it('특정 주식의 현재가 체결 데이터를 반환한다.', async () => { + jest + .spyOn(koreaInvestmentDomainService, 'requestApi') + .mockResolvedValueOnce(STOCK_TRADE_HISTORY_TODAY_MOCK); + jest.spyOn(stockPriceSocketService, 'subscribeByCode').mockResolvedValue(); + + const response = + await stockTradeHistoryService.getTodayStockTradeHistory('005930'); + + const expected = { + stck_cntg_hour: '155958', + stck_prpr: '53600', + prdy_vrss_sign: '5', + cntg_vol: '5', + prdy_ctrt: '-1.11', + }; + + expect(response[0]).toEqual(expected); + }); + + it('특정 주식의 일자별 체결 데이터를 반환한다.', async () => { + jest + .spyOn(koreaInvestmentDomainService, 'requestApi') + .mockResolvedValueOnce(STOCK_TRADE_HISTORY_DAILY_MOCK); + + const response = + await stockTradeHistoryService.getDailyStockTradeHistory('005930'); + + const expected = { + stck_bsop_date: '20241202', + stck_oprc: '54300', + stck_hgpr: '54400', + stck_lwpr: '53100', + stck_clpr: '53600', + acml_vol: '21924956', + prdy_vrss_sign: '5', + prdy_ctrt: '-1.11', + }; + + expect(response[0]).toEqual(expected); + expect(response[0].stck_bsop_date).toEqual('20241202'); + expect(response[1].stck_bsop_date).toEqual('20241129'); + expect(response[2].stck_bsop_date).toEqual('20241128'); + }); +}); diff --git a/BE/src/stock/trade/history/stock-trade-history.service.ts b/BE/src/stock/trade/history/stock-trade-history.service.ts index 7fa7e60c..198e9ab4 100644 --- a/BE/src/stock/trade/history/stock-trade-history.service.ts +++ b/BE/src/stock/trade/history/stock-trade-history.service.ts @@ -36,7 +36,7 @@ export class StockTradeHistoryService { ); try { - this.stockPriceSocketService.subscribeByCode(stockCode); + await this.stockPriceSocketService.subscribeByCode(stockCode); } catch (e) { throw new InternalServerErrorException(e); } diff --git a/BE/src/stockSocket/stock-price-socket.service.ts b/BE/src/stockSocket/stock-price-socket.service.ts index 23bda31f..8897ea1e 100644 --- a/BE/src/stockSocket/stock-price-socket.service.ts +++ b/BE/src/stockSocket/stock-price-socket.service.ts @@ -1,24 +1,25 @@ import { Injectable, InternalServerErrorException } from '@nestjs/common'; -import { filter, map, Observable, Subject } from 'rxjs'; +import { Cron } from '@nestjs/schedule'; import { BaseSocketDomainService } from '../common/websocket/base-socket.domain-service'; -import { SocketGateway } from '../common/websocket/socket.gateway'; import { BaseStockSocketDomainService } from './base-stock-socket.domain-service'; +import { RedisDomainService } from '../common/redis/redis.domain-service'; +import { SocketGateway } from '../common/websocket/socket.gateway'; import { Order } from '../stock/order/stock-order.entity'; +import { StockExecuteOrderRepository } from './stock-execute-order.repository'; import { StatusType } from '../stock/order/enum/status-type'; import { TodayStockTradeHistoryDataDto } from '../stock/trade/history/dto/today-stock-trade-history-data.dto'; import { StockDetailSocketDataDto } from '../stock/trade/history/dto/stock-detail-socket-data.dto'; -import { StockExecuteOrderRepository } from './stock-execute-order.repository'; -import { SseEvent } from '../stock/trade/history/interface/sse-event'; @Injectable() export class StockPriceSocketService extends BaseStockSocketDomainService { private connection: { [key: string]: number } = {}; - private eventSubject = new Subject(); + private register: string[] = []; constructor( protected readonly socketGateway: SocketGateway, protected readonly baseSocketDomainService: BaseSocketDomainService, private readonly stockExecuteOrderRepository: StockExecuteOrderRepository, + private readonly redisDomainService: RedisDomainService, ) { super(socketGateway, baseSocketDomainService, 'H0STCNT0'); } @@ -55,12 +56,6 @@ export class StockPriceSocketService extends BaseStockSocketDomainService { prdy_ctrt: data[5], }; - this.eventSubject.next({ - data: JSON.stringify({ - tradeData, - }), - }); - this.socketGateway.sendStockIndexValueToClient( `trade-history/${data[0]}`, tradeData, @@ -72,34 +67,67 @@ export class StockPriceSocketService extends BaseStockSocketDomainService { ); } - getTradeDataStream(targetStockCode: string): Observable { - return this.eventSubject.pipe( - filter((event: SseEvent) => { - const parsed = JSON.parse(event.data); - return parsed.tradeData.stck_shrn_iscd === targetStockCode; - }), - map((event: SseEvent) => event), - ); - } + async subscribeByCode(trKey: string) { + // 아무 서버도 한투와 구독 중이지 않을때 + if (!(await this.redisDomainService.exists(trKey))) { + this.baseSocketDomainService.registerCode(this.TR_ID, trKey); + await this.redisDomainService.subscribe(`stock/${trKey}`); + this.register.push(trKey); + await this.redisDomainService.set(trKey, 1); + this.connection[trKey] = 1; - subscribeByCode(trKey: string) { - this.baseSocketDomainService.registerCode(this.TR_ID, trKey); + return; + } + + // 특정 서버는 한투와 구독 중일테니까... + await this.redisDomainService.increment(trKey); + + // 여기 서버에서 최초로 구독을 시작한다면, + if (!this.connection[trKey]) { + await this.redisDomainService.subscribe(`stock/${trKey}`); + this.connection[trKey] = 1; - if (this.connection[trKey]) { - this.connection[trKey] += 1; return; } - this.connection[trKey] = 1; + + this.connection[trKey] += 1; + } + + async unsubscribeByCode(trKeys: string[]) { + for (const trKey of trKeys) { + if (!this.connection[trKey]) return; + + // redis 내의 key(종목코드)에 대한 value -= 1; + await this.redisDomainService.decrement(trKey); + + // 현재 서버에서 구독 중이고 구독 유지해야 할 때 + if (this.connection[trKey] > 1) { + this.connection[trKey] -= 1; + return; + } + + // 현재 서버에서 모든 연결이 종료됐을 경우 + if (this.connection[trKey] === 1) { + delete this.connection[trKey]; + await this.redisDomainService.unsubscribe(`stock/${trKey}`); + } + + // 레디스 내에서 모든 연결이 종료됐을 경우 + if ((await this.redisDomainService.get(trKey)) === 0) { + await this.redisDomainService.del(trKey); + } + } } - unsubscribeByCode(trKey: string) { - if (!this.connection[trKey]) return; - if (this.connection[trKey] > 1) { - this.connection[trKey] -= 1; - return; + @Cron('*/5 * * * *') + async checkConnection() { + for (const trKey of this.register) { + if (!(await this.redisDomainService.exists(trKey))) { + this.baseSocketDomainService.unregisterCode(this.TR_ID, trKey); + const idx = this.register.indexOf(trKey); + if (idx) this.register.splice(idx, 1); + } } - delete this.connection[trKey]; - this.baseSocketDomainService.unregisterCode(this.TR_ID, trKey); } private async checkExecutableOrder(stockCode: string, value) { @@ -122,6 +150,6 @@ export class StockPriceSocketService extends BaseStockSocketDomainService { status: StatusType.PENDING, })) ) - this.unsubscribeByCode(stockCode); + await this.unsubscribeByCode([stockCode]); } } diff --git a/BE/test/stock/index/stock.index.list.e2e-spec.ts b/BE/test/stock/index/stock.index.list.e2e-spec.ts deleted file mode 100644 index 9a844581..00000000 --- a/BE/test/stock/index/stock.index.list.e2e-spec.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { Test } from '@nestjs/testing'; -import axios from 'axios'; -import { StockIndexService } from '../../../src/stock/index/stock-index.service'; -import { STOCK_INDEX_LIST_MOCK } from './mockdata/stock.index.list.mockdata'; - -jest.mock('axios'); - -describe('stock index list test', () => { - let stockIndexService: StockIndexService; - - beforeEach(async () => { - const module = await Test.createTestingModule({ - providers: [StockIndexService], - }).compile(); - - stockIndexService = module.get(StockIndexService); - }); - - it('주가 지수 차트 조회 API에서 정상적인 데이터를 조회한 경우, 형식에 맞춰 정상적으로 반환한다.', async () => { - (axios.get as jest.Mock).mockResolvedValue( - STOCK_INDEX_LIST_MOCK.VALID_DATA, - ); - - expect( - await stockIndexService.getDomesticStockIndexListByCode( - 'code', - 'accessToken', - ), - ).toEqual({ - code: 'code', - chart: [ - { - time: STOCK_INDEX_LIST_MOCK.VALID_DATA.data.output[0].bsop_hour, - value: STOCK_INDEX_LIST_MOCK.VALID_DATA.data.output[0].bstp_nmix_prpr, - }, - ], - }); - }); - - it('주가 지수 차트 조회 API에서 데이터를 조회하지 못한 경우, 에러를 발생시킨다.', async () => { - (axios.get as jest.Mock).mockResolvedValue( - STOCK_INDEX_LIST_MOCK.INVALID_DATA, - ); - - await expect( - stockIndexService.getDomesticStockIndexListByCode('code', 'accessToken'), - ).rejects.toThrow('데이터를 정상적으로 조회하지 못했습니다.'); - }); -}); diff --git a/BE/test/stock/index/stock.index.value.e2e-spec.ts b/BE/test/stock/index/stock.index.value.e2e-spec.ts deleted file mode 100644 index 2008f2e4..00000000 --- a/BE/test/stock/index/stock.index.value.e2e-spec.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { Test } from '@nestjs/testing'; -import axios from 'axios'; -import { StockIndexService } from '../../../src/stock/index/stock-index.service'; -import { STOCK_INDEX_VALUE_MOCK } from './mockdata/stock.index.value.mockdata'; - -jest.mock('axios'); - -describe('stock index list test', () => { - let stockIndexService: StockIndexService; - - beforeEach(async () => { - const module = await Test.createTestingModule({ - providers: [StockIndexService], - }).compile(); - - stockIndexService = module.get(StockIndexService); - }); - - it('주가 지수 값 조회 API에서 정상적인 데이터를 조회한 경우, 형식에 맞춰 정상적으로 반환한다.', async () => { - (axios.get as jest.Mock).mockResolvedValue( - STOCK_INDEX_VALUE_MOCK.VALID_DATA, - ); - - expect( - await stockIndexService.getDomesticStockIndexValueByCode( - 'code', - 'accessToken', - ), - ).toEqual({ - code: 'code', - value: STOCK_INDEX_VALUE_MOCK.VALID_DATA.data.output.bstp_nmix_prpr, - diff: STOCK_INDEX_VALUE_MOCK.VALID_DATA.data.output.bstp_nmix_prdy_vrss, - diffRate: - STOCK_INDEX_VALUE_MOCK.VALID_DATA.data.output.bstp_nmix_prdy_ctrt, - sign: STOCK_INDEX_VALUE_MOCK.VALID_DATA.data.output.prdy_vrss_sign, - }); - }); - - it('주가 지수 값 조회 API에서 데이터를 조회하지 못한 경우, 에러를 발생시킨다.', async () => { - (axios.get as jest.Mock).mockResolvedValue( - STOCK_INDEX_VALUE_MOCK.INVALID_DATA, - ); - - await expect( - stockIndexService.getDomesticStockIndexValueByCode('code', 'accessToken'), - ).rejects.toThrow('데이터를 정상적으로 조회하지 못했습니다.'); - }); -}); diff --git a/FE/src/assets/favicon.ico b/FE/src/assets/favicon.ico index 2dccc075..8efdf207 100644 Binary files a/FE/src/assets/favicon.ico and b/FE/src/assets/favicon.ico differ