galaxis-po/backend/app/api/position_sizing.py
머니페니 12d235a1f1 feat: add 9 new modules - notification alerts, trading journal, position sizing, pension allocation, drawdown monitoring, benchmark dashboard, tax simulation, correlation analysis, parameter optimizer
Phase 1:
- Real-time signal alerts (Discord/Telegram webhook)
- Trading journal with entry/exit tracking
- Position sizing calculator (Fixed/Kelly/ATR)

Phase 2:
- Pension asset allocation (DC/IRP 70% risk limit)
- Drawdown monitoring with SVG gauge
- Benchmark dashboard (portfolio vs KOSPI vs deposit)

Phase 3:
- Tax benefit simulation (Korean pension tax rules)
- Correlation matrix heatmap
- Parameter optimizer with grid search + overfit detection
2026-03-29 10:03:08 +09:00

84 lines
2.8 KiB
Python

"""
Position sizing API endpoints.
"""
from fastapi import APIRouter, HTTPException
from app.api.deps import CurrentUser
from app.schemas.position_sizing import (
SizingMethod,
PositionSizeRequest,
PositionSizeResponse,
MethodInfo,
MethodsResponse,
)
from app.services.position_sizing import fixed_ratio, kelly_criterion, atr_based
router = APIRouter(prefix="/api/position-sizing", tags=["position-sizing"])
METHODS = [
MethodInfo(
name="fixed",
label="균등 분배 (Fixed Ratio)",
description="자본금을 종목 수로 균등 분배. 현금 비중 설정 가능. quant.md 기본 방식.",
),
MethodInfo(
name="kelly",
label="켈리 기준 (Kelly Criterion)",
description="승률과 손익비를 기반으로 최적 베팅 비율 계산. 보수적 1/4 켈리 기본.",
),
MethodInfo(
name="atr",
label="ATR 변동성 (ATR-Based)",
description="ATR(평균 진폭)을 이용한 변동성 기반 포지션 사이징.",
),
]
@router.post("/calculate", response_model=PositionSizeResponse)
async def calculate_position_size(
request: PositionSizeRequest,
current_user: CurrentUser,
):
"""Calculate position size based on selected method."""
try:
if request.method == SizingMethod.FIXED:
result = fixed_ratio(
capital=request.capital,
num_positions=request.num_positions,
cash_ratio=request.cash_ratio,
)
elif request.method == SizingMethod.KELLY:
if not all([request.win_rate, request.avg_win, request.avg_loss]):
raise HTTPException(
status_code=422,
detail="Kelly method requires win_rate, avg_win, avg_loss",
)
result = kelly_criterion(
win_rate=request.win_rate,
avg_win=request.avg_win,
avg_loss=request.avg_loss,
)
elif request.method == SizingMethod.ATR:
if not request.atr:
raise HTTPException(
status_code=422,
detail="ATR method requires atr value",
)
result = atr_based(
capital=request.capital,
atr=request.atr,
risk_pct=request.risk_pct,
)
else:
raise HTTPException(status_code=422, detail=f"Unknown method: {request.method}")
except ValueError as e:
raise HTTPException(status_code=422, detail=str(e))
return PositionSizeResponse(**result)
@router.get("/methods", response_model=MethodsResponse)
async def get_methods(current_user: CurrentUser):
"""List supported position sizing methods."""
return MethodsResponse(methods=METHODS)