From 8ede780f742d5bf403d80ed8fc46ee035496ff00 Mon Sep 17 00:00:00 2001 From: anjdydhody Date: Mon, 2 Dec 2024 14:37:00 +0900 Subject: [PATCH 01/35] =?UTF-8?q?=F0=9F=94=A7=20fix:=20stock=20index=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=95=88=20=EB=93=A4=EC=96=B4?= =?UTF-8?q?=EC=99=94=EC=9D=84=20=EB=95=8C=20=EC=98=88=EC=99=B8=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BE/src/stock/index/stock-index.service.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) 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( From 5d33dea4d90ff83cd80445e385f8ed90a8567584 Mon Sep 17 00:00:00 2001 From: anjdydhody Date: Mon, 2 Dec 2024 14:37:45 +0900 Subject: [PATCH 02/35] =?UTF-8?q?=E2=9C=85=20test:=20stock=20index=20servi?= =?UTF-8?q?ce=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=ED=86=B5=ED=95=A9=20=EB=B0=8F=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mockdata/stock.index.list.mockdata.ts | 0 .../mockdata/stock.index.value.mockdata.ts | 0 .../stock/index/stock.index.service.spec.ts | 86 +++++++++++++++++++ .../stock/index/stock.index.list.e2e-spec.ts | 49 ----------- .../stock/index/stock.index.value.e2e-spec.ts | 48 ----------- 5 files changed, 86 insertions(+), 97 deletions(-) rename BE/{test => src}/stock/index/mockdata/stock.index.list.mockdata.ts (100%) rename BE/{test => src}/stock/index/mockdata/stock.index.value.mockdata.ts (100%) create mode 100644 BE/src/stock/index/stock.index.service.spec.ts delete mode 100644 BE/test/stock/index/stock.index.list.e2e-spec.ts delete mode 100644 BE/test/stock/index/stock.index.value.e2e-spec.ts 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..205d76dd --- /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/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('데이터를 정상적으로 조회하지 못했습니다.'); - }); -}); From fd3bdde2695fd668085cd93288fd7ce1ce026701 Mon Sep 17 00:00:00 2001 From: sieun <147706431+sieunie@users.noreply.github.com> Date: Mon, 2 Dec 2024 15:18:55 +0900 Subject: [PATCH 03/35] =?UTF-8?q?Revert=20"[BE]=20=EC=A3=BC=EA=B0=80=20?= =?UTF-8?q?=EC=A7=80=EC=88=98=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BE/src/news/news.service.ts | 3 +- BE/src/stock/index/stock-index.service.ts | 12 +-- .../stock/index/stock.index.service.spec.ts | 86 ------------------ .../mockdata/stock.index.list.mockdata.ts | 0 .../mockdata/stock.index.value.mockdata.ts | 0 .../stock/index/stock.index.list.e2e-spec.ts | 49 ++++++++++ .../stock/index/stock.index.value.e2e-spec.ts | 48 ++++++++++ FE/src/assets/favicon.ico | Bin 68551 -> 1150 bytes README.md | 36 ++------ 9 files changed, 107 insertions(+), 127 deletions(-) delete mode 100644 BE/src/stock/index/stock.index.service.spec.ts rename BE/{src => test}/stock/index/mockdata/stock.index.list.mockdata.ts (100%) rename BE/{src => test}/stock/index/mockdata/stock.index.value.mockdata.ts (100%) create mode 100644 BE/test/stock/index/stock.index.list.e2e-spec.ts create mode 100644 BE/test/stock/index/stock.index.value.e2e-spec.ts 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/index/stock-index.service.ts b/BE/src/stock/index/stock-index.service.ts index 62dae6c1..f68aa9d1 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, InternalServerErrorException } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { StockIndexListChartElementDto } from './dto/stock-index-list-chart.element.dto'; import { StockIndexValueElementDto } from './dto/stock-index-value-element.dto'; import { @@ -87,11 +87,6 @@ export class StockIndexService { queryParams, ); - if (result.rt_cd !== '0') - throw new InternalServerErrorException( - '데이터를 정상적으로 조회하지 못했습니다.', - ); - return result.output.map((element) => { return new StockIndexListChartElementDto( element.bsop_hour, @@ -114,11 +109,6 @@ 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/index/stock.index.service.spec.ts b/BE/src/stock/index/stock.index.service.spec.ts deleted file mode 100644 index 205d76dd..00000000 --- a/BE/src/stock/index/stock.index.service.spec.ts +++ /dev/null @@ -1,86 +0,0 @@ -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/mockdata/stock.index.list.mockdata.ts b/BE/test/stock/index/mockdata/stock.index.list.mockdata.ts similarity index 100% rename from BE/src/stock/index/mockdata/stock.index.list.mockdata.ts rename to BE/test/stock/index/mockdata/stock.index.list.mockdata.ts diff --git a/BE/src/stock/index/mockdata/stock.index.value.mockdata.ts b/BE/test/stock/index/mockdata/stock.index.value.mockdata.ts similarity index 100% rename from BE/src/stock/index/mockdata/stock.index.value.mockdata.ts rename to BE/test/stock/index/mockdata/stock.index.value.mockdata.ts diff --git a/BE/test/stock/index/stock.index.list.e2e-spec.ts b/BE/test/stock/index/stock.index.list.e2e-spec.ts new file mode 100644 index 00000000..9a844581 --- /dev/null +++ b/BE/test/stock/index/stock.index.list.e2e-spec.ts @@ -0,0 +1,49 @@ +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 new file mode 100644 index 00000000..2008f2e4 --- /dev/null +++ b/BE/test/stock/index/stock.index.value.e2e-spec.ts @@ -0,0 +1,48 @@ +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 2dccc07558a18fe9c2d0fff6bbbfefc408edf3e2..8efdf207e8ca2d71b365925c0050111f2e649e09 100644 GIT binary patch literal 1150 zcmb7EOHUI~6dn_Q04vun-Me;U;?5PUT)B1SLKCAgF)>Dsfdq+B9L0!AY30>gtxOA* z^f83uQ0Tj*57f3I9j4P#XrWB!^UfWn)J7NH$+?en&i9>f&fE;coFGq64@2hD%$1W2 zbBbY@GsK9CcyvEm7>2~U2ReqTs`R9-k|^R^Dow5Qa}$c9=&@F-197}Hu-QZs*5Q}< z_>=|5j=)LGG5iA7XDpJdp*H_95>Ssg6Tvl@ANC{b^I~31NfhF*PuV zpr6+{x^y&~O-w$10x=dvrI3TTk%FAhKrUt>eP2g8pM{*uz%n?5T6L$b!>FB!#j$L+ zBP|5*BNf5Hu7o{#6ZPsZH0xy)({Zc^c&yF45EE7?zC|=>rj57|#PXaAWpNAKxCyPp z8V(v2YO1;qZtNYli+Kcn9)y>c+jygXA-4hB=qTJa8v^bH8dsDi>3fsz1&;@As}(b^ zUL%)GQlC*}^5Pij57Mx`k!9WdPKG=yyk>ZVSW@zv_ zW|4`faHH=MhHl-4eQX>Kvjyzkd$@YB7pX`Bu4$`|?dZS3BX3KXo1Oyia*^C98udE% z8x5?Jz2C{fH93KD>Cf7fj7$bcj@+M-NByW)WFv&V-95Y=c#eb+qx!4zajc!BaU5w% zg~IT0TsyR2pT>imy%>LU9pS}Q>No1Ola4kBDtdL_#0mHb?Q?N4{2Ix@he8( nUxvlgNA940hyI199Hg$%6Ma=77PcWQ__0&fd)WOy_}}wy$eW}V literal 68551 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 EKbrO*7XSbN diff --git a/README.md b/README.md index 6d2c4d79..b55a23c6 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@
-

실시간 주식 데이터를 활용한 모의투자 경험을 통해 주식 투자에 대해 배울 수 있는 서비스

+

실시간 주식 데이터를 활용해 모의 투자를 경험하고, 친구들과 함께 성장할 수 있는 게임 서비스

--- @@ -15,10 +15,13 @@ - 처음이라 시작하기가 두려워요. - 자금이 부족해 다양한 시도를 못 해봤어요. - 주식의 전반적인 시스템을 공부하고 싶어요. +- 친구와 함께 투자를 시작하고 싶어요. ### 🎯 Juga를 통해 이런 걸 경험하세요! - 실제 주식 투자 전에 게임으로 먼저 경험해보세요. - 리스크 없이 투자 감각을 키워보세요. +- 친구들과 함께 성장하는 재미를 느껴보세요. +- 경쟁하고 정보도 나누며 더 즐겁게 배워보세요. ### [🚀 시작하기](https://juga.kro.kr/) @@ -32,42 +35,17 @@ ### 주의사항 +- 카카오 로그인 기능은 현재 심사 중으로 사용이 불가능합니다. - 실제 금전적 거래는 이루어지지 않는 모의투자 서비스입니다. ## ⭐️ 프로젝트 기능 소개 - - -### 메인 페이지 -![juga_main](https://github.com/user-attachments/assets/abb04197-ae88-4877-be53-2ca71ad3e57b) - -- 메인 페이지에서 코스피, 코스닥 등 실시간 주가 지수를 확인할 수 있습니다. -- 상승률/하락률 TOP5 종목을 주가지수 별로 확인할 수 있습니다. -- 오늘 실시간 주요 뉴스를 확인할 수 있습니다. - -### 주식 상세 페이지 -![juga_detail](https://github.com/user-attachments/assets/14ed36ae-085e-4899-a314-8ece85236a55) - -- 해당 주식에 대한 정보를 차트로 확인할 수 있습니다. -- 일별, 실시간 시세를 확인할 수 있습니다. -- 매수, 매도 요청을 할 수 있습니다. - ### 주식 차트 ![화면 기록 2024-11-28 오후 6 40 30](https://github.com/user-attachments/assets/6d36b0d9-2db2-4018-a7f3-2c12fb586fd0) -- 일, 주, 월, 년 단위로 주식 차트를 확인할 수 있습니다. +- 일, 별, 월, 년 단위로 주식 차트를 확인할 수 있습니다. - 이동평균선 정보를 활용해 해당 주식의 추이를 더 자세히 확인할 수 있습니다. -- 라이브러리를 사용하지 않고 canvas를 활용해 직접 구현했습니다. - -### 마이페이지 -![juga_mypage](https://github.com/user-attachments/assets/8cdfa089-ac26-40a0-8d19-7a56a0f3c6e7) - -- 현재 자산 현황, 투자 성과를 확인할 수 있습니다. -- 자신이 매수한 주식 정보들을 확인할 수 있습니다. -- 주문 요청 현황 탭에서 주문 요청한 주식들을 확인하고 요청을 취소할 수 있습니다. -- 즐겨찾기 탭에서 주식 상세 페이지에서 좋아요한 주식들을 확인할 수 있습니다. -- 내 정보 탭에서 자신의 닉네임을 변경할 수 있습니다. - +- 라이브러리를 사용하지 않고 canvas를 활용해 직접 구현했습니다. ### 로그인 ![image (14)](https://github.com/user-attachments/assets/9968ef08-cbf8-41fd-bfdc-8ca25dd8d80c) From 2847ff3b2334563fe3ad6253c5d4c6c9d13a9847 Mon Sep 17 00:00:00 2001 From: anjdydhody Date: Mon, 2 Dec 2024 15:30:33 +0900 Subject: [PATCH 04/35] =?UTF-8?q?=F0=9F=94=A7=20fix:=20stock=20index=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=95=88=20=EB=93=A4=EC=96=B4?= =?UTF-8?q?=EC=99=94=EC=9D=84=20=EB=95=8C=20=EC=98=88=EC=99=B8=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BE/src/stock/index/stock-index.service.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) 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( From 675f1c74f3b5f2b49dcc9b2f041f31eda27ef59b Mon Sep 17 00:00:00 2001 From: anjdydhody Date: Mon, 2 Dec 2024 15:30:43 +0900 Subject: [PATCH 05/35] =?UTF-8?q?=E2=9C=85=20test:=20stock=20index=20servi?= =?UTF-8?q?ce=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=ED=86=B5=ED=95=A9=20=EB=B0=8F=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mockdata/stock.index.list.mockdata.ts | 0 .../mockdata/stock.index.value.mockdata.ts | 0 .../stock/index/stock.index.service.spec.ts | 86 +++++++++++++++++++ .../stock/index/stock.index.list.e2e-spec.ts | 49 ----------- .../stock/index/stock.index.value.e2e-spec.ts | 48 ----------- 5 files changed, 86 insertions(+), 97 deletions(-) rename BE/{test => src}/stock/index/mockdata/stock.index.list.mockdata.ts (100%) rename BE/{test => src}/stock/index/mockdata/stock.index.value.mockdata.ts (100%) create mode 100644 BE/src/stock/index/stock.index.service.spec.ts delete mode 100644 BE/test/stock/index/stock.index.list.e2e-spec.ts delete mode 100644 BE/test/stock/index/stock.index.value.e2e-spec.ts 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..205d76dd --- /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/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('데이터를 정상적으로 조회하지 못했습니다.'); - }); -}); From 3bfb649c8056e5a12b5ebfd6f3fb1e2c1a4e411e Mon Sep 17 00:00:00 2001 From: JIN Date: Mon, 2 Dec 2024 16:37:20 +0900 Subject: [PATCH 06/35] =?UTF-8?q?=E2=9C=85=20test:=20topfive=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=ED=85=8C=EC=8A=A4=ED=8A=B8=EB=A5=BC=20=EC=9C=84?= =?UTF-8?q?=ED=95=9C=20=EB=AA=A9=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EB=B0=8F=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=A7=84?= =?UTF-8?q?=ED=96=89#237?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../topfive/stock-topfive-high.mock-data.ts | 787 ++++++++++++++++++ .../topfive/stock-topfive-low.mock-data.ts | 787 ++++++++++++++++++ BE/src/stock/topfive/stock-topfive.spec.ts | 48 ++ 3 files changed, 1622 insertions(+) create mode 100644 BE/src/stock/topfive/stock-topfive-high.mock-data.ts create mode 100644 BE/src/stock/topfive/stock-topfive-low.mock-data.ts create mode 100644 BE/src/stock/topfive/stock-topfive.spec.ts diff --git a/BE/src/stock/topfive/stock-topfive-high.mock-data.ts b/BE/src/stock/topfive/stock-topfive-high.mock-data.ts new file mode 100644 index 00000000..528c9cf7 --- /dev/null +++ b/BE/src/stock/topfive/stock-topfive-high.mock-data.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/stock-topfive-low.mock-data.ts b/BE/src/stock/topfive/stock-topfive-low.mock-data.ts new file mode 100644 index 00000000..dee7888f --- /dev/null +++ b/BE/src/stock/topfive/stock-topfive-low.mock-data.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.spec.ts b/BE/src/stock/topfive/stock-topfive.spec.ts new file mode 100644 index 00000000..5557159d --- /dev/null +++ b/BE/src/stock/topfive/stock-topfive.spec.ts @@ -0,0 +1,48 @@ +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 './stock-topfive-high.mock-data'; +import { STOCK_TOP_FIVE_LOW_MOCK } from './stock-topfive-low.mock-data'; +import { MarketType } from '../enum/market-type'; + +jest.mock('axios'); + +describe('stock index list 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('한선엔지니어링'); + }); +}); From 4cd8976fa0f84d80009055cc6698f9faf36620ea Mon Sep 17 00:00:00 2001 From: JIN Date: Mon, 2 Dec 2024 16:51:21 +0900 Subject: [PATCH 07/35] =?UTF-8?q?=F0=9F=9A=9A=20rename:=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=EB=AA=85=20=EB=B0=8F=20=EB=94=94=EB=A0=89=ED=84=B0?= =?UTF-8?q?=EB=A6=AC=20=ED=86=B5=EC=9D=BC#237?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../stock-topfive-high.mockdata.ts} | 0 .../stock-topfive-low.mockdata.ts} | 0 BE/src/stock/topfive/stock-topfive.spec.ts | 4 ++-- 3 files changed, 2 insertions(+), 2 deletions(-) rename BE/src/stock/topfive/{stock-topfive-high.mock-data.ts => mockdata/stock-topfive-high.mockdata.ts} (100%) rename BE/src/stock/topfive/{stock-topfive-low.mock-data.ts => mockdata/stock-topfive-low.mockdata.ts} (100%) diff --git a/BE/src/stock/topfive/stock-topfive-high.mock-data.ts b/BE/src/stock/topfive/mockdata/stock-topfive-high.mockdata.ts similarity index 100% rename from BE/src/stock/topfive/stock-topfive-high.mock-data.ts rename to BE/src/stock/topfive/mockdata/stock-topfive-high.mockdata.ts diff --git a/BE/src/stock/topfive/stock-topfive-low.mock-data.ts b/BE/src/stock/topfive/mockdata/stock-topfive-low.mockdata.ts similarity index 100% rename from BE/src/stock/topfive/stock-topfive-low.mock-data.ts rename to BE/src/stock/topfive/mockdata/stock-topfive-low.mockdata.ts diff --git a/BE/src/stock/topfive/stock-topfive.spec.ts b/BE/src/stock/topfive/stock-topfive.spec.ts index 5557159d..a807fbf1 100644 --- a/BE/src/stock/topfive/stock-topfive.spec.ts +++ b/BE/src/stock/topfive/stock-topfive.spec.ts @@ -2,8 +2,8 @@ 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 './stock-topfive-high.mock-data'; -import { STOCK_TOP_FIVE_LOW_MOCK } from './stock-topfive-low.mock-data'; +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'; jest.mock('axios'); From 4cfba3be7523f4dd9433ca16e86c9d509d0374da Mon Sep 17 00:00:00 2001 From: JIN Date: Mon, 2 Dec 2024 16:55:17 +0900 Subject: [PATCH 08/35] =?UTF-8?q?=F0=9F=9A=9A=20rename:=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=EB=AA=85=EC=97=90=20=EB=88=84=EB=9D=BD=EB=90=98?= =?UTF-8?q?=EC=96=B4=EC=9E=88=EB=8D=98=20service=20=EC=B6=94=EA=B0=80#237?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{stock-topfive.spec.ts => stock-topfive.service.spec.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename BE/src/stock/topfive/{stock-topfive.spec.ts => stock-topfive.service.spec.ts} (100%) diff --git a/BE/src/stock/topfive/stock-topfive.spec.ts b/BE/src/stock/topfive/stock-topfive.service.spec.ts similarity index 100% rename from BE/src/stock/topfive/stock-topfive.spec.ts rename to BE/src/stock/topfive/stock-topfive.service.spec.ts From 361389a230720d97bc071748d53cf1649443d9c7 Mon Sep 17 00:00:00 2001 From: JIN Date: Mon, 2 Dec 2024 17:15:29 +0900 Subject: [PATCH 09/35] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20chore:=20=EB=B6=88?= =?UTF-8?q?=ED=95=84=EC=9A=94=ED=95=9C=20=EC=BD=94=EB=93=9C=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20=EB=B0=8F=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=20=EC=88=98=EC=A0=95#237?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BE/src/stock/topfive/stock-topfive.service.spec.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/BE/src/stock/topfive/stock-topfive.service.spec.ts b/BE/src/stock/topfive/stock-topfive.service.spec.ts index a807fbf1..1365d13f 100644 --- a/BE/src/stock/topfive/stock-topfive.service.spec.ts +++ b/BE/src/stock/topfive/stock-topfive.service.spec.ts @@ -6,9 +6,7 @@ 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'; -jest.mock('axios'); - -describe('stock index list test', () => { +describe('stock topfive test', () => { let stockTopfiveService: StockTopfiveService; let koreaInvestmentDomainService: KoreaInvestmentDomainService; let highResponse: StockRankingDataDto[]; From b69ee2c968ce45d7145742048e463b40b30e54de Mon Sep 17 00:00:00 2001 From: anjdydhody Date: Mon, 2 Dec 2024 17:50:11 +0900 Subject: [PATCH 10/35] =?UTF-8?q?=E2=9C=85=20test:=20=EC=A3=BC=EC=8B=9D=20?= =?UTF-8?q?=EB=A7=A4=EC=88=98=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=9E=91=EC=84=B1=20#237?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../stock/order/stock-order.service.spec.ts | 125 ++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 BE/src/stock/order/stock-order.service.spec.ts 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..b5dc8c98 --- /dev/null +++ b/BE/src/stock/order/stock-order.service.spec.ts @@ -0,0 +1,125 @@ +import { Test } from '@nestjs/testing'; +import { BadRequestException } 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'; + +jest.mock('../../asset/asset.repository'); +jest.mock('./stock-order.repository'); + +describe('stock order test', () => { + let stockOrderService: StockOrderService; + let stockOrderRepository: StockOrderRepository; + let assetRepository: AssetRepository; + + beforeEach(async () => { + const mockStockOrderRepository = { + findBy: jest.fn(), + save: jest.fn(), + create: jest.fn(), + }; + const mockAssetRepository = { + findOneBy: jest.fn(), + }; + const mockStockPriceSocketService = { + subscribeByCode: jest.fn(), + }; + const mockUserStockRepository = {}; + + 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); + }); + + 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(), + }, + ]); + + const createMock = jest.fn(); + jest.spyOn(stockOrderRepository, 'create').mockImplementation(createMock); + + const saveMock = jest.fn(); + jest.spyOn(stockOrderRepository, 'save').mockImplementation(saveMock); + + await expect( + stockOrderService.buy(1, { + stock_code: '005930', + price: 1000, + amount: 1, + }), + ).rejects.toThrow(BadRequestException); + }); +}); From acc7ae2c937171706ad5efbd9192b4ff653d833b Mon Sep 17 00:00:00 2001 From: anjdydhody Date: Mon, 2 Dec 2024 18:04:24 +0900 Subject: [PATCH 11/35] =?UTF-8?q?=E2=9C=85=20test:=20=EC=A3=BC=EC=8B=9D=20?= =?UTF-8?q?=EB=A7=A4=EB=8F=84=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=9E=91=EC=84=B1=20#237?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../stock/order/stock-order.service.spec.ts | 75 +++++++++++++++++-- 1 file changed, 67 insertions(+), 8 deletions(-) diff --git a/BE/src/stock/order/stock-order.service.spec.ts b/BE/src/stock/order/stock-order.service.spec.ts index b5dc8c98..3f41eed7 100644 --- a/BE/src/stock/order/stock-order.service.spec.ts +++ b/BE/src/stock/order/stock-order.service.spec.ts @@ -15,6 +15,7 @@ describe('stock order test', () => { let stockOrderService: StockOrderService; let stockOrderRepository: StockOrderRepository; let assetRepository: AssetRepository; + let userStockRepository: UserStockRepository; beforeEach(async () => { const mockStockOrderRepository = { @@ -22,13 +23,9 @@ describe('stock order test', () => { save: jest.fn(), create: jest.fn(), }; - const mockAssetRepository = { - findOneBy: jest.fn(), - }; - const mockStockPriceSocketService = { - subscribeByCode: jest.fn(), - }; - const mockUserStockRepository = {}; + const mockAssetRepository = { findOneBy: jest.fn() }; + const mockStockPriceSocketService = { subscribeByCode: jest.fn() }; + const mockUserStockRepository = { findOneBy: jest.fn() }; const module = await Test.createTestingModule({ providers: [ @@ -46,6 +43,7 @@ describe('stock order test', () => { stockOrderService = module.get(StockOrderService); stockOrderRepository = module.get(StockOrderRepository); assetRepository = module.get(AssetRepository); + userStockRepository = module.get(UserStockRepository); }); it('충분한 자산을 가지고 특정 주식에 대해 매수를 요청할 경우, 요청이 DB에 정상적으로 등록된다.', async () => { @@ -108,14 +106,75 @@ describe('stock order test', () => { }, ]); + 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.buy(1, { + stockOrderService.sell(1, { stock_code: '005930', price: 1000, amount: 1, From a8336aef18ad679c317acc1a5b8c9fdd0e9b46fd Mon Sep 17 00:00:00 2001 From: anjdydhody Date: Mon, 2 Dec 2024 18:12:51 +0900 Subject: [PATCH 12/35] =?UTF-8?q?=E2=9C=85=20test:=20=EC=A3=BC=EB=AC=B8=20?= =?UTF-8?q?=EC=B7=A8=EC=86=8C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=9E=91=EC=84=B1=20#237?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../stock/order/stock-order.service.spec.ts | 74 ++++++++++++++++++- 1 file changed, 72 insertions(+), 2 deletions(-) diff --git a/BE/src/stock/order/stock-order.service.spec.ts b/BE/src/stock/order/stock-order.service.spec.ts index 3f41eed7..e0be9cd2 100644 --- a/BE/src/stock/order/stock-order.service.spec.ts +++ b/BE/src/stock/order/stock-order.service.spec.ts @@ -1,5 +1,9 @@ import { Test } from '@nestjs/testing'; -import { BadRequestException } from '@nestjs/common'; +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'; @@ -22,9 +26,15 @@ describe('stock order test', () => { 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() }; + const mockStockPriceSocketService = { + subscribeByCode: jest.fn(), + unsubscribeByCode: jest.fn(), + }; const mockUserStockRepository = { findOneBy: jest.fn() }; const module = await Test.createTestingModule({ @@ -181,4 +191,64 @@ describe('stock order test', () => { }), ).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, + ); + }); }); From 50f22168791b21c1c86ba4738a0fa3b88788ef51 Mon Sep 17 00:00:00 2001 From: anjdydhody Date: Mon, 2 Dec 2024 19:06:29 +0900 Subject: [PATCH 13/35] =?UTF-8?q?=F0=9F=9A=9A=20rename:=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EB=B0=8F=20=EB=AA=A9=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=ED=8C=8C=EC=9D=BC=EB=AA=85=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?#237?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...ck.index.list.mockdata.ts => stock-index-list.mockdata.ts} | 0 ....index.value.mockdata.ts => stock-index-value.mockdata.ts} | 0 ...tock.index.service.spec.ts => stock-index-service.spec.ts} | 4 ++-- 3 files changed, 2 insertions(+), 2 deletions(-) rename BE/src/stock/index/mockdata/{stock.index.list.mockdata.ts => stock-index-list.mockdata.ts} (100%) rename BE/src/stock/index/mockdata/{stock.index.value.mockdata.ts => stock-index-value.mockdata.ts} (100%) rename BE/src/stock/index/{stock.index.service.spec.ts => stock-index-service.spec.ts} (96%) diff --git a/BE/src/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/src/stock/index/mockdata/stock.index.list.mockdata.ts rename to BE/src/stock/index/mockdata/stock-index-list.mockdata.ts diff --git a/BE/src/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/src/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 similarity index 96% rename from BE/src/stock/index/stock.index.service.spec.ts rename to BE/src/stock/index/stock-index-service.spec.ts index 205d76dd..091977e3 100644 --- a/BE/src/stock/index/stock.index.service.spec.ts +++ b/BE/src/stock/index/stock-index-service.spec.ts @@ -2,8 +2,8 @@ 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 { 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'; From 749ad87e26157ebbb140926af34c29b16dbf22f9 Mon Sep 17 00:00:00 2001 From: anjdydhody Date: Mon, 2 Dec 2024 19:11:13 +0900 Subject: [PATCH 14/35] =?UTF-8?q?=F0=9F=9A=9A=20rename:=20service=20?= =?UTF-8?q?=EA=B5=AC=EB=B6=84=EC=9E=90=20.=EC=9C=BC=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20#237?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{stock-index-service.spec.ts => stock-index.service.spec.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename BE/src/stock/index/{stock-index-service.spec.ts => stock-index.service.spec.ts} (100%) diff --git a/BE/src/stock/index/stock-index-service.spec.ts b/BE/src/stock/index/stock-index.service.spec.ts similarity index 100% rename from BE/src/stock/index/stock-index-service.spec.ts rename to BE/src/stock/index/stock-index.service.spec.ts From f20dea2179ba421bffa851507fd8cce66e54a320 Mon Sep 17 00:00:00 2001 From: anjdydhody Date: Mon, 2 Dec 2024 19:15:39 +0900 Subject: [PATCH 15/35] =?UTF-8?q?=F0=9F=94=A7=20fix:=20=ED=95=84=EC=9A=94?= =?UTF-8?q?=20=EC=97=86=EB=8A=94=20=EB=AA=A8=ED=82=B9=20=EC=82=AD=EC=A0=9C?= =?UTF-8?q?=20#237?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BE/src/stock/order/stock-order.service.spec.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/BE/src/stock/order/stock-order.service.spec.ts b/BE/src/stock/order/stock-order.service.spec.ts index e0be9cd2..78d0b2b6 100644 --- a/BE/src/stock/order/stock-order.service.spec.ts +++ b/BE/src/stock/order/stock-order.service.spec.ts @@ -12,9 +12,6 @@ import { AssetRepository } from '../../asset/asset.repository'; import { TradeType } from './enum/trade-type'; import { StatusType } from './enum/status-type'; -jest.mock('../../asset/asset.repository'); -jest.mock('./stock-order.repository'); - describe('stock order test', () => { let stockOrderService: StockOrderService; let stockOrderRepository: StockOrderRepository; From 633b8850bd74593e331b759bac6e7f76e8f7ab0c Mon Sep 17 00:00:00 2001 From: JIN Date: Mon, 2 Dec 2024 19:46:36 +0900 Subject: [PATCH 16/35] =?UTF-8?q?=E2=9C=85=20test:=20trade-history=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=ED=85=8C=EC=8A=A4=ED=8A=B8=EB=A5=BC=20?= =?UTF-8?q?=EC=9C=84=ED=95=9C=20=EB=AA=A9=EB=8D=B0=EC=9D=B4=ED=84=B0=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EB=B0=8F=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=A7=84=ED=96=89#237?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../stock-trade-history-daily.mockdata.ts | 487 ++++++++++++++++++ .../stock-trade-history-today.mockdata.ts | 277 ++++++++++ .../stock-trade-history.service.spec.ts | 82 +++ 3 files changed, 846 insertions(+) create mode 100644 BE/src/stock/trade/history/mockdata/stock-trade-history-daily.mockdata.ts create mode 100644 BE/src/stock/trade/history/mockdata/stock-trade-history-today.mockdata.ts create mode 100644 BE/src/stock/trade/history/stock-trade-history.service.spec.ts 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.service.spec.ts b/BE/src/stock/trade/history/stock-trade-history.service.spec.ts new file mode 100644 index 00000000..363ecc94 --- /dev/null +++ b/BE/src/stock/trade/history/stock-trade-history.service.spec.ts @@ -0,0 +1,82 @@ +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') + .mockImplementation(() => {}); + + 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'); + }); +}); From 4d5ba504252513093bfb1ad61454864f3240e268 Mon Sep 17 00:00:00 2001 From: JIN Date: Mon, 2 Dec 2024 21:02:47 +0900 Subject: [PATCH 17/35] =?UTF-8?q?=E2=9C=85=20test:=20detail=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=ED=85=8C=EC=8A=A4=ED=8A=B8=EB=A5=BC=20=EC=9C=84?= =?UTF-8?q?=ED=95=9C=20=EB=AA=A9=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EB=B0=8F=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=A7=84?= =?UTF-8?q?=ED=96=89#237?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mockdata/stock-detail-chart.mockdata.ts | 1539 +++++++++++++++++ .../detail/mockdata/stock-detail.mockdata.ts | 86 + .../stock/detail/stock-detail.service.spec.ts | 100 ++ 3 files changed, 1725 insertions(+) create mode 100644 BE/src/stock/detail/mockdata/stock-detail-chart.mockdata.ts create mode 100644 BE/src/stock/detail/mockdata/stock-detail.mockdata.ts create mode 100644 BE/src/stock/detail/stock-detail.service.spec.ts 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..a89e8a24 --- /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 trade history 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'); + }); +}); From d32a30e91ac89f3e02d3cbc9bb2df6278b904094 Mon Sep 17 00:00:00 2001 From: anjdydhody Date: Mon, 2 Dec 2024 21:04:41 +0900 Subject: [PATCH 18/35] =?UTF-8?q?=E2=9C=85=20test:=20=EB=A7=A4=EC=88=98/?= =?UTF-8?q?=EB=A7=A4=EB=8F=84=20=EA=B0=80=EB=8A=A5=20=EC=88=98=EB=9F=89=20?= =?UTF-8?q?=EB=B0=8F=20=EA=B8=88=EC=95=A1=20=EC=A1=B0=ED=9A=8C=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80=20#237?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BE/src/asset/asset-service.spec.ts | 106 +++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 BE/src/asset/asset-service.spec.ts diff --git a/BE/src/asset/asset-service.spec.ts b/BE/src/asset/asset-service.spec.ts new file mode 100644 index 00000000..e7cdd9a4 --- /dev/null +++ b/BE/src/asset/asset-service.spec.ts @@ -0,0 +1,106 @@ +import { Test } from '@nestjs/testing'; +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'; + +describe('asset test', () => { + let assetService: AssetService; + let userStockRepository: UserStockRepository; + let assetRepository: AssetRepository; + let stockDetailService: StockDetailService; + let stockPriceSocketService: StockPriceSocketService; + + beforeEach(async () => { + const mockUserStockRepository = { findOneBy: jest.fn() }; + const mockAssetRepository = { + findAllPendingOrders: jest.fn(), + findOneBy: jest.fn(), + }; + const mockStockDetailService = {}; + const mockStockPriceSocketService = {}; + + 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); + stockPriceSocketService = module.get(StockPriceSocketService); + }); + + 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, + }); + }); +}); From c11dd04b21333fb93fe841bc70d2e24fa81541a0 Mon Sep 17 00:00:00 2001 From: JIN Date: Mon, 2 Dec 2024 21:05:13 +0900 Subject: [PATCH 19/35] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20chore:=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=9D=B4=EB=A6=84=20=EC=88=98=EC=A0=95#23?= =?UTF-8?q?7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BE/src/stock/detail/stock-detail.service.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BE/src/stock/detail/stock-detail.service.spec.ts b/BE/src/stock/detail/stock-detail.service.spec.ts index a89e8a24..cf8d5b1c 100644 --- a/BE/src/stock/detail/stock-detail.service.spec.ts +++ b/BE/src/stock/detail/stock-detail.service.spec.ts @@ -6,7 +6,7 @@ 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 trade history test', () => { +describe('stock detail test', () => { let stockDetailService: StockDetailService; let koreaInvestmentDomainService: KoreaInvestmentDomainService; let stockDetailRepository: StockDetailRepository; @@ -69,7 +69,7 @@ describe('stock trade history test', () => { expect(response).toEqual(expected); }); - it('특정 주식의 일자별 체결 데이터를 반환한다.', async () => { + it('특정 주식의 차트 데이터를 반환한다.', async () => { jest .spyOn(koreaInvestmentDomainService, 'requestApi') .mockResolvedValueOnce(STOCK_DETAIL_CHART_MOCK); From bdedb24a309adf87f4818aac3befb1d015a1b021 Mon Sep 17 00:00:00 2001 From: anjdydhody Date: Mon, 2 Dec 2024 21:56:08 +0900 Subject: [PATCH 20/35] =?UTF-8?q?=E2=9C=85=20test:=20=EB=A7=88=EC=9D=B4?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=A1=B0=ED=9A=8C=20API=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=20#237?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BE/src/asset/asset-service.spec.ts | 106 ++++++++++++++++++++++++++++- 1 file changed, 103 insertions(+), 3 deletions(-) diff --git a/BE/src/asset/asset-service.spec.ts b/BE/src/asset/asset-service.spec.ts index e7cdd9a4..96c165e8 100644 --- a/BE/src/asset/asset-service.spec.ts +++ b/BE/src/asset/asset-service.spec.ts @@ -1,4 +1,6 @@ import { Test } from '@nestjs/testing'; +import { ApiProperty } from '@nestjs/swagger'; +import { DeepPartial } from 'typeorm'; import { AssetService } from './asset.service'; import { UserStockRepository } from './user-stock.repository'; import { AssetRepository } from './asset.repository'; @@ -6,6 +8,10 @@ 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; @@ -15,13 +21,19 @@ describe('asset test', () => { let stockPriceSocketService: StockPriceSocketService; beforeEach(async () => { - const mockUserStockRepository = { findOneBy: jest.fn() }; + 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 = {}; - const mockStockPriceSocketService = {}; + const mockStockDetailService = { getInquirePrice: jest.fn() }; + const mockStockPriceSocketService = { subscribeByCode: jest.fn() }; const module = await Test.createTestingModule({ providers: [ @@ -103,4 +115,92 @@ describe('asset test', () => { 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); + }); }); From 1da593874ee9c3610659b31b7a754d330d49bf93 Mon Sep 17 00:00:00 2001 From: anjdydhody Date: Mon, 2 Dec 2024 22:01:12 +0900 Subject: [PATCH 21/35] =?UTF-8?q?=F0=9F=94=A7=20=EB=A6=B0=ED=8A=B8=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98=20=ED=95=B4=EA=B2=B0=20#237?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BE/src/asset/asset-service.spec.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/BE/src/asset/asset-service.spec.ts b/BE/src/asset/asset-service.spec.ts index 96c165e8..719ae277 100644 --- a/BE/src/asset/asset-service.spec.ts +++ b/BE/src/asset/asset-service.spec.ts @@ -1,5 +1,4 @@ import { Test } from '@nestjs/testing'; -import { ApiProperty } from '@nestjs/swagger'; import { DeepPartial } from 'typeorm'; import { AssetService } from './asset.service'; import { UserStockRepository } from './user-stock.repository'; @@ -18,7 +17,6 @@ describe('asset test', () => { let userStockRepository: UserStockRepository; let assetRepository: AssetRepository; let stockDetailService: StockDetailService; - let stockPriceSocketService: StockPriceSocketService; beforeEach(async () => { const mockUserStockRepository = { @@ -55,7 +53,6 @@ describe('asset test', () => { userStockRepository = module.get(UserStockRepository); assetRepository = module.get(AssetRepository); stockDetailService = module.get(StockDetailService); - stockPriceSocketService = module.get(StockPriceSocketService); }); it('보유 주식과 미체결 주문을 모두 반영한 매도 가능 주식 수를 반환한다.', async () => { From 576d8c73cf8552759315e75d121007b55bb50fbc Mon Sep 17 00:00:00 2001 From: JIN Date: Mon, 2 Dec 2024 22:12:19 +0900 Subject: [PATCH 22/35] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20SSE?= =?UTF-8?q?=20=EC=BD=94=EB=93=9C=20=EC=A0=9C=EA=B1=B0#250?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../stockSocket/stock-price-socket.service.ts | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/BE/src/stockSocket/stock-price-socket.service.ts b/BE/src/stockSocket/stock-price-socket.service.ts index 23bda31f..0df995f3 100644 --- a/BE/src/stockSocket/stock-price-socket.service.ts +++ b/BE/src/stockSocket/stock-price-socket.service.ts @@ -1,5 +1,4 @@ import { Injectable, InternalServerErrorException } from '@nestjs/common'; -import { filter, map, Observable, Subject } from 'rxjs'; import { BaseSocketDomainService } from '../common/websocket/base-socket.domain-service'; import { SocketGateway } from '../common/websocket/socket.gateway'; import { BaseStockSocketDomainService } from './base-stock-socket.domain-service'; @@ -8,12 +7,10 @@ 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(); constructor( protected readonly socketGateway: SocketGateway, @@ -55,12 +52,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,16 +63,6 @@ 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), - ); - } - subscribeByCode(trKey: string) { this.baseSocketDomainService.registerCode(this.TR_ID, trKey); From ffe39e2a83133ed02321f4767230df89b582c420 Mon Sep 17 00:00:00 2001 From: anjdydhody Date: Mon, 2 Dec 2024 22:17:56 +0900 Subject: [PATCH 23/35] =?UTF-8?q?=E2=9C=85=20test:=20=EC=A6=90=EA=B2=A8?= =?UTF-8?q?=EC=B0=BE=EA=B8=B0=20=EB=93=B1=EB=A1=9D=20API=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1=20?= =?UTF-8?q?#237?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bookmark/stock-bookmark.service.spec.ts | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 BE/src/stock/bookmark/stock-bookmark.service.spec.ts 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..f5371660 --- /dev/null +++ b/BE/src/stock/bookmark/stock-bookmark.service.spec.ts @@ -0,0 +1,66 @@ +import { Test } from '@nestjs/testing'; +import { BadRequestException } from '@nestjs/common'; +import { StockBookmarkRepository } from './stock-bookmark.repository'; +import { StockDetailService } from '../detail/stock-detail.service'; +import { StockBookmarkService } from './stock-bookmark.service'; + +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(), + }; + 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); + }); +}); From 9c7248a60fb0bee10da10dada169cabd0d4fbdd3 Mon Sep 17 00:00:00 2001 From: JIN Date: Mon, 2 Dec 2024 22:25:26 +0900 Subject: [PATCH 24/35] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20?= =?UTF-8?q?=EC=97=AC=EB=9F=AC=20=EC=A2=85=EB=AA=A9=EC=97=90=20=EB=8C=80?= =?UTF-8?q?=ED=95=9C=20=EA=B5=AC=EB=8F=85=20=ED=95=B4=EC=A0=9C=20=EC=9A=94?= =?UTF-8?q?=EC=B2=AD=EC=9D=84=20=ED=95=9C=EB=B2=88=EC=97=90=20=ED=95=A0=20?= =?UTF-8?q?=EC=88=98=20=EC=9E=88=EB=8F=84=EB=A1=9D=20=EB=B0=B0=EC=97=B4?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD#250?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BE/src/asset/asset.service.ts | 4 +- BE/src/stock/order/stock-order.service.ts | 8 +-- .../history/stock-trade-history.controller.ts | 57 +++++++------------ .../stockSocket/stock-price-socket.service.ts | 20 ++++--- 4 files changed, 35 insertions(+), 54 deletions(-) diff --git a/BE/src/asset/asset.service.ts b/BE/src/asset/asset.service.ts index a9c37c0d..f08a14a1 100644 --- a/BE/src/asset/asset.service.ts +++ b/BE/src/asset/asset.service.ts @@ -157,8 +157,8 @@ export class AssetService { const userStocks: UserStock[] = await this.userStockRepository.findAllDistinctCode(userId); - userStocks.map((userStock) => - this.stockPriceSocketService.unsubscribeByCode(userStock.stock_code), + this.stockPriceSocketService.unsubscribeByCode( + userStocks.map((userStock) => userStock.stock_code), ); } diff --git a/BE/src/stock/order/stock-order.service.ts b/BE/src/stock/order/stock-order.service.ts index 73879bca..98320459 100644 --- a/BE/src/stock/order/stock-order.service.ts +++ b/BE/src/stock/order/stock-order.service.ts @@ -111,7 +111,7 @@ export class StockOrderService { status: StatusType.PENDING, })) ) - this.stockPriceSocketService.unsubscribeByCode(order.stock_code); + this.stockPriceSocketService.unsubscribeByCode([order.stock_code]); } async getPendingListByUserId(userId: number) { @@ -135,10 +135,8 @@ export class StockOrderService { const orders: Order[] = await this.stockOrderRepository.findAllCodeByStatus(); - await Promise.all( - orders.map((order) => - this.stockPriceSocketService.unsubscribeByCode(order.stock_code), - ), + this.stockPriceSocketService.unsubscribeByCode( + orders.map((order) => order.stock_code), ); await this.stockOrderRepository.delete({ status: StatusType.PENDING }); 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..8b5fa60d 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); + unsubscribeCode(@Query('stockCode') stockCode: string | string[]) { + const stockCodeArray = Array.isArray(stockCode) ? stockCode : [stockCode]; + this.stockPriceSocketService.unsubscribeByCode(stockCodeArray); } } diff --git a/BE/src/stockSocket/stock-price-socket.service.ts b/BE/src/stockSocket/stock-price-socket.service.ts index 0df995f3..d9299d53 100644 --- a/BE/src/stockSocket/stock-price-socket.service.ts +++ b/BE/src/stockSocket/stock-price-socket.service.ts @@ -73,14 +73,16 @@ export class StockPriceSocketService extends BaseStockSocketDomainService { this.connection[trKey] = 1; } - unsubscribeByCode(trKey: string) { - if (!this.connection[trKey]) return; - if (this.connection[trKey] > 1) { - this.connection[trKey] -= 1; - return; - } - delete this.connection[trKey]; - this.baseSocketDomainService.unregisterCode(this.TR_ID, trKey); + unsubscribeByCode(trKeys: string[]) { + trKeys.forEach((trKey) => { + if (!this.connection[trKey]) return; + if (this.connection[trKey] > 1) { + this.connection[trKey] -= 1; + return; + } + delete this.connection[trKey]; + this.baseSocketDomainService.unregisterCode(this.TR_ID, trKey); + }); } private async checkExecutableOrder(stockCode: string, value) { @@ -103,6 +105,6 @@ export class StockPriceSocketService extends BaseStockSocketDomainService { status: StatusType.PENDING, })) ) - this.unsubscribeByCode(stockCode); + this.unsubscribeByCode([stockCode]); } } From 80752a27b48099a794876e9c9ffb04d758859987 Mon Sep 17 00:00:00 2001 From: anjdydhody Date: Mon, 2 Dec 2024 22:25:51 +0900 Subject: [PATCH 25/35] =?UTF-8?q?=E2=9C=85=20test:=20=EC=A6=90=EA=B2=A8?= =?UTF-8?q?=EC=B0=BE=EA=B8=B0=20=EC=B7=A8=EC=86=8C=20API=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1=20?= =?UTF-8?q?#237?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bookmark/stock-bookmark.service.spec.ts | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/BE/src/stock/bookmark/stock-bookmark.service.spec.ts b/BE/src/stock/bookmark/stock-bookmark.service.spec.ts index f5371660..22a4a2ff 100644 --- a/BE/src/stock/bookmark/stock-bookmark.service.spec.ts +++ b/BE/src/stock/bookmark/stock-bookmark.service.spec.ts @@ -1,5 +1,6 @@ import { Test } from '@nestjs/testing'; -import { BadRequestException } from '@nestjs/common'; +import { BadRequestException, NotFoundException } from '@nestjs/common'; +import { Column, PrimaryGeneratedColumn } from 'typeorm'; import { StockBookmarkRepository } from './stock-bookmark.repository'; import { StockDetailService } from '../detail/stock-detail.service'; import { StockBookmarkService } from './stock-bookmark.service'; @@ -14,6 +15,8 @@ describe('stock bookmark test', () => { existsBy: jest.fn(), create: jest.fn(), insert: jest.fn(), + findOneBy: jest.fn(), + remove: jest.fn(), }; const mockStockDetailService = { getInquirePrice: jest.fn() }; @@ -63,4 +66,31 @@ describe('stock bookmark test', () => { 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); + }); }); From 0e87ffeef98efe05ea040eec6497ca835e41a438 Mon Sep 17 00:00:00 2001 From: anjdydhody Date: Mon, 2 Dec 2024 22:35:59 +0900 Subject: [PATCH 26/35] =?UTF-8?q?=E2=9C=85=20test:=20=EC=A6=90=EA=B2=A8?= =?UTF-8?q?=EC=B0=BE=EA=B8=B0=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20API=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=9E=91=EC=84=B1=20#237?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bookmark/stock-bookmark.service.spec.ts | 45 ++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/BE/src/stock/bookmark/stock-bookmark.service.spec.ts b/BE/src/stock/bookmark/stock-bookmark.service.spec.ts index 22a4a2ff..d0bb4197 100644 --- a/BE/src/stock/bookmark/stock-bookmark.service.spec.ts +++ b/BE/src/stock/bookmark/stock-bookmark.service.spec.ts @@ -1,9 +1,9 @@ import { Test } from '@nestjs/testing'; import { BadRequestException, NotFoundException } from '@nestjs/common'; -import { Column, PrimaryGeneratedColumn } from 'typeorm'; 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; @@ -17,6 +17,7 @@ describe('stock bookmark test', () => { insert: jest.fn(), findOneBy: jest.fn(), remove: jest.fn(), + findBookmarkWithNameByUserId: jest.fn(), }; const mockStockDetailService = { getInquirePrice: jest.fn() }; @@ -93,4 +94,46 @@ describe('stock bookmark test', () => { 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, + ]); + }); }); From dae9f7cd713eaf2a3e4a908d72ebd38378bec8a4 Mon Sep 17 00:00:00 2001 From: JIN Date: Tue, 3 Dec 2024 18:05:02 +0900 Subject: [PATCH 27/35] =?UTF-8?q?=E2=9C=A8=20feat:=20redis=20pub/sub?= =?UTF-8?q?=EC=97=90=20=EC=9D=B4=EC=9A=A9=ED=95=A0=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: sieunie --- BE/src/common/redis/redis.domain-service.ts | 30 +++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/BE/src/common/redis/redis.domain-service.ts b/BE/src/common/redis/redis.domain-service.ts index 77700cc4..0163e86e 100644 --- a/BE/src/common/redis/redis.domain-service.ts +++ b/BE/src/common/redis/redis.domain-service.ts @@ -12,11 +12,11 @@ export class RedisDomainService { 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 +62,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.redis.publish(channel, message); + } + + async subscribe(channel: string) { + await this.redis.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); + } } From 1c115906427fb76692a4a305f3760ddfc266c225 Mon Sep 17 00:00:00 2001 From: JIN Date: Tue, 3 Dec 2024 18:05:55 +0900 Subject: [PATCH 28/35] =?UTF-8?q?=E2=9C=A8=20feat:=20redis=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=ED=95=9C=ED=88=AC=20=EA=B5=AC=EB=8F=85=EC=9D=84=20?= =?UTF-8?q?=EA=B4=80=EB=A6=AC=ED=95=A0=20=EC=88=98=20=EC=9E=88=EA=B2=8C=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: sieunie --- BE/.eslintrc.js | 2 ++ .../websocket/base-socket.domain-service.ts | 19 +++++++++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) 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/common/websocket/base-socket.domain-service.ts b/BE/src/common/websocket/base-socket.domain-service.ts index 527bf9ff..13f000f6 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() { @@ -56,11 +58,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 = () => { @@ -71,6 +81,11 @@ export class BaseSocketDomainService implements OnModuleInit { }); }, 60000); }; + + this.redisDomainService.on((message) => { + const dataList = message.split('^'); + this.socketDataHandlers.H0STCNT0(dataList); + }); } registerCode(trId: string, trKey: string) { From ed54e276ad2bcd2ba91c4544ddbecfd19d9a4462 Mon Sep 17 00:00:00 2001 From: JIN Date: Tue, 3 Dec 2024 18:07:32 +0900 Subject: [PATCH 29/35] =?UTF-8?q?=E2=9C=A8=20feat:=20redis=EB=A5=BC=20?= =?UTF-8?q?=EC=9D=B4=EC=9A=A9=ED=95=B4=20=EB=B6=84=EC=82=B0=ED=95=B4?= =?UTF-8?q?=EC=84=9C=20=EC=84=B8=EC=85=98=EC=9D=84=20=EC=9D=B4=EC=9A=A9?= =?UTF-8?q?=ED=95=A0=20=EC=88=98=20=EC=9E=88=EB=8F=84=EB=A1=9D=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: sieunie --- .../stockSocket/stock-price-socket.service.ts | 70 +++++++++++++++---- 1 file changed, 57 insertions(+), 13 deletions(-) diff --git a/BE/src/stockSocket/stock-price-socket.service.ts b/BE/src/stockSocket/stock-price-socket.service.ts index d9299d53..4b51d957 100644 --- a/BE/src/stockSocket/stock-price-socket.service.ts +++ b/BE/src/stockSocket/stock-price-socket.service.ts @@ -1,21 +1,25 @@ import { Injectable, InternalServerErrorException } from '@nestjs/common'; +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'; @Injectable() export class StockPriceSocketService extends BaseStockSocketDomainService { private connection: { [key: string]: number } = {}; + private register: string[] = []; constructor( protected readonly socketGateway: SocketGateway, protected readonly baseSocketDomainService: BaseSocketDomainService, private readonly stockExecuteOrderRepository: StockExecuteOrderRepository, + private readonly redisDomainService: RedisDomainService, ) { super(socketGateway, baseSocketDomainService, 'H0STCNT0'); } @@ -63,26 +67,66 @@ export class StockPriceSocketService extends BaseStockSocketDomainService { ); } - subscribeByCode(trKey: string) { - this.baseSocketDomainService.registerCode(this.TR_ID, trKey); + async subscribeByCode(trKey: string) { + // 아무 서버도 한투와 구독 중이지 않을때 + if (!(await this.redisDomainService.exists(trKey))) { + this.baseSocketDomainService.registerCode(this.TR_ID, trKey); + this.register.push(trKey); + await this.redisDomainService.set(trKey, 1); + this.connection[trKey] = 1; - if (this.connection[trKey]) { - this.connection[trKey] += 1; return; } - this.connection[trKey] = 1; + + // 특정 서버는 한투와 구독 중일테니까... + await this.redisDomainService.increment(trKey); + + // 여기 서버에서 최초로 구독을 시작한다면, + if (!this.connection[trKey]) { + await this.redisDomainService.subscribe(`stock/${trKey}`); + this.connection[trKey] = 1; + + return; + } + + this.connection[trKey] += 1; } - unsubscribeByCode(trKeys: string[]) { - trKeys.forEach((trKey) => { + 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; } - delete this.connection[trKey]; - this.baseSocketDomainService.unregisterCode(this.TR_ID, trKey); - }); + + // 현재 서버에서 모든 연결이 종료됐을 경우 + 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); + } + } + } + + @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); + } + } } private async checkExecutableOrder(stockCode: string, value) { @@ -105,6 +149,6 @@ export class StockPriceSocketService extends BaseStockSocketDomainService { status: StatusType.PENDING, })) ) - this.unsubscribeByCode([stockCode]); + await this.unsubscribeByCode([stockCode]); } } From 4cc12c685c3d635a688e8a914b157eae788eb2db Mon Sep 17 00:00:00 2001 From: JIN Date: Tue, 3 Dec 2024 18:07:53 +0900 Subject: [PATCH 30/35] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20=20await?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: sieunie --- BE/src/asset/asset.service.ts | 2 +- BE/src/stock/order/stock-order.service.ts | 4 ++-- BE/src/stock/trade/history/stock-trade-history.controller.ts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/BE/src/asset/asset.service.ts b/BE/src/asset/asset.service.ts index f08a14a1..1112c56f 100644 --- a/BE/src/asset/asset.service.ts +++ b/BE/src/asset/asset.service.ts @@ -157,7 +157,7 @@ export class AssetService { const userStocks: UserStock[] = await this.userStockRepository.findAllDistinctCode(userId); - this.stockPriceSocketService.unsubscribeByCode( + await this.stockPriceSocketService.unsubscribeByCode( userStocks.map((userStock) => userStock.stock_code), ); } diff --git a/BE/src/stock/order/stock-order.service.ts b/BE/src/stock/order/stock-order.service.ts index 98320459..3de9e9d7 100644 --- a/BE/src/stock/order/stock-order.service.ts +++ b/BE/src/stock/order/stock-order.service.ts @@ -111,7 +111,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,7 +135,7 @@ export class StockOrderService { const orders: Order[] = await this.stockOrderRepository.findAllCodeByStatus(); - this.stockPriceSocketService.unsubscribeByCode( + await this.stockPriceSocketService.unsubscribeByCode( orders.map((order) => order.stock_code), ); 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 8b5fa60d..18b8b3c2 100644 --- a/BE/src/stock/trade/history/stock-trade-history.controller.ts +++ b/BE/src/stock/trade/history/stock-trade-history.controller.ts @@ -74,8 +74,8 @@ export class StockTradeHistoryController { status: 200, description: '구독 취소 성공', }) - unsubscribeCode(@Query('stockCode') stockCode: string | string[]) { + async unsubscribeCode(@Query('stockCode') stockCode: string | string[]) { const stockCodeArray = Array.isArray(stockCode) ? stockCode : [stockCode]; - this.stockPriceSocketService.unsubscribeByCode(stockCodeArray); + await this.stockPriceSocketService.unsubscribeByCode(stockCodeArray); } } From 495fe735c255f74d469fab670c52e31aec209ee8 Mon Sep 17 00:00:00 2001 From: JIN Date: Tue, 3 Dec 2024 18:11:31 +0900 Subject: [PATCH 31/35] =?UTF-8?q?=F0=9F=94=A7=20fix:=20=EC=9E=90=EC=8B=A0?= =?UTF-8?q?=EC=9D=B4=20pub=ED=95=9C=20=EA=B2=83=EB=8F=84=20sub=ED=95=A0=20?= =?UTF-8?q?=EC=88=98=20=EC=9E=88=EA=B2=8C=20=EC=BD=94=EB=93=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: sieunie --- BE/src/stockSocket/stock-price-socket.service.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/BE/src/stockSocket/stock-price-socket.service.ts b/BE/src/stockSocket/stock-price-socket.service.ts index 4b51d957..8897ea1e 100644 --- a/BE/src/stockSocket/stock-price-socket.service.ts +++ b/BE/src/stockSocket/stock-price-socket.service.ts @@ -71,6 +71,7 @@ export class StockPriceSocketService extends BaseStockSocketDomainService { // 아무 서버도 한투와 구독 중이지 않을때 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; From 0389366cca0e552f98922a2cf986ef836a9dc692 Mon Sep 17 00:00:00 2001 From: JIN Date: Tue, 3 Dec 2024 18:26:59 +0900 Subject: [PATCH 32/35] =?UTF-8?q?=F0=9F=94=A7=20fix:=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=EC=BD=94=EB=93=9C=EA=B0=80=20=EC=A0=95=EC=83=81?= =?UTF-8?q?=EC=A0=81=EC=9C=BC=EB=A1=9C=20=EB=8F=99=EC=9E=91=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EB=B6=80=EB=B6=84=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../stock/trade/history/stock-trade-history.service.spec.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) 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 index 363ecc94..f8477862 100644 --- a/BE/src/stock/trade/history/stock-trade-history.service.spec.ts +++ b/BE/src/stock/trade/history/stock-trade-history.service.spec.ts @@ -37,9 +37,7 @@ describe('stock trade history test', () => { jest .spyOn(koreaInvestmentDomainService, 'requestApi') .mockResolvedValueOnce(STOCK_TRADE_HISTORY_TODAY_MOCK); - jest - .spyOn(stockPriceSocketService, 'subscribeByCode') - .mockImplementation(() => {}); + jest.spyOn(stockPriceSocketService, 'subscribeByCode').mockResolvedValue(); const response = await stockTradeHistoryService.getTodayStockTradeHistory('005930'); From adc5be5076976a3ebee4c68a86ede6893a853518 Mon Sep 17 00:00:00 2001 From: JIN Date: Tue, 3 Dec 2024 18:33:34 +0900 Subject: [PATCH 33/35] =?UTF-8?q?=F0=9F=94=A7=20fix:=20=EB=A6=B0=ED=8A=B8?= =?UTF-8?q?=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BE/src/asset/asset.service.ts | 6 ++++-- BE/src/stock/order/stock-order.service.ts | 8 ++++++-- BE/src/stock/trade/history/stock-trade-history.service.ts | 2 +- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/BE/src/asset/asset.service.ts b/BE/src/asset/asset.service.ts index 1112c56f..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), + ), ); } diff --git a/BE/src/stock/order/stock-order.service.ts b/BE/src/stock/order/stock-order.service.ts index 3de9e9d7..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) { 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 8ef3b26c..34d077b4 100644 --- a/BE/src/stock/trade/history/stock-trade-history.service.ts +++ b/BE/src/stock/trade/history/stock-trade-history.service.ts @@ -35,7 +35,7 @@ export class StockTradeHistoryService { queryParams, ); - this.stockPriceSocketService.subscribeByCode(stockCode); + await this.stockPriceSocketService.subscribeByCode(stockCode); return this.formatTodayStockTradeHistoryData(response.output); } From 46a886debba663b8d8636a58d022624c9ca9c599 Mon Sep 17 00:00:00 2001 From: anjdydhody Date: Wed, 4 Dec 2024 10:30:32 +0900 Subject: [PATCH 34/35] =?UTF-8?q?=F0=9F=9A=91=20!HOTFIX:=20redis=20pub/sub?= =?UTF-8?q?=EC=9D=80=20=EB=8B=A4=EB=A5=B8=20=ED=81=B4=EB=9D=BC=EC=9D=B4?= =?UTF-8?q?=EC=96=B8=ED=8A=B8=20=EC=82=AC=EC=9A=A9=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BE/src/common/redis/redis.domain-service.ts | 9 +++++---- BE/src/common/redis/redis.module.ts | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/BE/src/common/redis/redis.domain-service.ts b/BE/src/common/redis/redis.domain-service.ts index 0163e86e..f25c7114 100644 --- a/BE/src/common/redis/redis.domain-service.ts +++ b/BE/src/common/redis/redis.domain-service.ts @@ -4,8 +4,9 @@ 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 { @@ -64,11 +65,11 @@ export class RedisDomainService { } async publish(channel: string, message: string) { - return this.redis.publish(channel, message); + return this.publisher.publish(channel, message); } async subscribe(channel: string) { - await this.redis.subscribe(channel); + await this.subscriber.subscribe(channel); } on(callback: (message: string) => void) { diff --git a/BE/src/common/redis/redis.module.ts b/BE/src/common/redis/redis.module.ts index 863812b5..91e0154a 100644 --- a/BE/src/common/redis/redis.module.ts +++ b/BE/src/common/redis/redis.module.ts @@ -15,6 +15,24 @@ 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'], From aedff421610e02397d74be875c71d8bfb38d2bb9 Mon Sep 17 00:00:00 2001 From: anjdydhody Date: Wed, 4 Dec 2024 10:38:29 +0900 Subject: [PATCH 35/35] =?UTF-8?q?=F0=9F=9A=91=20!HOTFIX:=20redis=20pub/sub?= =?UTF-8?q?=20export?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BE/src/common/redis/redis.module.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/BE/src/common/redis/redis.module.ts b/BE/src/common/redis/redis.module.ts index 91e0154a..595b02ae 100644 --- a/BE/src/common/redis/redis.module.ts +++ b/BE/src/common/redis/redis.module.ts @@ -35,6 +35,11 @@ import { RedisDomainService } from './redis.domain-service'; }, RedisDomainService, ], - exports: [RedisDomainService, 'REDIS_CLIENT'], + exports: [ + RedisDomainService, + 'REDIS_CLIENT', + 'REDIS_PUBLISHER', + 'REDIS_SUBSCRIBER', + ], }) export class RedisModule {}