전략분봉 저장 에러 문제 수정
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
import type { PaperSummaryDto, PaperTradeDto } from './backendApi';
|
||||
|
||||
export interface PaperPerformanceMetrics {
|
||||
mddPct: number;
|
||||
sharpeRatio: number;
|
||||
winRatePct: number;
|
||||
profitFactor: number;
|
||||
}
|
||||
|
||||
function buildEquityCurve(
|
||||
trades: PaperTradeDto[],
|
||||
initialCapital: number,
|
||||
): number[] {
|
||||
const sorted = [...trades].sort((a, b) =>
|
||||
(a.createdAt ?? '').localeCompare(b.createdAt ?? ''),
|
||||
);
|
||||
const curve: number[] = [initialCapital];
|
||||
let cash = initialCapital;
|
||||
const holdings = new Map<string, { qty: number; cost: number }>();
|
||||
|
||||
for (const t of sorted) {
|
||||
const pos = holdings.get(t.symbol) ?? { qty: 0, cost: 0 };
|
||||
if (t.side === 'BUY') {
|
||||
cash -= t.netAmount;
|
||||
pos.qty += t.quantity;
|
||||
pos.cost += t.netAmount;
|
||||
} else {
|
||||
cash += t.netAmount;
|
||||
const avg = pos.qty > 0 ? pos.cost / pos.qty : 0;
|
||||
pos.qty = Math.max(0, pos.qty - t.quantity);
|
||||
pos.cost = pos.qty > 0 ? avg * pos.qty : 0;
|
||||
}
|
||||
holdings.set(t.symbol, pos);
|
||||
let stockVal = 0;
|
||||
holdings.forEach(p => {
|
||||
if (p.qty > 0) stockVal += p.cost;
|
||||
});
|
||||
curve.push(cash + stockVal);
|
||||
}
|
||||
return curve;
|
||||
}
|
||||
|
||||
function maxDrawdownPct(curve: number[]): number {
|
||||
if (curve.length < 2) return 0;
|
||||
let peak = curve[0];
|
||||
let mdd = 0;
|
||||
for (const v of curve) {
|
||||
if (v > peak) peak = v;
|
||||
if (peak > 0) {
|
||||
const dd = ((peak - v) / peak) * 100;
|
||||
if (dd > mdd) mdd = dd;
|
||||
}
|
||||
}
|
||||
return mdd;
|
||||
}
|
||||
|
||||
function sharpeFromCurve(curve: number[]): number {
|
||||
if (curve.length < 3) return 0;
|
||||
const rets: number[] = [];
|
||||
for (let i = 1; i < curve.length; i++) {
|
||||
const prev = curve[i - 1];
|
||||
if (prev > 0) rets.push((curve[i] - prev) / prev);
|
||||
}
|
||||
if (!rets.length) return 0;
|
||||
const mean = rets.reduce((a, b) => a + b, 0) / rets.length;
|
||||
const variance = rets.reduce((a, r) => a + (r - mean) ** 2, 0) / rets.length;
|
||||
const std = Math.sqrt(variance);
|
||||
if (std === 0) return mean > 0 ? 2.45 : 0;
|
||||
return (mean / std) * Math.sqrt(252);
|
||||
}
|
||||
|
||||
function roundTripStats(trades: PaperTradeDto[]): { wins: number; total: number; grossProfit: number; grossLoss: number } {
|
||||
const bySymbol = new Map<string, PaperTradeDto[]>();
|
||||
for (const t of trades) {
|
||||
const list = bySymbol.get(t.symbol) ?? [];
|
||||
list.push(t);
|
||||
bySymbol.set(t.symbol, list);
|
||||
}
|
||||
let wins = 0;
|
||||
let total = 0;
|
||||
let grossProfit = 0;
|
||||
let grossLoss = 0;
|
||||
|
||||
bySymbol.forEach(list => {
|
||||
const sorted = [...list].sort((a, b) => (a.createdAt ?? '').localeCompare(b.createdAt ?? ''));
|
||||
let buyCost = 0;
|
||||
let buyQty = 0;
|
||||
for (const t of sorted) {
|
||||
if (t.side === 'BUY') {
|
||||
buyCost += t.netAmount;
|
||||
buyQty += t.quantity;
|
||||
} else if (buyQty > 0) {
|
||||
const avg = buyCost / buyQty;
|
||||
const pnl = (t.price - avg) * t.quantity - t.feeAmount;
|
||||
total += 1;
|
||||
if (pnl >= 0) {
|
||||
wins += 1;
|
||||
grossProfit += pnl;
|
||||
} else {
|
||||
grossLoss += Math.abs(pnl);
|
||||
}
|
||||
buyQty = Math.max(0, buyQty - t.quantity);
|
||||
buyCost = buyQty > 0 ? avg * buyQty : 0;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return { wins, total, grossProfit, grossLoss };
|
||||
}
|
||||
|
||||
export function computePaperMetrics(
|
||||
trades: PaperTradeDto[],
|
||||
summary: PaperSummaryDto | null,
|
||||
): PaperPerformanceMetrics {
|
||||
const initial = summary?.initialCapital ?? 10_000_000;
|
||||
const curve = buildEquityCurve(trades, initial);
|
||||
const { wins, total, grossProfit, grossLoss } = roundTripStats(trades);
|
||||
|
||||
const winRatePct = total > 0 ? (wins / total) * 100 : 0;
|
||||
const profitFactor = grossLoss > 0 ? grossProfit / grossLoss : grossProfit > 0 ? 99 : 0;
|
||||
|
||||
return {
|
||||
mddPct: maxDrawdownPct(curve),
|
||||
sharpeRatio: sharpeFromCurve(curve),
|
||||
winRatePct,
|
||||
profitFactor: Math.min(profitFactor, 99),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user