diff --git a/packages/backend/src/scraper/openapi/api/openapi.abstract.ts b/packages/backend/src/scraper/openapi/api/openapi.abstract.ts new file mode 100644 index 00000000..446bbcd6 --- /dev/null +++ b/packages/backend/src/scraper/openapi/api/openapi.abstract.ts @@ -0,0 +1,30 @@ +import { DataSource } from 'typeorm'; +import { openApiConfig } from '../config/openapi.config'; +import { Stock } from '@/stock/domain/stock.entity'; + +export abstract class Openapi { + constructor(protected readonly datasource: DataSource) {} + + protected abstract start(): Promise; + + protected abstract interval(idx: number, stocks: Stock[]): Promise; + + protected abstract step(idx: number, stock: Stock): Promise; + + protected abstract getFromUrl( + config: typeof openApiConfig, + stockId: string, + ): object; + + protected abstract convertResToEntity(res: object, stockId: string): object; + + protected async getStockId() { + const entity = Stock; + const manager = this.datasource.manager; + const result = await manager.find(entity, { + select: { id: true }, + where: { isTrading: true }, + }); + return result; + } +} diff --git a/packages/backend/src/scraper/openapi/api/openapiDetailData.api.ts b/packages/backend/src/scraper/openapi/api/openapiDetailData.api.ts index 15484f0a..5f0168bd 100644 --- a/packages/backend/src/scraper/openapi/api/openapiDetailData.api.ts +++ b/packages/backend/src/scraper/openapi/api/openapiDetailData.api.ts @@ -1,278 +1,112 @@ import { Inject, Injectable } from '@nestjs/common'; import { Cron } from '@nestjs/schedule'; -import { Between, DataSource } from 'typeorm'; +import { DataSource } from 'typeorm'; import { Logger } from 'winston'; import { openApiConfig } from '../config/openapi.config'; -import { - DetailDataQuery, - FinancialRatio, - isFinancialRatioData, - isProductDetail, - ProductDetail, - StockDetailQuery, -} from '../type/openapiDetailData.type'; -import { TR_IDS } from '../type/openapiUtil.type'; +import { DetailData, isDetailData } from '../type/openapiDetailData.type'; +import { TR_ID } from '../type/openapiUtil.type'; import { getOpenApi } from '../util/openapiUtil.api'; +import { Openapi } from './openapi.abstract'; import { OpenapiTokenApi } from './openapiToken.api'; -import { KospiStock } from '@/stock/domain/kospiStock.entity'; import { Stock } from '@/stock/domain/stock.entity'; -import { StockDaily } from '@/stock/domain/stockData.entity'; import { StockDetail } from '@/stock/domain/stockDetail.entity'; @Injectable() -export class OpenapiDetailData { - private readonly financialUrl: string = - '/uapi/domestic-stock/v1/finance/financial-ratio'; - private readonly productUrl: string = - '/uapi/domestic-stock/v1/quotations/search-stock-info'; - private readonly intervals = 1000; +export class OpenapiDetailData extends Openapi { + private readonly TR_ID: TR_ID = 'FHKST01010100'; + private readonly url: string = + '/uapi/domestic-stock/v1/quotations/inquire-price'; constructor( - private readonly openApiToken: OpenapiTokenApi, - private readonly datasource: DataSource, @Inject('winston') private readonly logger: Logger, + protected readonly datasource: DataSource, + private readonly config: OpenapiTokenApi, ) { - //this.getDetailData(); - } - - @Cron('0 8 * * 1-5') - async getDetailData() { - if (process.env.NODE_ENV !== 'production') return; - const entityManager = this.datasource.manager; - const stocks = await entityManager.find(Stock); - const configCount = (await this.openApiToken.configs()).length; - const chunkSize = Math.ceil(stocks.length / configCount); - - for (let i = 0; i < configCount; i++) { - this.logger.info((await this.openApiToken.configs())[i]); - const chunk = stocks.slice(i * chunkSize, (i + 1) * chunkSize); - this.getDetailDataChunk(chunk, (await this.openApiToken.configs())[i]); - } - } - - private async saveDetailData(stockDetail: StockDetail) { - const manager = this.datasource.manager; - const entity = StockDetail; - const existingStockDetail = await manager.findOne(entity, { - where: { - stock: { id: stockDetail.stock.id }, - }, - }); - if (existingStockDetail) { - manager.update( - entity, - { stock: { id: stockDetail.stock.id } }, - stockDetail, - ); - } else { - manager.save(entity, stockDetail); - } - } - - private async saveKospiData(stockDetail: KospiStock) { - const manager = this.datasource.manager; - const entity = KospiStock; - const existingStockDetail = await manager.findOne(entity, { - where: { - stock: { id: stockDetail.stock.id }, - }, - }); - - if (existingStockDetail) { - manager.update( - entity, - { stock: { id: stockDetail.stock.id } }, - stockDetail, - ); - } else { - manager.save(entity, stockDetail); + super(datasource); + } + + @Cron('35 0 * * 1-5') + async start() { + const stock = await this.getStockId(); + const len = (await this.config.configs()).length; + const stockSize = Math.ceil(stock.length / len); + let i = 0; + while (i < len) { + this.interval(i, stock.slice(i * stockSize, (i + 1) * stockSize)); + i++; } } - private async calPer(eps: number): Promise { - if (eps <= 0) return NaN; - const manager = this.datasource.manager; - const latestResult = await manager.find(StockDaily, { - skip: 0, - take: 1, - order: { createdAt: 'desc' }, - }); - // TODO : price가 없는 경우 0으로 리턴, 나중에 NaN과 대응되게 리턴 - if (latestResult && latestResult[0] && latestResult[0].close) { - const currentPrice = latestResult[0].close; - const per = currentPrice / eps; - - if (isNaN(per)) return 0; - else return per; - } else { - return 0; + protected async interval(idx: number, stocks: Stock[]) { + const interval = 100; + let time = 0; + for (const stock of stocks) { + setTimeout(() => this.step(idx, stock), time); + time += interval; } } - private async calMarketCap(lstg: number) { - const manager = this.datasource.manager; - const latestResult = await manager.find(StockDaily, { - skip: 0, - take: 1, - order: { createdAt: 'desc' }, - }); - - // TODO : price가 없는 경우 0으로 리턴, 나중에 NaN과 대응되게 리턴 - if (latestResult && latestResult[0] && latestResult[0].close) { - const currentPrice = latestResult[0].close; - const marketCap = lstg * currentPrice; - - if (isNaN(marketCap)) return 0; - else return marketCap; - } else { - return 0; + protected async step(idx: number, stock: Stock) { + try { + const config = (await this.config.configs())[idx]; + const res = await this.getFromUrl(config, stock.id); + if (res.output && isDetailData(res.output)) { + const entity = this.convertResToEntity(res.output, stock.id); + await this.save(entity); + } + } catch (error) { + this.logger.warn(`Error in detail data : ${error}`); + setTimeout(() => this.step(idx, stock), 100); } } - private async get52WeeksLowHigh() { - const manager = this.datasource.manager; - const nowDate = new Date(); - const weeksAgoDate = this.getDate52WeeksAgo(); - // 주식의 52주간 일단위 데이터 전체 중에 최고, 최저가를 바탕으로 최저가, 최고가 계산해서 가져오기 - const output = await manager.find(StockDaily, { - select: ['low', 'high'], - where: { - startTime: Between(weeksAgoDate, nowDate), - }, - }); - const result = output.reduce((prev, cur) => { - if (prev.low > cur.low) prev.low = cur.low; - if (prev.high < cur.high) prev.high = cur.high; - return cur; - }, new StockDaily()); - let low = 0; - let high = 0; - if (result.low && !isNaN(result.low)) low = result.low; - if (result.high && !isNaN(result.high)) high = result.high; - return { low, high }; + protected async getFromUrl(config: typeof openApiConfig, stockId: string) { + const query = this.query(stockId); + const res = await getOpenApi(this.url, config, query, this.TR_ID); + if (res) return res; + else throw new Error(); } - private async makeStockDetailObject( - output1: FinancialRatio, - output2: ProductDetail, - stockId: string, - ): Promise { + protected convertResToEntity(res: DetailData, stockId: string): StockDetail { const result = new StockDetail(); + result.eps = parseInt(res.eps); + result.high52w = parseInt(res.w52_hgpr); + result.low52w = parseInt(res.w52_lwpr); + result.marketCap = res.hts_avls; + result.per = parseFloat(res.per); result.stock = { id: stockId } as Stock; - result.marketCap = - (await this.calMarketCap(parseInt(output2.lstg_stqt))) + ''; - result.eps = parseInt(output1.eps); - const { low, high } = await this.get52WeeksLowHigh(); - result.low52w = low; - result.high52w = high; - const eps = parseInt(output1.eps); - if (isNaN(eps)) result.eps = 0; - else result.eps = eps; - const per = await this.calPer(eps); - if (isNaN(per)) result.per = 0; - else result.per = per; result.updatedAt = new Date(); return result; } - private async makeKospiStockObject(output: ProductDetail, stockId: string) { - const ret = new KospiStock(); - ret.isKospi = output.kospi200_item_yn === 'Y' ? true : false; - ret.stock = { id: stockId } as Stock; - return ret; - } - - private async getFinancialRatio(stock: Stock, conf: typeof openApiConfig) { - const dataQuery = this.getDetailDataQuery(stock.id!); - // 여기서 가져올 건 eps -> eps와 per 계산하자. - try { - const response = await getOpenApi( - this.financialUrl, - conf, - dataQuery, - TR_IDS.FINANCIAL_DATA, - ); - if (response.output && response.output[0]) { - const output1 = response.output; - return output1[0]; - } - } catch (error) { - this.logger.warn(error); - } - } - - private async getProductData(stock: Stock, conf: typeof openApiConfig) { - const defaultQuery = this.getFinancialDataQuery(stock.id!); - - // 여기서 가져올 건 lstg-stqt - 상장주수를 바탕으로 시가총액 계산, kospi200_item_yn 코스피200종목여부 업데이트 - try { - const response = await getOpenApi( - this.productUrl, - conf, - defaultQuery, - TR_IDS.PRODUCTION_DETAIL, - ); - if (response.output) { - const output2 = response.output; - return output2; - } - } catch (error) { - this.logger.warn(error); - } - } - - private async getDetailDataDelay(stock: Stock, conf: typeof openApiConfig) { - const output1 = await this.getFinancialRatio(stock, conf); - const output2 = await this.getProductData(stock, conf); - - if (isFinancialRatioData(output1) && isProductDetail(output2)) { - const stockDetail = await this.makeStockDetailObject( - output1, - output2, - stock.id!, - ); - this.saveDetailData(stockDetail); - const kospiStock = await this.makeKospiStockObject(output2, stock.id!); - this.saveKospiData(kospiStock); - - this.logger.info(`${stock.id!} detail data is saved`); - } - } - - private async getDetailDataChunk(chunk: Stock[], conf: typeof openApiConfig) { - let delay = 0; - for await (const stock of chunk) { - setTimeout(() => this.getDetailDataDelay(stock, conf), delay); - delay += this.intervals; - } - } + //private async getStockId() { + // const entity = Stock; + // const manager = this.datasource.manager; + // const result = await manager.find(entity, { + // select: { id: true }, + // where: { isTrading: true }, + // }); + // return result; + //} - private getFinancialDataQuery( - stockId: string, - code: '300' | '301' | '302' | '306' = '300', - ): StockDetailQuery { - return { - pdno: stockId, - prdt_type_cd: code, - }; - } - - private getDetailDataQuery( - stockId: string, - divCode: 'J' = 'J', - classify: '0' | '1' = '0', - ): DetailDataQuery { + private async save(saveEntity: StockDetail) { + const entity = StockDetail; + const manager = this.datasource.manager; + await manager + .createQueryBuilder() + .insert() + .into(entity) + .values(saveEntity) + .orUpdate( + ['market_cap', 'eps', 'per', 'high52w', 'low52w', 'updated_at'], + ['stock_id'], + ) + .execute(); + } + + protected query(stockId: string, code: 'J' = 'J') { return { - fid_div_cls_code: classify, - fid_cond_mrkt_div_code: divCode, + fid_cond_mrkt_div_code: code, fid_input_iscd: stockId, }; } - - private getDate52WeeksAgo(): Date { - const today = new Date(); - const weeksAgo = 52 * 7; - const date52WeeksAgo = new Date(today.setDate(today.getDate() - weeksAgo)); - date52WeeksAgo.setHours(0, 0, 0, 0); - return date52WeeksAgo; - } } diff --git a/packages/backend/src/scraper/openapi/api/openapiLiveData.api.ts b/packages/backend/src/scraper/openapi/api/openapiLiveData.api.ts index 7db12b2c..2c02f87d 100644 --- a/packages/backend/src/scraper/openapi/api/openapiLiveData.api.ts +++ b/packages/backend/src/scraper/openapi/api/openapiLiveData.api.ts @@ -42,7 +42,7 @@ export class OpenapiLiveData { } } - // 현재가 체결로는 데이터가 부족해 현재가 시세를 사용함. + // 현재가 체결 convertResponseToStockLiveData = ( data: OpenapiLiveData, stockId: string, diff --git a/packages/backend/src/scraper/openapi/api/openapiPeriodData.api.ts b/packages/backend/src/scraper/openapi/api/openapiPeriodData.api.ts index 1d5d86a0..778f82ee 100644 --- a/packages/backend/src/scraper/openapi/api/openapiPeriodData.api.ts +++ b/packages/backend/src/scraper/openapi/api/openapiPeriodData.api.ts @@ -7,7 +7,7 @@ import { isChartData, ItemChartPriceQuery, Period, -} from '../type/openapiPeriodData'; +} from '../type/openapiPeriodData.type'; import { TR_IDS } from '../type/openapiUtil.type'; import { getOpenApi, diff --git a/packages/backend/src/scraper/openapi/type/openapiDetailData.type.ts b/packages/backend/src/scraper/openapi/type/openapiDetailData.type.ts index fa4b3a88..a8d21d02 100644 --- a/packages/backend/src/scraper/openapi/type/openapiDetailData.type.ts +++ b/packages/backend/src/scraper/openapi/type/openapiDetailData.type.ts @@ -1,215 +1,168 @@ /* eslint-disable @typescript-eslint/no-explicit-any*/ /* eslint-disable max-lines-per-function */ -export type DetailDataQuery = { - fid_cond_mrkt_div_code: 'J'; - fid_input_iscd: string; - fid_div_cls_code: '0' | '1'; +export type DetailData = { + iscd_stat_cls_code: string; + marg_rate: string; + rprs_mrkt_kor_name: string; + bstp_kor_isnm: string; + temp_stop_yn: string; + oprc_rang_cont_yn: string; + clpr_rang_cont_yn: string; + crdt_able_yn: string; + grmn_rate_cls_code: string; + elw_pblc_yn: string; + stck_prpr: string; + prdy_vrss: string; + prdy_vrss_sign: string; + prdy_ctrt: string; + acml_tr_pbmn: string; + acml_vol: string; + prdy_vrss_vol_rate: string; + stck_oprc: string; + stck_hgpr: string; + stck_lwpr: string; + stck_mxpr: string; + stck_llam: string; + stck_sdpr: string; + wghn_avrg_stck_prc: string; + hts_frgn_ehrt: string; + frgn_ntby_qty: string; + pgtr_ntby_qty: string; + pvt_scnd_dmrs_prc: string; + pvt_frst_dmrs_prc: string; + pvt_pont_val: string; + pvt_frst_dmsp_prc: string; + pvt_scnd_dmsp_prc: string; + dmrs_val: string; + dmsp_val: string; + cpfn: string; + rstc_wdth_prc: string; + stck_fcam: string; + stck_sspr: string; + aspr_unit: string; + hts_deal_qty_unit_val: string; + lstn_stcn: string; + hts_avls: string; + per: string; + pbr: string; + stac_month: string; + vol_tnrt: string; + eps: string; + bps: string; + d250_hgpr: string; + d250_hgpr_date: string; + d250_hgpr_vrss_prpr_rate: string; + d250_lwpr: string; + d250_lwpr_date: string; + d250_lwpr_vrss_prpr_rate: string; + stck_dryy_hgpr: string; + dryy_hgpr_vrss_prpr_rate: string; + dryy_hgpr_date: string; + stck_dryy_lwpr: string; + dryy_lwpr_vrss_prpr_rate: string; + dryy_lwpr_date: string; + w52_hgpr: string; + w52_hgpr_vrss_prpr_ctrt: string; + w52_hgpr_date: string; + w52_lwpr: string; + w52_lwpr_vrss_prpr_ctrt: string; + w52_lwpr_date: string; + whol_loan_rmnd_rate: string; + ssts_yn: string; + stck_shrn_iscd: string; + fcam_cnnm: string; + cpfn_cnnm: string; + frgn_hldn_qty: string; + vi_cls_code: string; + ovtm_vi_cls_code: string; + last_ssts_cntg_qty: string; + invt_caful_yn: string; + mrkt_warn_cls_code: string; + short_over_yn: string; + sltr_yn: string; }; -export type FinancialRatio = { - stac_yymm: string; // 결산 년월 - grs: string; // 매출액 증가율 - bsop_prfi_inrt: string; // 영업 이익 증가율 - ntin_inrt: string; // 순이익 증가율 - roe_val: string; // ROE 값 - eps: string; // EPS - sps: string; // 주당매출액 - bps: string; // BPS - rsrv_rate: string; // 유보 비율 - lblt_rate: string; // 부채 비율 -}; - -export function isFinancialRatioData(data: any): data is FinancialRatio { +export function isDetailData(data: any): data is DetailData { return ( - data && - typeof data.stac_yymm === 'string' && - typeof data.grs === 'string' && - typeof data.bsop_prfi_inrt === 'string' && - typeof data.ntin_inrt === 'string' && - typeof data.roe_val === 'string' && + typeof data.iscd_stat_cls_code === 'string' && + typeof data.marg_rate === 'string' && + typeof data.rprs_mrkt_kor_name === 'string' && + typeof data.bstp_kor_isnm === 'string' && + typeof data.temp_stop_yn === 'string' && + typeof data.oprc_rang_cont_yn === 'string' && + typeof data.clpr_rang_cont_yn === 'string' && + typeof data.crdt_able_yn === 'string' && + typeof data.grmn_rate_cls_code === 'string' && + typeof data.elw_pblc_yn === 'string' && + typeof data.stck_prpr === 'string' && + typeof data.prdy_vrss === 'string' && + typeof data.prdy_vrss_sign === 'string' && + typeof data.prdy_ctrt === 'string' && + typeof data.acml_tr_pbmn === 'string' && + typeof data.acml_vol === 'string' && + typeof data.prdy_vrss_vol_rate === 'string' && + typeof data.stck_oprc === 'string' && + typeof data.stck_hgpr === 'string' && + typeof data.stck_lwpr === 'string' && + typeof data.stck_mxpr === 'string' && + typeof data.stck_llam === 'string' && + typeof data.stck_sdpr === 'string' && + typeof data.wghn_avrg_stck_prc === 'string' && + typeof data.hts_frgn_ehrt === 'string' && + typeof data.frgn_ntby_qty === 'string' && + typeof data.pgtr_ntby_qty === 'string' && + typeof data.pvt_scnd_dmrs_prc === 'string' && + typeof data.pvt_frst_dmrs_prc === 'string' && + typeof data.pvt_pont_val === 'string' && + typeof data.pvt_frst_dmsp_prc === 'string' && + typeof data.pvt_scnd_dmsp_prc === 'string' && + typeof data.dmrs_val === 'string' && + typeof data.dmsp_val === 'string' && + typeof data.cpfn === 'string' && + typeof data.rstc_wdth_prc === 'string' && + typeof data.stck_fcam === 'string' && + typeof data.stck_sspr === 'string' && + typeof data.aspr_unit === 'string' && + typeof data.hts_deal_qty_unit_val === 'string' && + typeof data.lstn_stcn === 'string' && + typeof data.hts_avls === 'string' && + typeof data.per === 'string' && + typeof data.pbr === 'string' && + typeof data.stac_month === 'string' && + typeof data.vol_tnrt === 'string' && typeof data.eps === 'string' && - typeof data.sps === 'string' && typeof data.bps === 'string' && - typeof data.rsrv_rate === 'string' && - typeof data.lblt_rate === 'string' + typeof data.d250_hgpr === 'string' && + typeof data.d250_hgpr_date === 'string' && + typeof data.d250_hgpr_vrss_prpr_rate === 'string' && + typeof data.d250_lwpr === 'string' && + typeof data.d250_lwpr_date === 'string' && + typeof data.d250_lwpr_vrss_prpr_rate === 'string' && + typeof data.stck_dryy_hgpr === 'string' && + typeof data.dryy_hgpr_vrss_prpr_rate === 'string' && + typeof data.dryy_hgpr_date === 'string' && + typeof data.stck_dryy_lwpr === 'string' && + typeof data.dryy_lwpr_vrss_prpr_rate === 'string' && + typeof data.dryy_lwpr_date === 'string' && + typeof data.w52_hgpr === 'string' && + typeof data.w52_hgpr_vrss_prpr_ctrt === 'string' && + typeof data.w52_hgpr_date === 'string' && + typeof data.w52_lwpr === 'string' && + typeof data.w52_lwpr_vrss_prpr_ctrt === 'string' && + typeof data.w52_lwpr_date === 'string' && + typeof data.whol_loan_rmnd_rate === 'string' && + typeof data.ssts_yn === 'string' && + typeof data.stck_shrn_iscd === 'string' && + typeof data.fcam_cnnm === 'string' && + typeof data.cpfn_cnnm === 'string' && + typeof data.frgn_hldn_qty === 'string' && + typeof data.vi_cls_code === 'string' && + typeof data.ovtm_vi_cls_code === 'string' && + typeof data.last_ssts_cntg_qty === 'string' && + typeof data.invt_caful_yn === 'string' && + typeof data.mrkt_warn_cls_code === 'string' && + typeof data.short_over_yn === 'string' && + typeof data.sltr_yn === 'string' ); } - -export type ProductDetail = { - pdno: string; // 상품번호 - prdt_type_cd: string; // 상품유형코드 - mket_id_cd: string; // 시장ID코드 - scty_grp_id_cd: string; // 증권그룹ID코드 - excg_dvsn_cd: string; // 거래소구분코드 - setl_mmdd: string; // 결산월일 - lstg_stqt: string; // 상장주수 - 이거 사용 - lstg_cptl_amt: string; // 상장자본금액 - cpta: string; // 자본금 - papr: string; // 액면가 - issu_pric: string; // 발행가격 - kospi200_item_yn: string; // 코스피200종목여부 - 이것도 사용 - scts_mket_lstg_dt: string; // 유가증권시장상장일자 - scts_mket_lstg_abol_dt: string; // 유가증권시장상장폐지일자 - kosdaq_mket_lstg_dt: string; // 코스닥시장상장일자 - kosdaq_mket_lstg_abol_dt: string; // 코스닥시장상장폐지일자 - frbd_mket_lstg_dt: string; // 프리보드시장상장일자 - frbd_mket_lstg_abol_dt: string; // 프리보드시장상장폐지일자 - reits_kind_cd: string; // 리츠종류코드 - etf_dvsn_cd: string; // ETF구분코드 - oilf_fund_yn: string; // 유전펀드여부 - idx_bztp_lcls_cd: string; // 지수업종대분류코드 - idx_bztp_mcls_cd: string; // 지수업종중분류코드 - idx_bztp_scls_cd: string; // 지수업종소분류코드 - stck_kind_cd: string; // 주식종류코드 - mfnd_opng_dt: string; // 뮤추얼펀드개시일자 - mfnd_end_dt: string; // 뮤추얼펀드종료일자 - dpsi_erlm_cncl_dt: string; // 예탁등록취소일자 - etf_cu_qty: string; // ETFCU수량 - prdt_name: string; // 상품명 - prdt_name120: string; // 상품명120 - prdt_abrv_name: string; // 상품약어명 - std_pdno: string; // 표준상품번호 - prdt_eng_name: string; // 상품영문명 - prdt_eng_name120: string; // 상품영문명120 - prdt_eng_abrv_name: string; // 상품영문약어명 - dpsi_aptm_erlm_yn: string; // 예탁지정등록여부 - etf_txtn_type_cd: string; // ETF과세유형코드 - etf_type_cd: string; // ETF유형코드 - lstg_abol_dt: string; // 상장폐지일자 - nwst_odst_dvsn_cd: string; // 신주구주구분코드 - sbst_pric: string; // 대용가격 - thco_sbst_pric: string; // 당사대용가격 - thco_sbst_pric_chng_dt: string; // 당사대용가격변경일자 - tr_stop_yn: string; // 거래정지여부 - admn_item_yn: string; // 관리종목여부 - thdt_clpr: string; // 당일종가 - bfdy_clpr: string; // 전일종가 - clpr_chng_dt: string; // 종가변경일자 - std_idst_clsf_cd: string; // 표준산업분류코드 - std_idst_clsf_cd_name: string; // 표준산업분류코드명 - idx_bztp_lcls_cd_name: string; // 지수업종대분류코드명 - idx_bztp_mcls_cd_name: string; // 지수업종중분류코드명 - idx_bztp_scls_cd_name: string; // 지수업종소분류코드명 - ocr_no: string; // OCR번호 - crfd_item_yn: string; // 크라우드펀딩종목여부 - elec_scty_yn: string; // 전자증권여부 - issu_istt_cd: string; // 발행기관코드 - etf_chas_erng_rt_dbnb: string; // ETF추적수익율배수 - etf_etn_ivst_heed_item_yn: string; // ETFETN투자유의종목여부 - stln_int_rt_dvsn_cd: string; // 대주이자율구분코드 - frnr_psnl_lmt_rt: string; // 외국인개인한도비율 - lstg_rqsr_issu_istt_cd: string; // 상장신청인발행기관코드 - lstg_rqsr_item_cd: string; // 상장신청인종목코드 - trst_istt_issu_istt_cd: string; // 신탁기관발행기관코드 -}; - -export const isProductDetail = (data: any): data is ProductDetail => { - return ( - data && - typeof data.pdno === 'string' && - typeof data.prdt_type_cd === 'string' && - typeof data.mket_id_cd === 'string' && - typeof data.scty_grp_id_cd === 'string' && - typeof data.excg_dvsn_cd === 'string' && - typeof data.setl_mmdd === 'string' && - typeof data.lstg_stqt === 'string' && - typeof data.lstg_cptl_amt === 'string' && - typeof data.cpta === 'string' && - typeof data.papr === 'string' && - typeof data.issu_pric === 'string' && - typeof data.kospi200_item_yn === 'string' && - typeof data.scts_mket_lstg_dt === 'string' && - typeof data.scts_mket_lstg_abol_dt === 'string' && - typeof data.kosdaq_mket_lstg_dt === 'string' && - typeof data.kosdaq_mket_lstg_abol_dt === 'string' && - typeof data.frbd_mket_lstg_dt === 'string' && - typeof data.frbd_mket_lstg_abol_dt === 'string' && - typeof data.reits_kind_cd === 'string' && - typeof data.etf_dvsn_cd === 'string' && - typeof data.oilf_fund_yn === 'string' && - typeof data.idx_bztp_lcls_cd === 'string' && - typeof data.idx_bztp_mcls_cd === 'string' && - typeof data.idx_bztp_scls_cd === 'string' && - typeof data.stck_kind_cd === 'string' && - typeof data.mfnd_opng_dt === 'string' && - typeof data.mfnd_end_dt === 'string' && - typeof data.dpsi_erlm_cncl_dt === 'string' && - typeof data.etf_cu_qty === 'string' && - typeof data.prdt_name === 'string' && - typeof data.prdt_name120 === 'string' && - typeof data.prdt_abrv_name === 'string' && - typeof data.std_pdno === 'string' && - typeof data.prdt_eng_name === 'string' && - typeof data.prdt_eng_name120 === 'string' && - typeof data.prdt_eng_abrv_name === 'string' && - typeof data.dpsi_aptm_erlm_yn === 'string' && - typeof data.etf_txtn_type_cd === 'string' && - typeof data.etf_type_cd === 'string' && - typeof data.lstg_abol_dt === 'string' && - typeof data.nwst_odst_dvsn_cd === 'string' && - typeof data.sbst_pric === 'string' && - typeof data.thco_sbst_pric === 'string' && - typeof data.thco_sbst_pric_chng_dt === 'string' && - typeof data.tr_stop_yn === 'string' && - typeof data.admn_item_yn === 'string' && - typeof data.thdt_clpr === 'string' && - typeof data.bfdy_clpr === 'string' && - typeof data.clpr_chng_dt === 'string' && - typeof data.std_idst_clsf_cd === 'string' && - typeof data.std_idst_clsf_cd_name === 'string' && - typeof data.idx_bztp_lcls_cd_name === 'string' && - typeof data.idx_bztp_mcls_cd_name === 'string' && - typeof data.idx_bztp_scls_cd_name === 'string' && - typeof data.ocr_no === 'string' && - typeof data.crfd_item_yn === 'string' && - typeof data.elec_scty_yn === 'string' && - typeof data.issu_istt_cd === 'string' && - typeof data.etf_chas_erng_rt_dbnb === 'string' && - typeof data.etf_etn_ivst_heed_item_yn === 'string' && - typeof data.stln_int_rt_dvsn_cd === 'string' && - typeof data.frnr_psnl_lmt_rt === 'string' && - typeof data.lstg_rqsr_issu_istt_cd === 'string' && - typeof data.lstg_rqsr_item_cd === 'string' && - typeof data.trst_istt_issu_istt_cd === 'string' - ); -}; - -export type StockDetailQuery = { - pdno: string; - prdt_type_cd: string; -}; - -//export type FinancialDetail = { -// stac_yymm: string; // 결산 년월 -// sale_account: string; // 매출액 -// sale_cost: string; // 매출원가 -// sale_totl_prfi: string; // 매출총이익 -// depr_cost: string; // 감가상각비 -// sell_mang: string; // 판매관리비 -// bsop_prti: string; // 영업이익 -// bsop_non_ernn: string; // 영업외수익 -// bsop_non_expn: string; // 영업외비용 -// op_prfi: string; // 영업이익 -// spec_prfi: string; // 특별이익 -// spec_loss: string; // 특별손실 -// thtr_ntin: string; // 세전순이익 -//}; - -//export const isFinancialDetail = (data: any): data is FinancialDetail => { -// return ( -// typeof data.stac_yymm === 'string' && -// typeof data.sale_account === 'string' && -// typeof data.sale_cost === 'string' && -// typeof data.sale_totl_prfi === 'string' && -// typeof data.depr_cost === 'string' && -// typeof data.sell_mang === 'string' && -// typeof data.bsop_prti === 'string' && -// typeof data.bsop_non_ernn === 'string' && -// typeof data.bsop_non_expn === 'string' && -// typeof data.op_prfi === 'string' && -// typeof data.spec_prfi === 'string' && -// typeof data.spec_loss === 'string' && -// typeof data.thtr_ntin === 'string' -// ); -//}; diff --git a/packages/backend/src/scraper/openapi/type/openapiPeriodData.type.ts b/packages/backend/src/scraper/openapi/type/openapiPeriodData.type.ts new file mode 100644 index 00000000..e4066f7c --- /dev/null +++ b/packages/backend/src/scraper/openapi/type/openapiPeriodData.type.ts @@ -0,0 +1,45 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +export type Period = 'D' | 'W' | 'M' | 'Y'; +export type ChartData = { + stck_bsop_date: string; + stck_clpr: string; + stck_oprc: string; + stck_hgpr: string; + stck_lwpr: string; + acml_vol: string; + acml_tr_pbmn: string; + flng_cls_code: string; + prtt_rate: string; + mod_yn: string; + prdy_vrss_sign: string; + prdy_vrss: string; + revl_issu_reas: string; +}; + +export type ItemChartPriceQuery = { + fid_cond_mrkt_div_code: 'J' | 'W'; + fid_input_iscd: string; + fid_input_date_1: string; + fid_input_date_2: string; + fid_period_div_code: Period; + fid_org_adj_prc: number; +}; + +export const isChartData = (data?: any) => { + return ( + data && + typeof data.stck_bsop_date === 'string' && + typeof data.stck_clpr === 'string' && + typeof data.stck_oprc === 'string' && + typeof data.stck_hgpr === 'string' && + typeof data.stck_lwpr === 'string' && + typeof data.acml_vol === 'string' && + typeof data.acml_tr_pbmn === 'string' && + typeof data.flng_cls_code === 'string' && + typeof data.prtt_rate === 'string' && + typeof data.mod_yn === 'string' && + typeof data.prdy_vrss_sign === 'string' && + typeof data.prdy_vrss === 'string' && + typeof data.revl_issu_reas === 'string' + ); +}; diff --git a/packages/backend/src/stock/domain/stockDetail.entity.ts b/packages/backend/src/stock/domain/stockDetail.entity.ts index e11d1cd9..22a084e4 100644 --- a/packages/backend/src/stock/domain/stockDetail.entity.ts +++ b/packages/backend/src/stock/domain/stockDetail.entity.ts @@ -27,7 +27,7 @@ export class StockDetail { @Column({ type: 'integer' }) eps: number; - @Column({ type: 'decimal', precision: 6, scale: 3 }) + @Column({ type: 'decimal', precision: 15, scale: 2 }) per: number; @Column({ type: 'integer' }) diff --git a/packages/backend/src/user/domain/user.entity.ts b/packages/backend/src/user/domain/user.entity.ts index 9f93e968..91a02c0b 100644 --- a/packages/backend/src/user/domain/user.entity.ts +++ b/packages/backend/src/user/domain/user.entity.ts @@ -5,11 +5,11 @@ import { OneToMany, PrimaryGeneratedColumn, } from 'typeorm'; +import { Mention } from '@/chat/domain/mention.entity'; import { DateEmbedded } from '@/common/dateEmbedded.entity'; import { UserStock } from '@/stock/domain/userStock.entity'; import { OauthType } from '@/user/domain/ouathType'; import { Role } from '@/user/domain/role'; -import { Mention } from '@/chat/domain/mention.entity'; @Index('nickname_sub_name', ['nickname', 'subName'], { unique: true }) @Index('type_oauth_id', ['type', 'oauthId'], { unique: true }) diff --git a/packages/backend/tsconfig.json b/packages/backend/tsconfig.json index f01a23e5..2eb3e242 100644 --- a/packages/backend/tsconfig.json +++ b/packages/backend/tsconfig.json @@ -8,5 +8,9 @@ }, "strictPropertyInitialization": false }, - "include": ["src/**/*", "test/**/*", "src/scraper/openapi/type/openapiPeriodData.ts"] + "include": [ + "src/**/*", + "test/**/*", + "src/scraper/openapi/type/openapiPeriodData.type.ts" + ] }