import { Injectable } from '@nestjs/common'; import { PriceSnapshotRepository, PriceSnapshotEntity } from '../../infrastructure/persistence/repositories/price-snapshot.repository'; export interface PriceDto { snapshotTime: Date; price: string; sharePool: string; blackHoleAmount: string; circulationPool: string; effectiveDenominator: string; } export interface KLineDataDto { time: Date; open: string; high: string; low: string; close: string; } @Injectable() export class GetPriceQuery { constructor(private readonly priceRepository: PriceSnapshotRepository) {} async getCurrentPrice(): Promise { const snapshot = await this.priceRepository.getLatestSnapshot(); if (!snapshot) { return null; } return this.toDto(snapshot); } async getPriceAt(time: Date): Promise { const snapshot = await this.priceRepository.getSnapshotAt(time); if (!snapshot) { return null; } return this.toDto(snapshot); } async getPriceHistory( startTime: Date, endTime: Date, interval: 'minute' | 'hour' | 'day', ): Promise { const snapshots = await this.priceRepository.getPriceHistory(startTime, endTime, interval); return snapshots.map((s) => this.toDto(s)); } async getKLineData( startTime: Date, endTime: Date, interval: 'minute' | 'hour' | 'day', ): Promise { const snapshots = await this.priceRepository.getPriceHistory(startTime, endTime, 'minute'); // 按间隔分组 const grouped = new Map(); for (const snapshot of snapshots) { let key: string; if (interval === 'minute') { key = snapshot.snapshotTime.toISOString().substring(0, 16); } else if (interval === 'hour') { key = snapshot.snapshotTime.toISOString().substring(0, 13); } else { key = snapshot.snapshotTime.toISOString().substring(0, 10); } if (!grouped.has(key)) { grouped.set(key, []); } grouped.get(key)!.push(snapshot); } // 生成 K 线数据 const kLineData: KLineDataDto[] = []; for (const [key, items] of grouped) { if (items.length === 0) continue; const prices = items.map((i) => i.price.value.toNumber()); const times = items.map((i) => i.snapshotTime); kLineData.push({ time: times[0], open: items[0].price.toString(), high: Math.max(...prices).toString(), low: Math.min(...prices).toString(), close: items[items.length - 1].price.toString(), }); } return kLineData.sort((a, b) => a.time.getTime() - b.time.getTime()); } private toDto(snapshot: PriceSnapshotEntity): PriceDto { return { snapshotTime: snapshot.snapshotTime, price: snapshot.price.toString(), sharePool: snapshot.sharePool.toString(), blackHoleAmount: snapshot.blackHoleAmount.toString(), circulationPool: snapshot.circulationPool.toString(), effectiveDenominator: snapshot.effectiveDenominator.toString(), }; } }