분석레포트 로직 수정
This commit is contained in:
@@ -1,16 +1,21 @@
|
||||
import type { PaperSummaryDto, PaperTradeDto } from './backendApi';
|
||||
import { computePaperMetrics } from './paperMetrics';
|
||||
import { buildAnalysisFromPaperTrades, computePaperMetrics } from './paperMetrics';
|
||||
|
||||
export interface LiveExecutionItem {
|
||||
id: string;
|
||||
symbol: string;
|
||||
strategyLabel: string;
|
||||
sourceLabel: string;
|
||||
/** 체결 시점 평가 시간봉 */
|
||||
timeframe: string;
|
||||
executionType?: string;
|
||||
strategyId?: number | null;
|
||||
trades: PaperTradeDto[];
|
||||
createdAt: string;
|
||||
totalReturnPct: number;
|
||||
winRatePct: number;
|
||||
tradeCount: number;
|
||||
roundTripCount: number;
|
||||
mddPct: number;
|
||||
sharpeRatio: number;
|
||||
profitFactor: number;
|
||||
@@ -26,6 +31,27 @@ function sourceLabel(source: string): string {
|
||||
return '수동';
|
||||
}
|
||||
|
||||
function resolveStrategyLabel(
|
||||
t: PaperTradeDto,
|
||||
strategyNames: Record<number, string>,
|
||||
): string {
|
||||
if (t.strategyName) return t.strategyName;
|
||||
if (t.strategyId != null) {
|
||||
return strategyNames[t.strategyId] ?? `전략 #${t.strategyId}`;
|
||||
}
|
||||
return '수동 매매';
|
||||
}
|
||||
|
||||
/** 자동매매: 종목·전략·시간봉·실행방식 기준 / 수동: 종목·일자 기준 */
|
||||
function groupKey(t: PaperTradeDto): string {
|
||||
if (t.source === 'STRATEGY') {
|
||||
const tf = t.candleType ?? 'unknown';
|
||||
const exec = t.executionType ?? '';
|
||||
return `${t.symbol}|${t.strategyId ?? 0}|${tf}|${exec}|STRATEGY`;
|
||||
}
|
||||
return `${t.symbol}|manual|${dayKey(t.createdAt)}|MANUAL`;
|
||||
}
|
||||
|
||||
/** 가상투자·모의투자 체결 이력을 실행 단위 목록으로 그룹 */
|
||||
export function buildLiveExecutionItems(
|
||||
trades: PaperTradeDto[],
|
||||
@@ -36,7 +62,7 @@ export function buildLiveExecutionItems(
|
||||
|
||||
const groups = new Map<string, PaperTradeDto[]>();
|
||||
for (const t of trades) {
|
||||
const key = `${t.symbol}|${t.strategyId ?? 0}|${dayKey(t.createdAt)}|${t.source}`;
|
||||
const key = groupKey(t);
|
||||
const list = groups.get(key) ?? [];
|
||||
list.push(t);
|
||||
groups.set(key, list);
|
||||
@@ -51,26 +77,25 @@ export function buildLiveExecutionItems(
|
||||
);
|
||||
const first = sorted[0];
|
||||
const last = sorted[sorted.length - 1];
|
||||
const analysis = buildAnalysisFromPaperTrades(sorted, initial);
|
||||
const metrics = computePaperMetrics(sorted, summary);
|
||||
const buyVol = sorted.filter(t => t.side === 'BUY').reduce((s, t) => s + t.netAmount, 0);
|
||||
const sellVol = sorted.filter(t => t.side === 'SELL').reduce((s, t) => s + t.netAmount, 0);
|
||||
const pnl = sellVol - buyVol;
|
||||
const totalReturnPct = initial > 0 ? pnl / initial : 0;
|
||||
|
||||
return {
|
||||
id: key,
|
||||
symbol: first.symbol,
|
||||
strategyLabel: first.strategyId != null
|
||||
? (strategyNames[first.strategyId] ?? `전략 #${first.strategyId}`)
|
||||
: '수동 매매',
|
||||
strategyLabel: resolveStrategyLabel(first, strategyNames),
|
||||
sourceLabel: sourceLabel(first.source),
|
||||
timeframe: first.candleType ?? 'unknown',
|
||||
executionType: first.executionType ?? undefined,
|
||||
strategyId: first.strategyId,
|
||||
trades: sorted,
|
||||
createdAt: last.createdAt ?? first.createdAt ?? new Date().toISOString(),
|
||||
totalReturnPct,
|
||||
winRatePct: metrics.winRatePct / 100,
|
||||
totalReturnPct: analysis.totalReturnPct,
|
||||
winRatePct: analysis.winRate,
|
||||
tradeCount: sorted.length,
|
||||
mddPct: metrics.mddPct / 100,
|
||||
sharpeRatio: metrics.sharpeRatio,
|
||||
roundTripCount: analysis.numberOfPositions,
|
||||
mddPct: analysis.maxDrawdownPct,
|
||||
sharpeRatio: analysis.sharpeRatio,
|
||||
profitFactor: metrics.profitFactor,
|
||||
};
|
||||
})
|
||||
@@ -84,6 +109,7 @@ export function paperTradesToSignals(trades: PaperTradeDto[]) {
|
||||
time: Math.floor(new Date(t.createdAt!).getTime() / 1000),
|
||||
type: t.side as 'BUY' | 'SELL',
|
||||
price: t.price,
|
||||
quantity: t.quantity,
|
||||
barIndex: 0,
|
||||
}));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user