fix: 리스크 결함 일괄 수정 — 누적 4% 상한·스윙 소유권·일일한도 분리·DT 순손익/체결가·시간손절 밴 제외·체결조회 경로
trade_executor - 종목당 4% 상한을 기존 보유분 포함 누적으로 (sizing_core.remaining_position_room) - 추천 경로는 스윙 소유 행만 매수·매도 (external/manual/daytrading/us_swing skip, 대칭), 고아 DT 행 warning - 일일 매수한도는 스윙 소유 매수만 집계, 집계 실패 시 fail-closed - 부분매도 반복 차단에서만 DT 매도 제외 (재매수 쿨다운은 유지) - 체결 확인: 부분체결 잔량 자동 재주문 제거(가드 전부 우회·취소/거부 시 발동), PARTIAL→COMPLETED+체결수량, 초과체결 캡 korea_investment_api - get_order_execution 이 존재하지 않는 /trading/inquire-ccnl 을 호출하던 결함 → inquire-daily-ccld (TTTC0081R 우선, 구 TR 폴백, 페이지네이션, ODNO 매칭, EXCG_ID_DVSN_CD=KRX), 실패 로그 warning daytrading - 시간손절은 loss_tracker 중립 (42일 밴이 스윙까지 막던 문제) - 실현손익을 수수료·거래세 차감 순손익으로 (trade_cost_core), 체결가 백그라운드 보정 (fill_reconciler) - legacy _record_trade strategy_type=daytrading 명시 db_worker - update_trade_history_by_order_id 를 당일(KST)+최신 1행으로 한정 (일별 재사용 ODNO 로 과거 행 덮어쓰기 방지) alembic 0013: trade_journals strategy_name='unknown' 을 trade_history.strategy_type 으로 보정 (백업 테이블 포함) CLAUDE.md: 변경 동작 반영, MT 예약 리비전 0013→0014 998 passed Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1035,6 +1035,11 @@ async def _try_enter(
|
||||
"take_profit": signal.target_price,
|
||||
})
|
||||
|
||||
# [2026-10-01] 실제 체결가 확인 — 백그라운드(루프 비차단). 위 기록의 executed_price 는
|
||||
# 신호 시점 1분봉 종가다. 확인되면 원장·인메모리 진입가·ActiveTrade 를 체결가로 보정하고,
|
||||
# 실패하면 신호가 기록이 그대로 남는다(warning).
|
||||
_schedule_buy_fill_reconcile(stock_code, order_id, current_price, quantity)
|
||||
|
||||
logger.info(
|
||||
f"[DayEntry] ✅ 진입: {stock_code} {stock_name} "
|
||||
f"수량={quantity} 가격={current_price:,.0f} 신호={signal.strategy}"
|
||||
@@ -1052,3 +1057,59 @@ async def _try_enter(
|
||||
except Exception as e:
|
||||
logger.error(f"[DayEntry] _try_enter 오류 ({stock_code}): {e}", exc_info=True)
|
||||
return None
|
||||
|
||||
|
||||
def _schedule_buy_fill_reconcile(
|
||||
stock_code: str, order_id: str, signal_price: float, quantity: int,
|
||||
):
|
||||
"""[2026-10-01] 매수 실제 체결가 확인을 백그라운드로 예약 (실시간 루프 비차단).
|
||||
|
||||
확인되면:
|
||||
- TradeHistory(order_id) executed_price·total_amount·slippage(%) 갱신 (db_worker 큐)
|
||||
- 인메모리 포지션 entry_price 를 체결가로 교체(같은 주문번호의 포지션이 남아 있을 때만).
|
||||
손절가·목표가는 신호 시점 절대가격 그대로 둔다(전략 파라미터 불변).
|
||||
- ActiveTrade(day_trade) entry_price 갱신 — 재기동 복원도 체결가 기준이 된다.
|
||||
이미 일부/전량 청산된 뒤에 확인되면 그 청산 손익은 신호가 기준으로 남는다(보고된 잔여 위험).
|
||||
"""
|
||||
from app.services.daytrading import fill_reconciler
|
||||
|
||||
from app.core.timeutil import now_kst as _now_kst
|
||||
_order_date = _now_kst().date().isoformat() # 주문일(KST) — 확인이 늦어져도 이 날짜로 조회
|
||||
|
||||
async def _on_fill(filled_qty: int, avg_price: float) -> None:
|
||||
if filled_qty < quantity:
|
||||
logger.warning(
|
||||
f"[DayEntry] 매수 부분체결 확인: {stock_code} {filled_qty}/{quantity}주 "
|
||||
f"order_id={order_id} — 인메모리 수량은 주문 수량 기준(수동 확인 필요)"
|
||||
)
|
||||
slippage_pct = ((avg_price - signal_price) / signal_price * 100) if signal_price > 0 else 0.0
|
||||
from decimal import Decimal
|
||||
from app.services.db_worker import enqueue_update_trade_history_by_order_id
|
||||
await enqueue_update_trade_history_by_order_id(order_id, {
|
||||
# 식별 키(db_worker 가 setattr 하지 않음): KIS ODNO 는 일별 순번이라 날짜·구분으로 좁힌다
|
||||
"order_date": _order_date,
|
||||
"order_type": "BUY",
|
||||
"strategy_type": "daytrading",
|
||||
"executed_price": Decimal(str(round(avg_price, 2))),
|
||||
"total_amount": Decimal(str(round(avg_price * quantity, 2))),
|
||||
"slippage": Decimal(str(round(slippage_pct, 4))),
|
||||
})
|
||||
updated = False
|
||||
async with day_trading_state.trade_lock:
|
||||
pos = day_trading_state.positions.get(stock_code)
|
||||
if pos is not None and str(pos.get("order_id", "")) == str(order_id):
|
||||
pos["entry_price"] = float(avg_price)
|
||||
updated = True
|
||||
if updated:
|
||||
await enqueue_active_trade_upsert({
|
||||
"stock_code": stock_code,
|
||||
"strategy_name": "day_trade",
|
||||
"entry_price": float(avg_price),
|
||||
})
|
||||
logger.info(
|
||||
f"[DayEntry] 매수 체결가 확인: {stock_code} 신호가={signal_price:,.0f} "
|
||||
f"체결가={avg_price:,.2f} (슬리피지 {slippage_pct:+.3f}%)"
|
||||
+ ("" if updated else " — 포지션 이미 청산/교체됨, 원장만 보정")
|
||||
)
|
||||
|
||||
return fill_reconciler.schedule(stock_code, order_id, "BUY", _on_fill)
|
||||
|
||||
Reference in New Issue
Block a user