diff --git a/BE/src/websocket/stock/index/stock-index-socket.service.ts b/BE/src/stock/index/stock-index-socket.service.ts similarity index 84% rename from BE/src/websocket/stock/index/stock-index-socket.service.ts rename to BE/src/stock/index/stock-index-socket.service.ts index 76ac9eb8..f82ba9a2 100644 --- a/BE/src/websocket/stock/index/stock-index-socket.service.ts +++ b/BE/src/stock/index/stock-index-socket.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; -import { StockIndexValueElementDto } from '../../../stock/index/dto/stock-index-value-element.dto'; -import { BaseSocketService } from '../../base-socket.service'; -import { SocketGateway } from '../../socket.gateway'; +import { StockIndexValueElementDto } from './dto/stock-index-value-element.dto'; +import { BaseSocketService } from '../../websocket/base-socket.service'; +import { SocketGateway } from '../../websocket/socket.gateway'; @Injectable() export class StockIndexSocketService { diff --git a/BE/src/stock/index/stock-index.module.ts b/BE/src/stock/index/stock-index.module.ts index 498ddd8b..d763d972 100644 --- a/BE/src/stock/index/stock-index.module.ts +++ b/BE/src/stock/index/stock-index.module.ts @@ -3,10 +3,11 @@ import { StockIndexController } from './stock-index.controller'; import { StockIndexService } from './stock-index.service'; import { KoreaInvestmentModule } from '../../koreaInvestment/korea-investment.module'; import { SocketModule } from '../../websocket/socket.module'; +import { StockIndexSocketService } from './stock-index-socket.service'; @Module({ imports: [KoreaInvestmentModule, SocketModule], controllers: [StockIndexController], - providers: [StockIndexService], + providers: [StockIndexService, StockIndexSocketService], }) export class StockIndexModule {} diff --git a/BE/src/stock/order/stock-order-socket.service.ts b/BE/src/stock/order/stock-order-socket.service.ts new file mode 100644 index 00000000..0f56140f --- /dev/null +++ b/BE/src/stock/order/stock-order-socket.service.ts @@ -0,0 +1,119 @@ +import { + Injectable, + InternalServerErrorException, + Logger, +} from '@nestjs/common'; +import { LessThanOrEqual, MoreThanOrEqual } from 'typeorm'; +import { BaseSocketService } from '../../websocket/base-socket.service'; +import { SocketGateway } from '../../websocket/socket.gateway'; +import { Order } from './stock-order.entity'; +import { TradeType } from './enum/trade-type'; +import { StatusType } from './enum/status-type'; +import { StockOrderRepository } from './stock-order.repository'; + +@Injectable() +export class StockOrderSocketService { + private TR_ID = 'H0STCNT0'; + + private readonly logger = new Logger(); + + constructor( + private readonly socketGateway: SocketGateway, + private readonly baseSocketService: BaseSocketService, + private readonly stockOrderRepository: StockOrderRepository, + ) { + baseSocketService.registerSocketOpenHandler(async () => { + const orders: Order[] = + await this.stockOrderRepository.findAllCodeByStatus(); + orders.forEach((order) => { + baseSocketService.registerCode(this.TR_ID, order.stock_code); + }); + }); + + baseSocketService.registerSocketDataHandler( + this.TR_ID, + (data: string[]) => { + this.checkExecutableOrder( + data[0], // 주식 코드 + data[2], // 주식 체결가 + ).catch(() => { + throw new InternalServerErrorException(); + }); + }, + ); + } + + subscribeByCode(trKey: string) { + this.baseSocketService.registerCode(this.TR_ID, trKey); + } + + unsubscribeByCode(trKey: string) { + this.baseSocketService.unregisterCode(this.TR_ID, trKey); + } + + private async checkExecutableOrder(stockCode: string, value) { + const buyOrders = await this.stockOrderRepository.find({ + where: { + stock_code: stockCode, + trade_type: TradeType.BUY, + status: StatusType.PENDING, + price: MoreThanOrEqual(value), + }, + }); + + const sellOrders = await this.stockOrderRepository.find({ + where: { + stock_code: stockCode, + trade_type: TradeType.SELL, + status: StatusType.PENDING, + price: LessThanOrEqual(value), + }, + }); + + await Promise.all(buyOrders.map((buyOrder) => this.executeBuy(buyOrder))); + await Promise.all( + sellOrders.map((sellOrder) => this.executeSell(sellOrder)), + ); + + if ( + !(await this.stockOrderRepository.existsBy({ + stock_code: stockCode, + status: StatusType.PENDING, + })) + ) + this.unsubscribeByCode(stockCode); + } + + private async executeBuy(order) { + this.logger.log(`${order.id}번 매수 예약이 체결되었습니다.`, 'BUY'); + + const totalPrice = order.price * order.amount; + const fee = this.calculateFee(totalPrice); + await this.stockOrderRepository.updateOrderAndAssetWhenBuy( + order, + totalPrice + fee, + ); + } + + private async executeSell(order) { + this.logger.log(`${order.id}번 매도 예약이 체결되었습니다.`, 'SELL'); + + const totalPrice = order.price * order.amount; + const fee = this.calculateFee(totalPrice); + await this.stockOrderRepository.updateOrderAndAssetWhenSell( + order, + totalPrice - fee, + ); + } + + private calculateFee(totalPrice: number) { + if (totalPrice <= 10000000) return totalPrice * 0.16; + if (totalPrice > 10000000 && totalPrice <= 50000000) + return totalPrice * 0.14; + if (totalPrice > 50000000 && totalPrice <= 100000000) + return totalPrice * 0.12; + if (totalPrice > 100000000 && totalPrice <= 300000000) + return totalPrice * 0.1; + return totalPrice * 0.08; + } +} diff --git a/BE/src/stock/order/stock-order.module.ts b/BE/src/stock/order/stock-order.module.ts index 944813d5..454ca252 100644 --- a/BE/src/stock/order/stock-order.module.ts +++ b/BE/src/stock/order/stock-order.module.ts @@ -1,4 +1,4 @@ -import { forwardRef, Module } from '@nestjs/common'; +import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { StockOrderController } from './stock-order.controller'; import { StockOrderService } from './stock-order.service'; @@ -6,15 +6,11 @@ import { Order } from './stock-order.entity'; import { StockOrderRepository } from './stock-order.repository'; import { SocketModule } from '../../websocket/socket.module'; import { AssetModule } from '../../asset/asset.module'; +import { StockOrderSocketService } from './stock-order-socket.service'; @Module({ - imports: [ - TypeOrmModule.forFeature([Order]), - forwardRef(() => SocketModule), - AssetModule, - ], + imports: [TypeOrmModule.forFeature([Order]), SocketModule, AssetModule], controllers: [StockOrderController], - providers: [StockOrderService, StockOrderRepository], - exports: [StockOrderService], + providers: [StockOrderService, StockOrderRepository, StockOrderSocketService], }) export class StockOrderModule {} diff --git a/BE/src/stock/order/stock-order.service.ts b/BE/src/stock/order/stock-order.service.ts index 866a96cc..f0266010 100644 --- a/BE/src/stock/order/stock-order.service.ts +++ b/BE/src/stock/order/stock-order.service.ts @@ -1,8 +1,6 @@ import { ConflictException, ForbiddenException, - forwardRef, - Inject, Injectable, Logger, } from '@nestjs/common'; @@ -12,22 +10,15 @@ import { StockOrderRequestDto } from './dto/stock-order-request.dto'; import { StockOrderRepository } from './stock-order.repository'; import { TradeType } from './enum/trade-type'; import { StatusType } from './enum/status-type'; -import { StockPriceSocketService } from '../../websocket/stock/price/stock-price-socket.service'; +import { StockOrderSocketService } from './stock-order-socket.service'; @Injectable() export class StockOrderService { constructor( private readonly stockOrderRepository: StockOrderRepository, - @Inject(forwardRef(() => StockPriceSocketService)) - private readonly stockPriceSocketService: StockPriceSocketService, + private readonly stockOrderSocketService: StockOrderSocketService, ) {} - private readonly logger = new Logger(); - - async findAllPendingOrderCode() { - return this.stockOrderRepository.findAllCodeByStatus(); - } - async buy(userId: number, stockOrderRequest: StockOrderRequestDto) { const order = this.stockOrderRepository.create({ user_id: userId, @@ -39,7 +30,7 @@ export class StockOrderService { }); await this.stockOrderRepository.save(order); - this.stockPriceSocketService.subscribeByCode(stockOrderRequest.stock_code); + this.stockOrderSocketService.subscribeByCode(stockOrderRequest.stock_code); } async sell(userId: number, stockOrderRequest: StockOrderRequestDto) { @@ -53,7 +44,7 @@ export class StockOrderService { }); await this.stockOrderRepository.save(order); - this.stockPriceSocketService.subscribeByCode(stockOrderRequest.stock_code); + this.stockOrderSocketService.subscribeByCode(stockOrderRequest.stock_code); } async cancel(userId: number, orderId: number) { @@ -75,72 +66,6 @@ export class StockOrderService { status: StatusType.PENDING, })) ) - this.stockPriceSocketService.unsubscribeByCode(order.stock_code); - } - - async checkExecutableOrder(stockCode: string, value) { - const buyOrders = await this.stockOrderRepository.find({ - where: { - stock_code: stockCode, - trade_type: TradeType.BUY, - status: StatusType.PENDING, - price: MoreThanOrEqual(value), - }, - }); - - const sellOrders = await this.stockOrderRepository.find({ - where: { - stock_code: stockCode, - trade_type: TradeType.SELL, - status: StatusType.PENDING, - price: LessThanOrEqual(value), - }, - }); - - await Promise.all(buyOrders.map((buyOrder) => this.executeBuy(buyOrder))); - await Promise.all( - sellOrders.map((sellOrder) => this.executeSell(sellOrder)), - ); - - if ( - !(await this.stockOrderRepository.existsBy({ - stock_code: stockCode, - status: StatusType.PENDING, - })) - ) - this.stockPriceSocketService.unsubscribeByCode(stockCode); - } - - private async executeBuy(order) { - this.logger.log(`${order.id}번 매수 예약이 체결되었습니다.`, 'BUY'); - - const totalPrice = order.price * order.amount; - const fee = this.calculateFee(totalPrice); - await this.stockOrderRepository.updateOrderAndAssetWhenBuy( - order, - totalPrice + fee, - ); - } - - private async executeSell(order) { - this.logger.log(`${order.id}번 매도 예약이 체결되었습니다.`, 'SELL'); - - const totalPrice = order.price * order.amount; - const fee = this.calculateFee(totalPrice); - await this.stockOrderRepository.updateOrderAndAssetWhenSell( - order, - totalPrice - fee, - ); - } - - private calculateFee(totalPrice: number) { - if (totalPrice <= 10000000) return totalPrice * 0.16; - if (totalPrice > 10000000 && totalPrice <= 50000000) - return totalPrice * 0.14; - if (totalPrice > 50000000 && totalPrice <= 100000000) - return totalPrice * 0.12; - if (totalPrice > 100000000 && totalPrice <= 300000000) - return totalPrice * 0.1; - return totalPrice * 0.08; + this.stockOrderSocketService.unsubscribeByCode(order.stock_code); } } diff --git a/BE/src/websocket/socket.module.ts b/BE/src/websocket/socket.module.ts index 5dd0e042..e7a2dcc7 100644 --- a/BE/src/websocket/socket.module.ts +++ b/BE/src/websocket/socket.module.ts @@ -1,21 +1,10 @@ -import { forwardRef, Module } from '@nestjs/common'; -import { StockIndexSocketService } from './stock/index/stock-index-socket.service'; +import { Module } from '@nestjs/common'; import { SocketGateway } from './socket.gateway'; import { SocketTokenService } from './socket-token.service'; -import { StockPriceSocketService } from './stock/price/stock-price-socket.service'; import { BaseSocketService } from './base-socket.service'; -import { StockOrderModule } from '../stock/order/stock-order.module'; @Module({ - imports: [forwardRef(() => StockOrderModule)], - controllers: [], - providers: [ - SocketTokenService, - StockIndexSocketService, - SocketGateway, - StockPriceSocketService, - BaseSocketService, - ], - exports: [SocketGateway, StockPriceSocketService], + providers: [SocketTokenService, SocketGateway, BaseSocketService], + exports: [SocketGateway, BaseSocketService], }) export class SocketModule {} diff --git a/BE/src/websocket/stock/price/stock-price-socket.service.ts b/BE/src/websocket/stock/price/stock-price-socket.service.ts deleted file mode 100644 index 4f591c81..00000000 --- a/BE/src/websocket/stock/price/stock-price-socket.service.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { - forwardRef, - Inject, - Injectable, - InternalServerErrorException, -} from '@nestjs/common'; -import { BaseSocketService } from '../../base-socket.service'; -import { SocketGateway } from '../../socket.gateway'; -import { StockOrderService } from '../../../stock/order/stock-order.service'; -import { Order } from '../../../stock/order/stock-order.entity'; - -@Injectable() -export class StockPriceSocketService { - private TR_ID = 'H0STCNT0'; - - constructor( - private readonly socketGateway: SocketGateway, - private readonly baseSocketService: BaseSocketService, - @Inject(forwardRef(() => StockOrderService)) - private readonly stockOrderService: StockOrderService, - ) { - baseSocketService.registerSocketOpenHandler(async () => { - const orders: Order[] = await stockOrderService.findAllPendingOrderCode(); - orders.forEach((order) => { - baseSocketService.registerCode(this.TR_ID, order.stock_code); - }); - }); - - baseSocketService.registerSocketDataHandler( - this.TR_ID, - (data: string[]) => { - stockOrderService - .checkExecutableOrder( - data[0], // 주식 코드 - data[2], // 주식 체결가 - ) - .catch(() => { - throw new InternalServerErrorException(); - }); - }, - ); - } - - subscribeByCode(trKey: string) { - this.baseSocketService.registerCode(this.TR_ID, trKey); - } - - unsubscribeByCode(trKey: string) { - this.baseSocketService.unregisterCode(this.TR_ID, trKey); - } -}