fix: 하드닝 루프 8회차 — 체결 보정·고아 감지·청산 경보·실시간/차트·리스크 가드 일괄 수정

- 스윙 체결가 확인 전 executed_price NULL + fill_sweep, 진입가 resolve(ActiveTrade↔Portfolio), 0체결/부분체결 매수 장부 원복
- DT 고아 감지(근거 한정)·0체결 매도 복원·stale 점검 확대, 시간손절 -0.3% 원복
- 폴백 손절 ATR 캐시, 거래량부족-차단 신호 매수 차단, 현금 5% 유보 재적용, MDD peak/reset 수정
- KIS 서킷브레이커 청산 우회·경보, db_worker 재큐잉 순서, heartbeat·health-watch 사이드카
- 실시간 WS 재구독·41건 우선순위·다건 프레임 분할, 차트 샘플 제거·당일봉 합성, WS 서브프로토콜 인증
- 프론트: 미관리 그룹·확인 모달·차트 실시간 반영·예수금 표시 정정
- 전문가 에이전트 6종 추가(.claude/agents)
- 유닛 스위트 2667 passed (소켓 차단 하), tsc OK

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y83Za98XsNw9zuA4FTUytN
This commit is contained in:
2026-10-10 12:17:49 +09:00
parent 5c4803b339
commit a264561576
230 changed files with 41160 additions and 2334 deletions

View File

@@ -172,10 +172,16 @@ async def get_trade_stats(
executed_trades = len([t for t in trades if t.status in ("EXECUTED", "COMPLETED")])
failed_trades = len([t for t in trades if t.status == "FAILED"])
# 총 거래 금액 계산
# 총 거래 금액 계산 — [2026-10-08] total_amount(체결 확인 시 체결가×체결수량으로 갱신) 우선.
# executed_price 는 체결 확인 전 NULL 이므로 곱셈만 쓰면 미확인 건이 0 으로 빠진다.
def _amount(t) -> float:
if t.total_amount is not None:
return float(t.total_amount)
price = t.executed_price if t.executed_price is not None else t.order_price
return float(price or 0) * (t.executed_quantity or 0)
total_amount = sum(
float(t.executed_price or 0) * (t.executed_quantity or 0)
for t in trades if t.status in ("EXECUTED", "COMPLETED")
_amount(t) for t in trades if t.status in ("EXECUTED", "COMPLETED")
)
return {
@@ -192,7 +198,11 @@ async def get_trade_stats(
@router.delete("/pending")
async def delete_pending_trades(db: AsyncSession = Depends(get_db)):
"""PENDING과 FAILED 상태의 매수 시도 내역 정리 — **전일 이전 행만**.
"""FAILED 상태의 주문 시도 내역 정리 — **전일 이전 행만, PENDING 은 보존**.
[R7] PENDING(지정가 미체결·결과 불명 BUY)은 원장 BUY 합계와 강등 방어의 근거라 지우지 않는다.
쓰기는 FAILED 전용 정리(기존 동작 유지). db_worker 에 delete task 가 없어 직접 삭제를 유지한다
(관리자 전용 등록은 security.ADMIN_ONLY_ROUTES 소관 — 별도 보고).
[L 2026-10-02] 기존에는 기간 무제한이라 장중 호출 시 당일 PENDING/FAILED 행까지
지웠다. 당일 행은 `already_ordered_today`·일일 스윙 매수 한도(`_count_swing_buys_today_v2`
@@ -204,7 +214,9 @@ async def delete_pending_trades(db: AsyncSession = Depends(get_db)):
cutoff = get_daily_cutoff_utc()
result = await db.execute(
select(TradeHistory).where(
TradeHistory.status.in_(['PENDING', 'FAILED']),
# [R7] PENDING 은 보존한다 — 체결 확인 실패 BUY 가 PENDING 으로 남아
# portfolio 원장 BUY 합계·유입잔고 강등(swing→external) 방어의 근거가 된다(main.py 기동 정리와 동일 정책).
TradeHistory.status == 'FAILED',
TradeHistory.created_at < cutoff,
)
)
@@ -215,9 +227,9 @@ async def delete_pending_trades(db: AsyncSession = Depends(get_db)):
await db.delete(trade)
await db.commit()
logger.info(f"✅ 정리 완료: {count}개의 PENDING/FAILED 매수 시도 삭제 (기준 {cutoff} 이전, 당일 행 보존)")
logger.info(f"✅ 정리 완료: {count}개의 FAILED 시도 삭제(PENDING 보존) (기준 {cutoff} 이전, 당일 행 보존)")
return {
"deleted_count": count,
"message": f"Cleaned up {count} pending/failed trades before {cutoff.isoformat()} (today's rows kept)",
"message": f"Cleaned up {count} failed trades (PENDING kept) before {cutoff.isoformat()} (today's rows kept)",
}