From de66fde6b80f881fb9e671d8ac0bd4dafc3905b5 Mon Sep 17 00:00:00 2001 From: kimminsu Date: Thu, 5 Dec 2024 02:33:14 +0900 Subject: [PATCH 1/2] =?UTF-8?q?=E2=9C=A8=20feat:=20=EC=A3=BC,=EC=9B=94,?= =?UTF-8?q?=EB=85=84=20=EB=8D=B0=EC=9D=B4=ED=84=B0=EB=8A=94=201=EC=8B=9C?= =?UTF-8?q?=EB=B6=80=ED=84=B0=2016=EC=8B=9C=EA=B9=8C=EC=A7=80=20=EA=B0=9C?= =?UTF-8?q?=EB=B3=84=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8=20=EC=A0=9C?= =?UTF-8?q?=ED=95=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/stock/dto/stockData.response.ts | 40 +++++++++++++++--- .../backend/src/stock/stockData.service.ts | 42 +++++++++++++++---- packages/backend/src/utils/date.ts | 10 ++++- 3 files changed, 78 insertions(+), 14 deletions(-) diff --git a/packages/backend/src/stock/dto/stockData.response.ts b/packages/backend/src/stock/dto/stockData.response.ts index 0f183d66..a9c92cd0 100644 --- a/packages/backend/src/stock/dto/stockData.response.ts +++ b/packages/backend/src/stock/dto/stockData.response.ts @@ -1,6 +1,12 @@ import { ApiProperty } from '@nestjs/swagger'; import { Type } from 'class-transformer'; -import { StockData } from '@/stock/domain/stockData.entity'; +import { + StockDaily, + StockData, + StockWeekly, +} from '@/stock/domain/stockData.entity'; +import { StockLiveData } from '@/stock/domain/stockLiveData.entity'; +import { getToday } from '@/utils/date'; export class PriceDto { @ApiProperty({ @@ -37,10 +43,10 @@ export class PriceDto { constructor(stockData: StockData) { this.startTime = stockData.startTime; - this.open = stockData.open; - this.high = stockData.high; - this.low = stockData.low; - this.close = stockData.close; + this.open = Number(stockData.open); + this.high = Number(stockData.high); + this.low = Number(stockData.low); + this.close = Number(stockData.close); } } @@ -61,7 +67,7 @@ export class VolumeDto { constructor(stockData: StockData) { this.startTime = stockData.startTime; - this.volume = stockData.volume; + this.volume = Number(stockData.volume); } } @@ -95,4 +101,26 @@ export class StockDataResponse { this.volumeDtoList = volumeDtoList; this.hasMore = hasMore; } + + renewLastData(stockLiveData: StockLiveData, entity: new () => StockData) { + const lastIndex = this.priceDtoList.length - 1; + this.priceDtoList[lastIndex].close = Number(stockLiveData.currentPrice); + this.priceDtoList[lastIndex].high = + stockLiveData.high > this.priceDtoList[lastIndex].high + ? stockLiveData.high + : this.priceDtoList[lastIndex].high; + this.priceDtoList[lastIndex].low = + stockLiveData.low < this.priceDtoList[lastIndex].low + ? stockLiveData.low + : this.priceDtoList[lastIndex].low; + + this.priceDtoList[lastIndex].startTime = + entity !== StockWeekly + ? getToday() + : this.priceDtoList[lastIndex].startTime; + this.volumeDtoList[lastIndex].volume = + entity === StockDaily + ? stockLiveData.volume + : this.volumeDtoList[lastIndex].volume + stockLiveData.volume; + } } diff --git a/packages/backend/src/stock/stockData.service.ts b/packages/backend/src/stock/stockData.service.ts index bc245eaa..3527b240 100644 --- a/packages/backend/src/stock/stockData.service.ts +++ b/packages/backend/src/stock/stockData.service.ts @@ -20,6 +20,7 @@ import { OpenapiPeriodData } from '@/scraper/openapi/api/openapiPeriodData.api'; import { Period } from '@/scraper/openapi/type/openapiPeriodData.type'; import { NewDate } from '@/scraper/openapi/util/newDate.util'; import { StockDataCache } from '@/stock/cache/stockData.cache'; +import { StockLiveData } from '@/stock/domain/stockLiveData.entity'; import { getFormattedDate, isTodayWeekend } from '@/utils/date'; type StockData = { @@ -108,7 +109,30 @@ export class StockDataService { lastStartTime, ); } - return await this.getChartDataFromDB(entity, stockId, lastStartTime); + const response = await this.getChartDataFromDB( + entity, + stockId, + lastStartTime, + ); + const time = new Date(); + if (!lastStartTime && time.getHours() < 16 && time.getHours() >= 9) { + return await this.renewResponse(response, entity, stockId); + } + return response; + } + + private async renewResponse( + response: StockDataResponse, + entity: new () => StockData, + stockId: string, + ) { + const liveData = await this.dataSource.manager.findOne(StockLiveData, { + where: { stock: { id: stockId } }, + }); + if (liveData) { + response.renewLastData(liveData, entity); + } + return response; } private async getChartDataFromDB( @@ -157,17 +181,21 @@ export class StockDataService { if (period === 'D') return lastDate.isSameDate(current); if (period === 'M') { return ( - lastDate.isSameWeek(current) && - lastDate.isSameYear(current) && - lastDate.isSameDate(current) && - lastDate.isSameDate(current) + lastDate.isSameMonth(current) && + (lastDate.isSameDate(current) || + (current.getHours() < 16 && current.getHours() > 1)) ); } if (period === 'Y') - return lastDate.isSameYear(current) && lastDate.isSameDate(current); + return ( + lastDate.isSameYear(current) && + (lastDate.isSameDate(current) || + (current.getHours() < 16 && current.getHours() > 1)) + ); return ( lastDate.isSameWeek(current) && - lastData.createdAt.getDate() === current.getDate() + (lastData.createdAt.getDate() === current.getDate() || + (current.getHours() < 16 && current.getHours() > 1)) ); } diff --git a/packages/backend/src/utils/date.ts b/packages/backend/src/utils/date.ts index c0bbc79b..bea2b209 100644 --- a/packages/backend/src/utils/date.ts +++ b/packages/backend/src/utils/date.ts @@ -9,4 +9,12 @@ export function isTodayWeekend() { const today = new Date(); const day = today.getDay(); return day === 0 || day === 6; -} \ No newline at end of file +} + +export function getToday() { + const now = new Date(); + const year = now.getFullYear(); + const month = now.getMonth(); + const day = now.getDate(); + return new Date(year, month, day); +} From 89725df0ec639d40f1d86e7eea5f3e807f2c2b89 Mon Sep 17 00:00:00 2001 From: kimminsu Date: Thu, 5 Dec 2024 02:44:00 +0900 Subject: [PATCH 2/2] =?UTF-8?q?=E2=9C=A8=20feat:=20=EC=9B=B9=20=EC=86=8C?= =?UTF-8?q?=EC=BC=93=20=EC=B2=AB=20=EC=97=B0=EA=B2=B0=20=EC=8B=9C=20rest?= =?UTF-8?q?=20=EC=9A=94=EC=B2=AD=EC=9D=84=20=ED=81=90=EB=A1=9C=20=EC=A0=9C?= =?UTF-8?q?=EC=96=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../openapi/api/openapiLiveData.api.ts | 13 +++++- .../src/scraper/openapi/liveData.service.ts | 44 +++++++------------ 2 files changed, 28 insertions(+), 29 deletions(-) diff --git a/packages/backend/src/scraper/openapi/api/openapiLiveData.api.ts b/packages/backend/src/scraper/openapi/api/openapiLiveData.api.ts index f7288829..7b17568a 100644 --- a/packages/backend/src/scraper/openapi/api/openapiLiveData.api.ts +++ b/packages/backend/src/scraper/openapi/api/openapiLiveData.api.ts @@ -5,7 +5,7 @@ import { openApiConfig } from '../config/openapi.config'; import { isOpenapiLiveData } from '../type/openapiLiveData.type'; import { TR_IDS } from '../type/openapiUtil.type'; import { getOpenApi } from '../util/openapiUtil.api'; -import { Json } from '@/scraper/openapi/queue/openapi.queue'; +import { Json, OpenapiQueue } from '@/scraper/openapi/queue/openapi.queue'; import { Stock } from '@/stock/domain/stock.entity'; import { StockLiveData } from '@/stock/domain/stockLiveData.entity'; @@ -15,6 +15,7 @@ export class OpenapiLiveData { '/uapi/domestic-stock/v1/quotations/inquire-ccnl'; constructor( private readonly datasource: DataSource, + private readonly openapiQueue: OpenapiQueue, @Inject('winston') private readonly logger: Logger, ) {} @@ -76,6 +77,16 @@ export class OpenapiLiveData { return stockData; } + insertLiveDataRequest(stockId: string) { + const query = this.makeLiveDataQuery(stockId); + this.openapiQueue.enqueue({ + url: this.url, + query, + trId: TR_IDS.LIVE_DATA, + callback: this.getLiveDataSaveCallback(stockId), + }); + } + async connectLiveData(stockId: string, config: typeof openApiConfig) { const query = this.makeLiveDataQuery(stockId); diff --git a/packages/backend/src/scraper/openapi/liveData.service.ts b/packages/backend/src/scraper/openapi/liveData.service.ts index 31b05b4c..473405b2 100644 --- a/packages/backend/src/scraper/openapi/liveData.service.ts +++ b/packages/backend/src/scraper/openapi/liveData.service.ts @@ -39,22 +39,6 @@ export class LiveData { }); } - private async openapiSubscribe(stockId: string) { - const config = (await this.openApiToken.configs())[0]; - const result = await this.openapiLiveData.connectLiveData(stockId, config); - try { - const stockLiveData = this.openapiLiveData.convertResponseToStockLiveData( - result.output, - stockId, - ); - if (stockLiveData) { - await this.openapiLiveData.saveLiveData(stockLiveData); - } - } catch (error) { - this.logger.warn(`Subscribe error in open api : ${error}`); - } - } - isSubscribe(stockId: string) { return Object.keys(this.subscribeStocks).some((val) => val === stockId); } @@ -105,6 +89,22 @@ export class LiveData { } } + @Cron('0 2 * * 1-5') + connect() { + this.websocketClient.forEach((socket, idx) => { + socket.connectFacade( + this.initOpenCallback(idx), + this.initMessageCallback, + this.initCloseCallback, + this.initErrorCallback, + ); + }); + } + + private async openapiSubscribe(stockId: string) { + this.openapiLiveData.insertLiveDataRequest(stockId); + } + private initOpenCallback = (idx: number) => (sendMessage: (message: string) => void) => async () => { this.logger.info('WebSocket connection established'); @@ -160,18 +160,6 @@ export class LiveData { return dateMinutes <= startMinutes || dateMinutes >= endMinutes; } - @Cron('0 2 * * 1-5') - connect() { - this.websocketClient.forEach((socket, idx) => { - socket.connectFacade( - this.initOpenCallback(idx), - this.initMessageCallback, - this.initCloseCallback, - this.initErrorCallback, - ); - }); - } - private convertObjectToMessage( config: typeof openApiConfig, stockId: string,