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
84 lines
2.8 KiB
Python
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)
|