""" 매매 규칙(TradingRule) 관리 API 엔드포인트 - GET /: 전체 매매 규칙 목록 조회 - GET /{rule_id}: 특정 규칙 조회 - POST /: 새 규칙 생성 - PUT /{rule_id}: 규칙 수정 - DELETE /{rule_id}: 규칙 삭제 - PATCH /{rule_id}/toggle: 규칙 활성화/비활성화 토글 """ from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select, update, delete from typing import List from datetime import datetime from app.core.database import get_db from app.core.security import require_api_key from app.models.models import TradingRule from pydantic import BaseModel from app.core.timeutil import now_kst router = APIRouter() class TradingRuleCreate(BaseModel): name: str description: str = None rule_type: str conditions: dict actions: dict is_active: bool = True priority: int = 0 class TradingRuleUpdate(BaseModel): name: str = None description: str = None rule_type: str = None conditions: dict = None actions: dict = None is_active: bool = None priority: int = None class TradingRuleResponse(BaseModel): id: int name: str description: str = None rule_type: str conditions: dict actions: dict is_active: bool priority: int created_at: datetime updated_at: datetime class Config: from_attributes = True @router.get("", response_model=List[TradingRuleResponse]) async def get_trading_rules( skip: int = 0, limit: int = 100, is_active: bool = None, db: AsyncSession = Depends(get_db) ): """트레이딩 규칙 목록 조회""" query = select(TradingRule) if is_active is not None: query = query.where(TradingRule.is_active == is_active) query = query.order_by(TradingRule.priority.desc(), TradingRule.created_at.desc()) query = query.offset(skip).limit(limit) result = await db.execute(query) rules = result.scalars().all() return rules @router.get("/{rule_id}", response_model=TradingRuleResponse) async def get_trading_rule(rule_id: int, db: AsyncSession = Depends(get_db)): """특정 트레이딩 규칙 조회""" result = await db.execute(select(TradingRule).where(TradingRule.id == rule_id)) rule = result.scalar_one_or_none() if not rule: raise HTTPException(status_code=404, detail="Trading rule not found") return rule @router.post("", response_model=TradingRuleResponse, dependencies=[Depends(require_api_key)]) async def create_trading_rule(rule: TradingRuleCreate, db: AsyncSession = Depends(get_db)): """새 트레이딩 규칙 생성""" db_rule = TradingRule(**rule.model_dump()) db.add(db_rule) await db.commit() await db.refresh(db_rule) return db_rule @router.put("/{rule_id}", response_model=TradingRuleResponse, dependencies=[Depends(require_api_key)]) async def update_trading_rule( rule_id: int, rule: TradingRuleUpdate, db: AsyncSession = Depends(get_db) ): """트레이딩 규칙 수정""" result = await db.execute(select(TradingRule).where(TradingRule.id == rule_id)) db_rule = result.scalar_one_or_none() if not db_rule: raise HTTPException(status_code=404, detail="Trading rule not found") update_data = rule.model_dump(exclude_unset=True) for field, value in update_data.items(): setattr(db_rule, field, value) db_rule.updated_at = now_kst() await db.commit() await db.refresh(db_rule) return db_rule @router.delete("/{rule_id}", dependencies=[Depends(require_api_key)]) async def delete_trading_rule(rule_id: int, db: AsyncSession = Depends(get_db)): """트레이딩 규칙 삭제""" result = await db.execute(select(TradingRule).where(TradingRule.id == rule_id)) db_rule = result.scalar_one_or_none() if not db_rule: raise HTTPException(status_code=404, detail="Trading rule not found") await db.delete(db_rule) await db.commit() return {"message": "Trading rule deleted successfully"} @router.post("/{rule_id}/toggle", dependencies=[Depends(require_api_key)]) async def toggle_trading_rule(rule_id: int, db: AsyncSession = Depends(get_db)): """트레이딩 규칙 활성화/비활성화 토글""" result = await db.execute(select(TradingRule).where(TradingRule.id == rule_id)) db_rule = result.scalar_one_or_none() if not db_rule: raise HTTPException(status_code=404, detail="Trading rule not found") db_rule.is_active = not db_rule.is_active db_rule.updated_at = now_kst() await db.commit() await db.refresh(db_rule) return db_rule