전략분봉 저장 에러 문제 수정
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
import type { BacktestSignal } from './backendApi';
|
||||
|
||||
export interface EquityPoint {
|
||||
time: number;
|
||||
equity: number;
|
||||
}
|
||||
|
||||
export interface TradeMarker {
|
||||
time: number;
|
||||
equity: number;
|
||||
type: 'buy' | 'sell';
|
||||
price: number;
|
||||
pnlPct?: number;
|
||||
}
|
||||
|
||||
export interface TradeHistoryRow {
|
||||
id: number;
|
||||
time: number;
|
||||
symbol: string;
|
||||
side: 'buy' | 'sell';
|
||||
price: number;
|
||||
quantity: number;
|
||||
pnlPct?: number;
|
||||
}
|
||||
|
||||
const BUY_TYPES = new Set(['BUY']);
|
||||
const SELL_TYPES = new Set(['SELL', 'SHORT_EXIT', 'PARTIAL_SELL']);
|
||||
|
||||
export function buildEquityFromSignals(
|
||||
signals: BacktestSignal[],
|
||||
initialCapital: number,
|
||||
symbol: string,
|
||||
): { curve: EquityPoint[]; markers: TradeMarker[]; trades: TradeHistoryRow[] } {
|
||||
const sorted = [...signals].sort((a, b) => a.time - b.time);
|
||||
const curve: EquityPoint[] = [];
|
||||
const markers: TradeMarker[] = [];
|
||||
const trades: TradeHistoryRow[] = [];
|
||||
|
||||
if (sorted.length === 0) {
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
return {
|
||||
curve: [{ time: now, equity: initialCapital }],
|
||||
markers: [],
|
||||
trades: [],
|
||||
};
|
||||
}
|
||||
|
||||
let cash = initialCapital;
|
||||
let qty = 0;
|
||||
let entryPrice = 0;
|
||||
let tradeId = 0;
|
||||
|
||||
const pushCurve = (time: number, price: number) => {
|
||||
const equity = qty > 0 ? qty * price : cash;
|
||||
const last = curve[curve.length - 1];
|
||||
if (!last || last.time !== time || Math.abs(last.equity - equity) > 0.01) {
|
||||
curve.push({ time, equity });
|
||||
}
|
||||
};
|
||||
|
||||
pushCurve(sorted[0].time, sorted[0].price);
|
||||
|
||||
for (const s of sorted) {
|
||||
if (BUY_TYPES.has(s.type) && qty === 0 && cash > 0) {
|
||||
qty = cash / s.price;
|
||||
entryPrice = s.price;
|
||||
cash = 0;
|
||||
tradeId += 1;
|
||||
const eq = qty * s.price;
|
||||
trades.push({
|
||||
id: tradeId,
|
||||
time: s.time,
|
||||
symbol,
|
||||
side: 'buy',
|
||||
price: s.price,
|
||||
quantity: qty,
|
||||
});
|
||||
markers.push({ time: s.time, equity: eq, type: 'buy', price: s.price });
|
||||
pushCurve(s.time, s.price);
|
||||
} else if (SELL_TYPES.has(s.type) && qty > 0) {
|
||||
const pnlPct = entryPrice > 0 ? (s.price - entryPrice) / entryPrice : 0;
|
||||
cash = qty * s.price;
|
||||
tradeId += 1;
|
||||
trades.push({
|
||||
id: tradeId,
|
||||
time: s.time,
|
||||
symbol,
|
||||
side: 'sell',
|
||||
price: s.price,
|
||||
quantity: qty,
|
||||
pnlPct,
|
||||
});
|
||||
markers.push({ time: s.time, equity: cash, type: 'sell', price: s.price, pnlPct });
|
||||
qty = 0;
|
||||
entryPrice = 0;
|
||||
pushCurve(s.time, s.price);
|
||||
} else {
|
||||
pushCurve(s.time, s.price);
|
||||
}
|
||||
}
|
||||
|
||||
if (curve.length === 1 && sorted.length > 0) {
|
||||
const last = sorted[sorted.length - 1];
|
||||
const equity = qty > 0 ? qty * last.price : cash;
|
||||
curve.push({ time: last.time, equity });
|
||||
}
|
||||
|
||||
return { curve, markers, trades };
|
||||
}
|
||||
|
||||
export function sparklinePath(values: number[], w: number, h: number): string {
|
||||
if (values.length < 2) {
|
||||
const y = h * 0.5;
|
||||
return `M0,${y} L${w},${y}`;
|
||||
}
|
||||
const min = Math.min(...values);
|
||||
const max = Math.max(...values);
|
||||
const range = max - min || 1;
|
||||
return values.map((v, i) => {
|
||||
const x = (i / (values.length - 1)) * w;
|
||||
const y = h - 4 - ((v - min) / range) * (h - 8);
|
||||
return `${i === 0 ? 'M' : 'L'}${x.toFixed(1)},${y.toFixed(1)}`;
|
||||
}).join(' ');
|
||||
}
|
||||
|
||||
export function equityToSparkline(curve: EquityPoint[]): number[] {
|
||||
if (curve.length <= 1) return [0, 1];
|
||||
return curve.map(p => p.equity);
|
||||
}
|
||||
Reference in New Issue
Block a user