galaxis-po/backend/app/api/pension.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

158 lines
4.5 KiB
Python

"""
Pension account API endpoints.
"""
from typing import List
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session, joinedload
from app.core.database import get_db
from app.api.deps import CurrentUser
from app.models.pension import PensionAccount, PensionHolding
from app.schemas.pension import (
PensionAccountCreate,
PensionAccountUpdate,
PensionAccountResponse,
AllocationResult,
RecommendationResult,
)
from app.services.pension_allocation import calculate_allocation, get_recommendation
router = APIRouter(prefix="/api/pension", tags=["pension"])
@router.post("/accounts", response_model=PensionAccountResponse, status_code=201)
async def create_account(
data: PensionAccountCreate,
current_user: CurrentUser,
db: Session = Depends(get_db),
):
account = PensionAccount(
user_id=current_user.id,
**data.model_dump(),
)
db.add(account)
db.commit()
db.refresh(account)
return account
@router.get("/accounts", response_model=List[PensionAccountResponse])
async def list_accounts(
current_user: CurrentUser,
db: Session = Depends(get_db),
skip: int = Query(0, ge=0),
limit: int = Query(50, ge=1, le=200),
):
accounts = (
db.query(PensionAccount)
.options(joinedload(PensionAccount.holdings))
.filter(PensionAccount.user_id == current_user.id)
.order_by(PensionAccount.created_at.desc())
.offset(skip)
.limit(limit)
.all()
)
return accounts
@router.get("/accounts/{account_id}", response_model=PensionAccountResponse)
async def get_account(
account_id: int,
current_user: CurrentUser,
db: Session = Depends(get_db),
):
account = (
db.query(PensionAccount)
.options(joinedload(PensionAccount.holdings))
.filter(PensionAccount.id == account_id, PensionAccount.user_id == current_user.id)
.first()
)
if not account:
raise HTTPException(status_code=404, detail="Pension account not found")
return account
@router.put("/accounts/{account_id}", response_model=PensionAccountResponse)
async def update_account(
account_id: int,
data: PensionAccountUpdate,
current_user: CurrentUser,
db: Session = Depends(get_db),
):
account = (
db.query(PensionAccount)
.options(joinedload(PensionAccount.holdings))
.filter(PensionAccount.id == account_id, PensionAccount.user_id == current_user.id)
.first()
)
if not account:
raise HTTPException(status_code=404, detail="Pension account not found")
update_data = data.model_dump(exclude_unset=True)
for field, value in update_data.items():
setattr(account, field, value)
db.commit()
db.refresh(account)
return account
@router.post("/accounts/{account_id}/allocate", response_model=AllocationResult)
async def allocate_assets(
account_id: int,
current_user: CurrentUser,
db: Session = Depends(get_db),
):
account = (
db.query(PensionAccount)
.filter(PensionAccount.id == account_id, PensionAccount.user_id == current_user.id)
.first()
)
if not account:
raise HTTPException(status_code=404, detail="Pension account not found")
result = calculate_allocation(
account_id=account.id,
account_type=account.account_type.value,
total_amount=account.total_amount,
birth_year=account.birth_year,
target_retirement_age=account.target_retirement_age,
)
# Save allocation as holdings
db.query(PensionHolding).filter(PensionHolding.account_id == account_id).delete()
for alloc in result.allocations:
holding = PensionHolding(
account_id=account_id,
asset_name=alloc.asset_name,
asset_type=alloc.asset_type,
amount=alloc.amount,
ratio=alloc.ratio,
)
db.add(holding)
db.commit()
return result
@router.get("/accounts/{account_id}/recommendation", response_model=RecommendationResult)
async def get_account_recommendation(
account_id: int,
current_user: CurrentUser,
db: Session = Depends(get_db),
):
account = (
db.query(PensionAccount)
.filter(PensionAccount.id == account_id, PensionAccount.user_id == current_user.id)
.first()
)
if not account:
raise HTTPException(status_code=404, detail="Pension account not found")
return get_recommendation(
account_id=account.id,
birth_year=account.birth_year,
target_retirement_age=account.target_retirement_age,
)