initial: stock trading dashboard

This commit is contained in:
Kwon Hyo Seung
2026-05-12 01:48:48 +00:00
commit 02356ba4a9
19 changed files with 7834 additions and 0 deletions
+8
View File
@@ -0,0 +1,8 @@
{
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"deleteOutDir": true
}
}
+4672
View File
File diff suppressed because it is too large Load Diff
+25
View File
@@ -0,0 +1,25 @@
{
"name": "trading-bot-server",
"version": "1.0.0",
"scripts": {
"start": "nest start",
"start:dev": "nest start --watch",
"build": "nest build"
},
"dependencies": {
"@nestjs/common": "^10.0.0",
"@nestjs/core": "^10.0.0",
"@nestjs/platform-express": "^10.0.0",
"@nestjs/platform-socket.io": "^10.0.0",
"@nestjs/websockets": "^10.0.0",
"socket.io": "^4.6.0",
"rxjs": "^7.8.0",
"reflect-metadata": "^0.1.13"
},
"devDependencies": {
"@nestjs/cli": "^10.0.0",
"@nestjs/schematics": "^10.0.0",
"@types/node": "^20.0.0",
"typescript": "^5.0.0"
}
}
+10
View File
@@ -0,0 +1,10 @@
import { Module } from '@nestjs/common';
import { BotGateway } from './websocket/bot.gateway';
import { BotService } from './bot/bot.service';
@Module({
imports: [],
controllers: [],
providers: [BotGateway, BotService],
})
export class AppModule {}
+36
View File
@@ -0,0 +1,36 @@
import { Injectable, Logger } from '@nestjs/common';
@Injectable()
export class BotService {
private logger = new Logger('BotService');
// 봇 상태 데이터 저장
private botState = {
isRunning: false,
positions: [],
balance: 0,
totalProfit: 0,
logs: [],
};
getBotState() {
return this.botState;
}
updateBotState(state: any) {
this.botState = { ...this.botState, ...state };
this.logger.log('Bot state updated');
}
addLog(log: any) {
this.botState.logs.push({
...log,
timestamp: new Date().toISOString(),
});
// 최근 100개만 유지
if (this.botState.logs.length > 100) {
this.botState.logs = this.botState.logs.slice(-100);
}
}
}
+15
View File
@@ -0,0 +1,15 @@
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.enableCors({
origin: ['http://localhost:5173', 'http://stock.wageulwageul.com', 'http://3.34.1.212'],
credentials: true,
});
await app.listen(3001);
console.log('🚀 Trading Bot Dashboard Server running on http://localhost:3001');
}
bootstrap();
+98
View File
@@ -0,0 +1,98 @@
import {
WebSocketGateway,
WebSocketServer,
SubscribeMessage,
OnGatewayConnection,
OnGatewayDisconnect,
} from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';
import { Logger } from '@nestjs/common';
@WebSocketGateway({
cors: {
origin: ['http://localhost:5173', 'http://stock.wageulwageul.com', 'http://3.34.1.212', 'https://stock.wageulwageul.com'],
credentials: true,
},
})
export class BotGateway implements OnGatewayConnection, OnGatewayDisconnect {
@WebSocketServer()
server: Server;
private logger = new Logger('BotGateway');
private connectedClients = new Set<string>();
// 마지막 상태 캐시 - 새 클라이언트 접속 시 즉시 전송
private lastUpdate: any = null;
private recentLogs: any[] = [];
private readonly MAX_LOGS = 100;
handleConnection(client: Socket) {
this.connectedClients.add(client.id);
this.logger.log(`Client connected: ${client.id} (Total: ${this.connectedClients.size})`);
client.emit('connection:success', {
message: 'Connected to Trading Bot Dashboard',
timestamp: new Date().toISOString(),
});
// 새 클라이언트에게 마지막 상태 즉시 전송
if (this.lastUpdate) {
client.emit('dashboard:update', this.lastUpdate);
}
// 최근 로그 재전송 (최대 50개)
const logsToSend = this.recentLogs.slice(-50);
for (const log of logsToSend) {
client.emit('dashboard:log', log);
}
}
handleDisconnect(client: Socket) {
this.connectedClients.delete(client.id);
this.logger.log(`Client disconnected: ${client.id} (Total: ${this.connectedClients.size})`);
}
@SubscribeMessage('bot:update')
handleBotUpdate(client: Socket, payload: any) {
this.lastUpdate = payload;
this.server.emit('dashboard:update', payload);
}
@SubscribeMessage('bot:trade')
handleTrade(client: Socket, payload: any) {
this.logger.log(`Trade executed: ${payload.action} ${payload.stock_code}`);
this.server.emit('dashboard:trade', payload);
}
@SubscribeMessage('bot:log')
handleLog(client: Socket, payload: any) {
// 로그 캐시에 추가
this.recentLogs.push(payload);
if (this.recentLogs.length > this.MAX_LOGS) {
this.recentLogs.shift();
}
this.server.emit('dashboard:log', payload);
}
broadcastUpdate(data: any) {
this.lastUpdate = data;
this.server.emit('dashboard:update', data);
}
broadcastTrade(data: any) {
this.server.emit('dashboard:trade', data);
}
broadcastLog(message: string, level: string = 'info') {
const payload = {
message,
level,
timestamp: new Date().toISOString(),
};
this.recentLogs.push(payload);
if (this.recentLogs.length > this.MAX_LOGS) {
this.recentLogs.shift();
}
this.server.emit('dashboard:log', payload);
}
}
+21
View File
@@ -0,0 +1,21 @@
{
"compilerOptions": {
"module": "commonjs",
"declaration": true,
"removeComments": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"target": "ES2021",
"sourceMap": true,
"outDir": "./dist",
"baseUrl": "./",
"incremental": true,
"skipLibCheck": true,
"strictNullChecks": false,
"noImplicitAny": false,
"strictBindCallApply": false,
"forceConsistentCasingInFileNames": false,
"noFallthroughCasesInSwitch": false
}
}