모의투자 관리 기능 추가

This commit is contained in:
Macbook
2026-05-31 03:47:07 +09:00
parent 9d7dddfa57
commit 16d0e2c226
78 changed files with 4688 additions and 972 deletions
+180 -16
View File
@@ -610,21 +610,42 @@ export interface PaperPositionDto {
profitLossPct?: number;
}
export interface PaperAllocationDto {
id?: number;
symbol: string;
koreanName?: string | null;
maxInvestKrw: number;
budgetPct?: number | null;
strategyId?: number | null;
isActive?: boolean;
pinned?: boolean;
sortOrder?: number;
investedKrw?: number;
availableBuyKrw?: number;
evalAmount?: number;
symbolReturnPct?: number;
weightPct?: number;
}
export interface PaperSummaryDto {
enabled: boolean;
initialCapital: number;
cashBalance: number;
stockEvalAmount: number;
totalAsset: number;
unrealizedPnl: number;
realizedPnl: number;
totalReturnPct: number;
feeRatePct: number;
slippagePct: number;
minOrderKrw: number;
autoTradeEnabled: boolean;
autoTradeBudgetPct: number;
positions: PaperPositionDto[];
enabled: boolean;
initialCapital: number;
cashBalance: number;
stockEvalAmount: number;
totalAsset: number;
unrealizedPnl: number;
realizedPnl: number;
totalReturnPct: number;
feeRatePct: number;
slippagePct: number;
minOrderKrw: number;
autoTradeEnabled: boolean;
autoTradeBudgetPct: number;
reservedCash?: number;
orderableCash?: number;
initialCapitalSnapshot?: number;
positions: PaperPositionDto[];
allocations?: PaperAllocationDto[];
}
export interface PaperTradeDto {
@@ -640,13 +661,79 @@ export interface PaperTradeDto {
feeAmount: number;
netAmount: number;
cashAfter: number;
realizedPnlDelta?: number | null;
positionQtyAfter?: number | null;
createdAt?: string | null;
}
export interface PaperPageDto<T> {
content: T[];
page: number;
size: number;
totalElements: number;
totalPages: number;
}
export interface PaperLedgerEntryDto {
id: number;
entryType: string;
amount: number;
cashBefore: number;
cashAfter: number;
refTradeId?: number | null;
symbol?: string | null;
createdAt?: string | null;
}
export interface PaperDailySnapshotDto {
snapshotDate: string;
cashBalance: number;
stockEvalAmount: number;
totalAsset: number;
dailyPnl: number;
dailyReturnPct: number;
cumulativeReturnPct: number;
}
export interface PaperOrderDto {
id: number;
symbol: string;
side: 'BUY' | 'SELL';
orderType: string;
limitPrice?: number | null;
quantity: number;
filledQuantity: number;
status: string;
reservedCash: number;
reservedQty: number;
orderKind: string;
source: string;
strategyId?: number | null;
createdAt?: string | null;
updatedAt?: string | null;
}
export interface PaperPlaceOrderResult {
trade?: PaperTradeDto | null;
order?: PaperOrderDto | null;
}
export interface PaperAllocationBulkItem {
symbol: string;
koreanName?: string;
maxInvestKrw?: number;
budgetPct?: number;
strategyId?: number | null;
isActive?: boolean;
pinned?: boolean;
sortOrder?: number;
}
export interface PaperOrderRequest {
market: string;
side: 'BUY' | 'SELL';
orderKind?: 'limit' | 'market';
orderType?: 'MARKET' | 'LIMIT';
price: number;
quantity: number;
/** 수량 0일 때 매수 예산 비율 (가용현금 %) */
@@ -671,13 +758,90 @@ export async function loadPaperTrades(): Promise<PaperTradeDto[]> {
return (await request<PaperTradeDto[]>('/paper/trades')) ?? [];
}
export async function placePaperOrder(body: PaperOrderRequest): Promise<PaperTradeDto | null> {
return request<PaperTradeDto>('/paper/orders', {
export async function loadPaperTradesPaged(params?: {
symbol?: string;
side?: string;
source?: string;
from?: string;
to?: string;
page?: number;
size?: number;
}): Promise<PaperPageDto<PaperTradeDto>> {
const q = new URLSearchParams();
if (params?.symbol) q.set('symbol', params.symbol);
if (params?.side) q.set('side', params.side);
if (params?.source) q.set('source', params.source);
if (params?.from) q.set('from', params.from);
if (params?.to) q.set('to', params.to);
if (params?.page != null) q.set('page', String(params.page));
if (params?.size != null) q.set('size', String(params.size));
const qs = q.toString();
return (await request<PaperPageDto<PaperTradeDto>>(`/paper/trades${qs ? `?${qs}` : ''}`)) ?? {
content: [], page: 0, size: 50, totalElements: 0, totalPages: 0,
};
}
export async function loadPaperAllocations(): Promise<PaperAllocationDto[]> {
return (await request<PaperAllocationDto[]>('/paper/allocations')) ?? [];
}
export async function putPaperAllocationsBulk(items: PaperAllocationBulkItem[]): Promise<PaperAllocationDto[]> {
return (await request<PaperAllocationDto[]>('/paper/allocations/bulk', {
method: 'PUT',
body: JSON.stringify({ items }),
})) ?? [];
}
export async function patchPaperAllocation(
symbol: string,
body: Partial<Pick<PaperAllocationDto, 'maxInvestKrw' | 'budgetPct' | 'strategyId' | 'isActive' | 'pinned' | 'sortOrder' | 'koreanName'>>,
): Promise<PaperAllocationDto | null> {
return request<PaperAllocationDto>(`/paper/allocations/${encodeURIComponent(symbol)}`, {
method: 'PATCH',
body: JSON.stringify(body),
});
}
export async function loadPaperLedger(params?: {
from?: string;
to?: string;
page?: number;
size?: number;
}): Promise<PaperPageDto<PaperLedgerEntryDto>> {
const q = new URLSearchParams();
if (params?.from) q.set('from', params.from);
if (params?.to) q.set('to', params.to);
if (params?.page != null) q.set('page', String(params.page));
if (params?.size != null) q.set('size', String(params.size));
const qs = q.toString();
return (await request<PaperPageDto<PaperLedgerEntryDto>>(`/paper/ledger${qs ? `?${qs}` : ''}`)) ?? {
content: [], page: 0, size: 50, totalElements: 0, totalPages: 0,
};
}
export async function loadPaperDailySnapshots(from?: string, to?: string): Promise<PaperDailySnapshotDto[]> {
const q = new URLSearchParams();
if (from) q.set('from', from);
if (to) q.set('to', to);
const qs = q.toString();
return (await request<PaperDailySnapshotDto[]>(`/paper/snapshots/daily${qs ? `?${qs}` : ''}`)) ?? [];
}
export async function loadPaperPendingOrders(): Promise<PaperOrderDto[]> {
return (await request<PaperOrderDto[]>('/paper/orders')) ?? [];
}
export async function placePaperOrder(body: PaperOrderRequest): Promise<PaperPlaceOrderResult | null> {
return request<PaperPlaceOrderResult>('/paper/orders', {
method: 'POST',
body: JSON.stringify(body),
});
}
export async function cancelPaperOrder(orderId: number): Promise<PaperOrderDto | null> {
return request<PaperOrderDto>(`/paper/orders/${orderId}/cancel`, { method: 'POST' });
}
export async function resetPaperAccount(): Promise<PaperSummaryDto | null> {
return request<PaperSummaryDto>('/paper/reset', { method: 'POST' });
}