Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug/#250 stock data 문제 해결 #256

Merged
merged 35 commits into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
ef1e803
Dev be (#185)
swkim12345 Nov 18, 2024
41a4115
Feature/#99 - 참여한 채팅방에서 이전 기록을 확인할 수 있다. (#190)
xjfcnfw3 Nov 19, 2024
8fe29ef
✨ feat: token entity 추가
swkim12345 Nov 25, 2024
903264e
✨ feat: entity 에 저장, expire 검사 로직 추가
swkim12345 Nov 25, 2024
e4429e5
🐛 fix: token 주입으로 로직 변경
swkim12345 Nov 25, 2024
3a81ad0
♻️ refactor: token 주입으로 변경, 그로 인한 오류 수정 및 console.log 삭제
swkim12345 Nov 25, 2024
57f1d5a
🐛 fix: live 데이터 수집 오류 해결, 데이터 없을 때 insert 오류 해결
swkim12345 Nov 25, 2024
1de39b4
🐛 fix: stock, livedata entity 수정
swkim12345 Nov 25, 2024
bb71125
♻️ refactor: develop 환경시 logging 활성화
swkim12345 Nov 25, 2024
e7e6724
📦️ ci: production 환경일 때 작동되게 변경
swkim12345 Nov 25, 2024
8f43995
♻️ refactor: websocket 모듈에서 liveData로 서비스 로직 분리
swkim12345 Nov 25, 2024
6bcb5c5
♻️ refactor: livedata stock module로 이동
swkim12345 Nov 25, 2024
93df868
♻️ refactor: dev-be와 merge
swkim12345 Nov 25, 2024
77e4105
✨ feat: 장 마감시 openapi로 부르는 로직 추가
swkim12345 Nov 25, 2024
fe29d50
🐛 fix: websocket logger 추가, client stock 저장되지 않는 오류 해결
swkim12345 Nov 25, 2024
007e2e5
🐛 fix: openapi는 저장이 필요 없음 롤 백
swkim12345 Nov 25, 2024
2a66928
💄 style: merging dev-be
swkim12345 Nov 25, 2024
ff9ad02
🐛 fix: open api로 데이터 받지 못하는 문제 해결
swkim12345 Nov 25, 2024
ddccc0a
💄 style: 안 쓰이는 것 빼기
swkim12345 Nov 25, 2024
df5b6e6
💄 style: console.log 삭제
swkim12345 Nov 25, 2024
79a34b6
🐛 fix: pk가 아닌 곳에 unique 키 추가
swkim12345 Nov 26, 2024
a751756
💄 style: 불필요한 logger 삭제
swkim12345 Nov 26, 2024
cacfefd
Merge branch 'feature/#240' into bug/#250
swkim12345 Nov 26, 2024
ea8d57e
♻️ refactor: error, disconnect function 분리
swkim12345 Nov 26, 2024
2692830
♻️ refactor: stock data에 indexing, unique 거릭
swkim12345 Nov 26, 2024
df7d087
🐛 fix: 타입 가드 빠진 부분을 추가하고, detail이
swkim12345 Nov 26, 2024
0d3f4c4
🐛 fix: 유량 제어 제거, try-catch로 다시 시작 추가
swkim12345 Nov 26, 2024
390a8b3
♻️ refactor: cron 추가
swkim12345 Nov 26, 2024
4682c57
🐛 fix: cron
swkim12345 Nov 26, 2024
39db490
♻️ refactor: period data 수정
swkim12345 Nov 26, 2024
315db7c
♻️ refactor: console.log 삭제
swkim12345 Nov 26, 2024
04b68c1
♻️ refactor: dev-be 머지
swkim12345 Nov 26, 2024
1429a8a
🐛 fix: token의 expired 먼저 확인하고 db 접근으로 변경
swkim12345 Nov 26, 2024
1f8f82e
🐛 fix: settimeout 시간 조정
swkim12345 Nov 26, 2024
44e54fb
♻️ refactor: 확인용 getItemchartprice 제거
swkim12345 Nov 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export class OpenapiDetailData {
private readonly datasource: DataSource,
@Inject('winston') private readonly logger: Logger,
) {
//setTimeout(() => this.getDetailData(), 5000);
//this.getDetailData();
}

@Cron('0 8 * * 1-5')
Expand Down Expand Up @@ -191,7 +191,7 @@ export class OpenapiDetailData {
dataQuery,
TR_IDS.FINANCIAL_DATA,
);
if (response.output) {
if (response.output && response.output[0]) {
const output1 = response.output;
return output1[0];
}
Expand Down Expand Up @@ -224,8 +224,6 @@ export class OpenapiDetailData {
const output1 = await this.getFinancialRatio(stock, conf);
const output2 = await this.getProductData(stock, conf);

this.logger.info(JSON.stringify(output1));
this.logger.info(JSON.stringify(output2));
if (isFinancialRatioData(output1) && isProductDetail(output2)) {
const stockDetail = await this.makeStockDetailObject(
output1,
Expand All @@ -236,7 +234,7 @@ export class OpenapiDetailData {
const kospiStock = await this.makeKospiStockObject(output2, stock.id!);
this.saveKospiData(kospiStock);

this.logger.info(`${stock.id!} is saved`);
this.logger.info(`${stock.id!} detail data is saved`);
}
}

Expand Down
Empty file.
58 changes: 23 additions & 35 deletions packages/backend/src/scraper/openapi/api/openapiPeriodData.api.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Inject, Injectable } from '@nestjs/common';
import { Cron } from '@nestjs/schedule';
import { DataSource, EntityManager } from 'typeorm';
import { DataSource } from 'typeorm';
import { Logger } from 'winston';
import {
ChartData,
Expand Down Expand Up @@ -32,13 +32,13 @@ const DATE_TO_ENTITY = {
};

const DATE_TO_MONTH = {
D: 3,
D: 1,
W: 6,
M: 12,
Y: 24,
M: 24,
Y: 120,
};

const INTERVALS = 4000;
const INTERVALS = 10000;

@Injectable()
export class OpenapiPeriodData {
Expand All @@ -48,9 +48,7 @@ export class OpenapiPeriodData {
private readonly datasource: DataSource,
private readonly openApiToken: OpenapiTokenApi,
@Inject('winston') private readonly logger: Logger,
) {
//this.getItemChartPriceCheck();
}
) {}

@Cron('0 1 * * 1-5')
async getItemChartPriceCheck() {
Expand All @@ -60,20 +58,15 @@ export class OpenapiPeriodData {
isTrading: true,
},
});
const configCount = this.openApiToken.configs.length;
const chunkSize = Math.ceil(stocks.length / configCount);

for (let i = 0; i < configCount; i++) {
const chunk = stocks.slice(i * chunkSize, (i + 1) * chunkSize);
this.getChartData(chunk, 'D');
setTimeout(() => this.getChartData(chunk, 'W'), INTERVALS);
setTimeout(() => this.getChartData(chunk, 'M'), INTERVALS * 2);
setTimeout(() => this.getChartData(chunk, 'Y'), INTERVALS * 3);
}

await this.getChartData(stocks, 'Y');
await this.getChartData(stocks, 'M');
await this.getChartData(stocks, 'W');
await this.getChartData(stocks, 'D');
}

private async getChartData(chunk: Stock[], period: Period) {
const baseTime = INTERVALS * 4;
const baseTime = INTERVALS;
const entity = DATE_TO_ENTITY[period];

let time = 0;
Expand All @@ -89,26 +82,23 @@ export class OpenapiPeriodData {
entity: typeof StockData,
) {
const stockPeriod = new StockData();
const manager = this.datasource.manager;
let configIdx = 0;
let end = getTodayDate();
let start = getPreviousDate(end, 3);
let start = getPreviousDate(end, DATE_TO_MONTH[period]);
let isFail = false;

while (!isFail) {
await new Promise((resolve) => setTimeout(resolve, INTERVALS / 10));
configIdx = (configIdx + 1) % (await this.openApiToken.configs()).length;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

계좌 정보를 공유되는 큐를 통해서 가져오면 분배가 잘 되거라고 생각합니다

this.setStockPeriod(stockPeriod, stock.id!, end);

// chart 데이터가 있는 지 확인 -> 리턴
if (await this.existsChartData(stockPeriod, manager, entity)) return;

const query = this.getItemChartPriceQuery(stock.id!, start, end, period);

const output = await this.fetchChartData(query, configIdx);

if (output) {
await this.saveChartData(entity, stock.id!, output);
({ endDate: end, startDate: start } = this.updateDates(start, period));
({ endDate: end, startDate: start } = this.updateDates(end, period));
} else isFail = true;
}
}
Expand Down Expand Up @@ -137,34 +127,32 @@ export class OpenapiPeriodData {
return response.output2 as ChartData[];
} catch (error) {
this.logger.warn(error);
setTimeout(() => this.fetchChartData(query, configIdx), INTERVALS / 10);
}
}

private updateDates(
startDate: string,
endDate: string,
period: Period,
): { endDate: string; startDate: string } {
const endDate = getPreviousDate(startDate, DATE_TO_MONTH[period]);
startDate = getPreviousDate(endDate, DATE_TO_MONTH[period]);
endDate = getPreviousDate(endDate, DATE_TO_MONTH[period]);
const startDate = getPreviousDate(endDate, DATE_TO_MONTH[period]);
return { endDate, startDate };
}

private async existsChartData(
stock: StockData,
manager: EntityManager,
entity: typeof StockData,
) {
private async existsChartData(stock: StockData, entity: typeof StockData) {
const manager = this.datasource.manager;
return await manager.findOne(entity, {
where: {
stock: { id: stock.stock.id },
createdAt: stock.startTime,
startTime: stock.startTime,
},
});
}

private async insertChartData(stock: StockData, entity: typeof StockData) {
const manager = this.datasource.manager;
if (!(await this.existsChartData(stock, manager, entity))) {
if (!(await this.existsChartData(stock, entity))) {
await manager.save(entity, stock);
}
}
Expand Down
9 changes: 9 additions & 0 deletions packages/backend/src/scraper/openapi/api/openapiToken.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@ export class OpenapiTokenApi {

@Cron('30 0 * * 1-5')
async init() {
const expired_config = this.config.filter(
(val) =>
this.isTokenExpired(val.STOCK_API_TIMEOUT) &&
this.isTokenExpired(val.STOCK_WEBSOCKET_TIMEOUT),
);
const isUndefined = this.config[0].STOCK_WEBSOCKET_TIMEOUT ? false : true;
if (!isUndefined && !expired_config.length) return;
const tokens = this.convertConfigToTokenEntity(this.config);
const config = await this.getPropertyFromDB(tokens);
const expired = config.filter(
Expand Down Expand Up @@ -79,6 +86,8 @@ export class OpenapiTokenApi {
STOCK_API_TOKEN: val.api_token,
STOCK_URL: val.api_url,
STOCK_WEBSOCKET_KEY: val.websocket_key,
STOCK_API_TIMEOUT: val.api_token_expire,
STOCK_WEBSOCKET_TIMEOUT: val.websocket_key_expire,
};
result.push(config);
});
Expand Down
2 changes: 2 additions & 0 deletions packages/backend/src/scraper/openapi/config/openapi.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ export const openApiConfig: {
STOCK_API_PASSWORD: string | undefined;
STOCK_API_TOKEN?: string;
STOCK_WEBSOCKET_KEY?: string;
STOCK_API_TIMEOUT?: Date;
STOCK_WEBSOCKET_TIMEOUT?: Date;
} = {
STOCK_URL: process.env.STOCK_URL,
STOCK_ACCOUNT: process.env.STOCK_ACCOUNT,
Expand Down
7 changes: 1 addition & 6 deletions packages/backend/src/scraper/openapi/liveData.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,11 @@ export class LiveData {
@Inject('winston') private readonly logger: Logger,
) {
this.connect();
this.subscribe('000020');
}

private async openapiSubscribe(stockId: string) {
const config = (await this.openApiToken.configs())[0];
const result = await this.openapiLiveData.connectLiveData(stockId, config);
this.logger.info(JSON.stringify(result));
try {
const stockLiveData = this.openapiLiveData.convertResponseToStockLiveData(
result.output,
Expand Down Expand Up @@ -58,6 +56,7 @@ export class LiveData {
this.webSocketClient.subscribe(message);
}
}


async discribe(stockId: string) {
if (this.clientStock.has(stockId)) {
Expand Down Expand Up @@ -90,13 +89,10 @@ export class LiveData {
const message = this.parseMessage(data);
if (message.header) {
if (message.header.tr_id === 'PINGPONG') {
this.logger.info(`Received PING: ${data}`);
client.pong(data);
}
return;
}
this.logger.info(`Recived data : ${data}`);
this.logger.info(`Stock id : ${message[0]['STOCK_ID']}`);
const liveData = this.openapiLiveData.convertLiveData(message);
await this.openapiLiveData.saveLiveData(liveData);
} catch (error) {
Expand Down Expand Up @@ -142,7 +138,6 @@ export class LiveData {
stockId: string,
tr_type: TR_IDS,
): string {
this.logger.info(JSON.stringify(config));
const message = {
header: {
approval_key: config.STOCK_WEBSOCKET_KEY!,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ export type ProductDetail = {

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' &&
Expand Down
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,13 @@ export class WebsocketClient {
constructor(@Inject('winston') private readonly logger: Logger) {}

subscribe(message: string) {
this.logger.info(`Subscribe : ${message}`);
this.sendMessage(message);
}

discribe(message: string) {
this.logger.info(`Discribe : ${message}`);
this.sendMessage(message);
}

// TODO : 분리
private initDisconnect(
initCloseCallback: () => void,
initErrorCallback: (error: unknown) => void,
) {
this.client.on('close', initCloseCallback);

this.client.on('error', initErrorCallback);
}

private initOpen(fn: () => void) {
this.client.on('open', fn);
}
Expand All @@ -39,6 +27,14 @@ export class WebsocketClient {
this.client.on('message', fn);
}

private initDisconnect(initCloseCallback: () => void) {
this.client.on('close', initCloseCallback);
}

private initError(initErrorCallback: (error: unknown) => void) {
this.client.on('error', initErrorCallback);
}

connectPacade(
initOpenCallback: (fn: (message: string) => void) => () => void,
initMessageCallback: (client: WebSocket) => (data: RawData) => void,
Expand All @@ -47,7 +43,8 @@ export class WebsocketClient {
) {
this.initOpen(initOpenCallback(this.sendMessage));
this.initMessage(initMessageCallback(this.client));
this.initDisconnect(initCloseCallback, initErrorCallback);
this.initDisconnect(initCloseCallback);
this.initError(initErrorCallback);
}

private sendMessage(message: string) {
Expand Down
2 changes: 2 additions & 0 deletions packages/backend/src/stock/domain/stockData.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import {
CreateDateColumn,
JoinColumn,
ManyToOne,
Index,
} from 'typeorm';
import { Stock } from './stock.entity';

@Index('stock_id_start_time', ['stock.id', 'startTime'], { unique: true })
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍👍

export class StockData {
@PrimaryGeneratedColumn()
id: number;
Expand Down