feat: day_trading _execute_entry MCP 리스크 평가 연동 + .env.example MCP_CORS_ORIGINS 문서화
This commit is contained in:
@@ -217,7 +217,7 @@ async def _record_trade(
|
||||
executed_price=price,
|
||||
executed_quantity=quantity,
|
||||
total_amount=total,
|
||||
net_profit_loss=profit_loss, # ← net_profit_loss (profit_loss 컬럼 없음)
|
||||
net_profit_loss=profit_loss,
|
||||
profit_loss_rate=(profit_loss / total * 100) if total > 0 else 0,
|
||||
slippage=slippage,
|
||||
status="COMPLETED",
|
||||
@@ -299,7 +299,7 @@ async def run_day_trading_cycle() -> Dict[str, Any]:
|
||||
available_cash = portfolio["available_cash"]
|
||||
global _last_total_assets
|
||||
if total_assets > 0:
|
||||
_last_total_assets = total_assets # is_daily_loss_exceeded 계산용 캐시 갱신
|
||||
_last_total_assets = total_assets
|
||||
|
||||
if not is_daily_loss_exceeded(total_assets, _daily_pnl):
|
||||
current_day_trade_amount = sum(
|
||||
@@ -415,7 +415,11 @@ async def _check_exits(
|
||||
)
|
||||
await db.execute(
|
||||
sa_update(ActiveTrade)
|
||||
.where(ActiveTrade.stock_code == code, ActiveTrade.strategy_name == "day_trade", ActiveTrade.status == "active")
|
||||
.where(
|
||||
ActiveTrade.stock_code == code,
|
||||
ActiveTrade.strategy_name == "day_trade",
|
||||
ActiveTrade.status == "active",
|
||||
)
|
||||
.values(status="closed")
|
||||
)
|
||||
await db.commit()
|
||||
@@ -515,7 +519,11 @@ async def _scan_signals(
|
||||
|
||||
|
||||
async def _execute_entry(signal: DayTradeSignal, quantity: int) -> Optional[Dict]:
|
||||
"""진입 주문 실행 및 포지션 등록"""
|
||||
"""진입 주문 실행 및 포지션 등록.
|
||||
|
||||
주문 직전 MCP RISK_ASSESSMENT를 호출해 approve 시에만 매수한다 (이슈 4).
|
||||
MCP 장애/타임아웃은 fail-open(approve=True)으로 처리해 매매를 차단하지 않는다.
|
||||
"""
|
||||
global _trade_lock
|
||||
async with _trade_lock:
|
||||
if len(_positions) >= MAX_POSITIONS:
|
||||
@@ -526,6 +534,39 @@ async def _execute_entry(signal: DayTradeSignal, quantity: int) -> Optional[Dict
|
||||
logger.info(f"⛔ 당일 청산 종목 재진입 차단: {signal.stock_code}")
|
||||
return None
|
||||
|
||||
# ── MCP RISK_ASSESSMENT (공통 함수, 이슈 4 — day_trading 측) ────────────
|
||||
from app.api.endpoints.mcp import request_mcp_risk_assessment
|
||||
_mcp_trade_plan = {
|
||||
"action": "BUY",
|
||||
"strategy": signal.strategy,
|
||||
"stock_code": signal.stock_code,
|
||||
"stock_name": signal.stock_name,
|
||||
"entry_price": signal.entry_price,
|
||||
"stop_loss": signal.stop_loss,
|
||||
"target_price": signal.target_price,
|
||||
"quantity": quantity,
|
||||
"reason": signal.reason,
|
||||
}
|
||||
_mcp_signals = {
|
||||
"strategy": signal.strategy,
|
||||
"confidence": signal.confidence,
|
||||
"reason": signal.reason,
|
||||
}
|
||||
_approve, _mcp_reason = await request_mcp_risk_assessment(
|
||||
_mcp_trade_plan, {}, _mcp_signals
|
||||
)
|
||||
if not _approve:
|
||||
logger.warning(
|
||||
f"🛑 MCP RISK_ASSESSMENT DENY [{signal.stock_code}] "
|
||||
f"→ 주문 스킵. 사유: {_mcp_reason}"
|
||||
)
|
||||
return None
|
||||
if _mcp_reason:
|
||||
logger.info(
|
||||
f"✅ MCP RISK_ASSESSMENT APPROVE [{signal.stock_code}]. 참고: {_mcp_reason}"
|
||||
)
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
order_no = await _place_buy_order(
|
||||
signal.stock_code, quantity, int(signal.entry_price)
|
||||
)
|
||||
@@ -608,7 +649,11 @@ async def _execute_eod_flat() -> None:
|
||||
)
|
||||
await db.execute(
|
||||
sa_update(ActiveTrade)
|
||||
.where(ActiveTrade.stock_code == code, ActiveTrade.strategy_name == "day_trade", ActiveTrade.status == "active")
|
||||
.where(
|
||||
ActiveTrade.stock_code == code,
|
||||
ActiveTrade.strategy_name == "day_trade",
|
||||
ActiveTrade.status == "active",
|
||||
)
|
||||
.values(status="closed")
|
||||
)
|
||||
await db.commit()
|
||||
@@ -637,7 +682,7 @@ async def restore_positions_from_db() -> None:
|
||||
if _positions:
|
||||
return # 이미 포지션 있으면 스킵
|
||||
from datetime import timezone
|
||||
today_utc = datetime.now(timezone.utc).date() # UTC 기준 오늘 날짜 (created_at과 동일 기준)
|
||||
today_utc = datetime.now(timezone.utc).date()
|
||||
try:
|
||||
async with AsyncSessionLocal() as db:
|
||||
result = await db.execute(
|
||||
@@ -648,11 +693,9 @@ async def restore_positions_from_db() -> None:
|
||||
)
|
||||
rows = result.scalars().all()
|
||||
for row in rows:
|
||||
# created_at은 UTC 저장 → UTC 날짜로 비교
|
||||
if row.created_at and row.created_at.date() != today_utc:
|
||||
continue
|
||||
entry_price = float(row.entry_price)
|
||||
# entry_time 표시: UTC → KST 변환 (UTC+9)
|
||||
if row.created_at:
|
||||
created_kst = row.created_at.replace(tzinfo=pytz.utc).astimezone(KST)
|
||||
entry_time_str = created_kst.strftime("%H:%M:%S")
|
||||
@@ -839,7 +882,7 @@ async def get_positions():
|
||||
"count": len(result),
|
||||
"daily_pnl": round(_daily_pnl, 0),
|
||||
"is_daily_loss_exceeded": is_daily_loss_exceeded(
|
||||
_last_total_assets, # 사이클 캐시 사용 (포지션 가치가 아닌 총자산)
|
||||
_last_total_assets,
|
||||
_daily_pnl,
|
||||
),
|
||||
}
|
||||
|
||||
@@ -9,6 +9,15 @@ CLAUDE_MODEL=claude-3-5-sonnet-20241022
|
||||
CLAUDE_MAX_TOKENS=4096
|
||||
CLAUDE_TEMPERATURE=0.7
|
||||
|
||||
# Gemini AI Configuration (AI_PROVIDER=gemini 시 사용)
|
||||
GEMINI_API_KEY=your_gemini_api_key_here
|
||||
GEMINI_MODEL=gemini-2.0-flash
|
||||
GEMINI_MAX_TOKENS=1024
|
||||
GEMINI_TEMPERATURE=0.3
|
||||
|
||||
# AI Provider (claude 또는 gemini)
|
||||
AI_PROVIDER=gemini
|
||||
|
||||
# Backend API Configuration
|
||||
BACKEND_API_URL=http://localhost:8000
|
||||
BACKEND_API_TIMEOUT=30
|
||||
@@ -34,6 +43,21 @@ KIS_APP_SECRET=your_virtual_app_secret_here
|
||||
KIS_ACCOUNT_NO=your_virtual_account_number_here
|
||||
KIS_BASE_URL=https://openapivts.koreainvestment.com:29443
|
||||
|
||||
# ────────────────────────────────────────────────────────────────────
|
||||
# CORS 설정 (이슈 5)
|
||||
# MCP 서버는 서버-서버 통신 전용 → allow_credentials=False 고정
|
||||
# 아래 환경변수로 허용 Origin을 명시적으로 제한한다.
|
||||
#
|
||||
# Docker Compose (backend 컨테이너에서 호출):
|
||||
# MCP_CORS_ORIGINS=http://backend:8000
|
||||
#
|
||||
# 로컬 개발 (localhost에서 호출):
|
||||
# MCP_CORS_ORIGINS=http://localhost:8000,http://127.0.0.1:8000
|
||||
#
|
||||
# 기본값 (미설정 시): http://localhost:8000,http://127.0.0.1:8000,http://backend:8000
|
||||
# ────────────────────────────────────────────────────────────────────
|
||||
MCP_CORS_ORIGINS=http://localhost:8000,http://127.0.0.1:8000,http://backend:8000
|
||||
|
||||
# Tool Configuration
|
||||
ENABLE_MARKET_DATA_TOOL=true
|
||||
ENABLE_INDICATOR_TOOL=true
|
||||
|
||||
Reference in New Issue
Block a user