Commit Graph

6 Commits

Author SHA1 Message Date
9de0715165 fix: 스윙 자동편입 차단 + DT 처리량 복구 + ATR/양봉 세션 경계 + DLQ 안전
사용자 요구 "스윙으로 편입하는 건 없어야 해" — 시스템이 매수하지 않은 KIS
계좌 잔고가 strategy_type='swing' 이 되는 경로를 전부 닫았다. 2026-09-30
09:26 에 유한양행 8주가 -6.49%/-40,000원 손절된 것이 직접 계기다.

## 스윙 자동편입 차단
- 강등 허용(swing→external). 기존엔 승격만 가능해 유입 잔고가 영구히
  자동 손절 대상이었다. 3중 방어로 정상 포지션 강등을 막는다:
  system_buy_registry(주문 즉시 인메모리) / 연속2회+120초 / db_worker 큐 적체 보류
  ※ 레이스는 이론이 아니다 — _verify_and_sync_execution 이 매수 직후
    _delayed_sync_portfolio(0) 을 지연 0초로 부른다
- 수량 인식 분류(_classify_holding_strategy_type_v2):
  _sys<=0<qty → external / 0<_sys<qty AND _sys==_dt_held → external
  == 을 쓴 이유: 과소·과대 어느 쪽으로 틀려도 swing(관리 유지)으로 실패한다
- _system_qty 를 과대 집계 쪽으로 편향 — 오판이 안전한 방향으로 나게
- external 행에 스윙 추가매수 차단(혼재 발생 자체를 봉쇄, 청산은 통과)
- 근거를 두 벌로 분리: 신규태깅·강등방어는 registry 포함("있다고 보는 게 안전"),
  승격·ActiveTrade 교정은 durable("없다고 보는 게 안전"). 안전 방향이 반대다
- 정합 루프가 external/혼재 행을 교정하지 않게(전제 "active 1행 = 전량 대표"를
  직접 검사). 미교정 시 ActiveTrade 가 부풀어 스윙 청산이 유입분까지 판다(§3.5)
- status 허용목록 → 거부목록(FAILED 만 제외). 진짜 구멍은 FILLED 가 아니라
  PENDING 이었다(체결확인 실패 시 영구 PENDING, 주식은 보유 중)
- SELL 중복제거 키에 nullif+trim — order_id 는 NULL 이 아니라 '' 로 저장된다
  (build_kis_order_id 가 미발견 시 "" 반환). coalesce 만으로는 그룹이 붕괴해
  SELL 이 과소 집계되고 유입 행이 영구히 swing 이 된다

## DT 처리량
- 감시종목 20 → 30 (DAY_TRADE_WATCHLIST_SIZE, 예산 상한 32에서 마진)
- 선조회 동시성 4→8, per-stock 45초, 단계 데드라인 120초 신설
  (120 < 150 _SCAN_TIME_BUDGET < 180 _guard). 오후는 종목당 32초가
  타임아웃 20초를 넘겨 4~7종목만 스캔되던 포화 상태였다 — 약 9배 복구
- 리스크 노출 불변: 동시 5종목·종목당 6%·DT 30% 무변경. 기회만 증가

## 세션 경계 (ATR·양봉)
- _atr() 이 내부에서 세션을 끊는다. 워밍업 시계열의 당일 첫 봉 TR =
  오버나이트 갭이라 갭 3%에서 ATR 1.50배, 10%에서 2.75배 부풀었고
  클램프 없는 target=현재가+1.5×ATR 을 직접 왜곡했다
  → 진입 ATR == 청산 트레일링 ATR 보장
  ※ 부작용 승인됨: 09:30~10:10 은 ATR 부재로 폴백 -3.5% 가 실효 손절폭
- 양봉 판정이 형성 중 버킷을 보던 것 → 마지막 완성봉(거래량 게이트와 같은 봉)
- 1.5R 하한 보장(두 분기). risk=max(1.5×ATR,2%) 라 저변동 종목은
  R:R 0.21 까지 떨어졌다 — §7 "1.5R 분할" 규약 복귀, RSI_BB 와 동일

## DLQ 안전
- replay_dlq 나이 제한: 상태 2시간+같은날짜 / 기록 24시간 / close 는
  충돌판정만(7일). "닫아라"는 지시는 시간이 지나도 거짓이 되지 않는다 —
  격리하면 반대로 유령 active 행이 남는다(§8.4.1 129개와 같은 형태)
- 상태 충돌 확인(유령 생성 위험 시 expired/ 격리, 삭제 안 함)
- DB 장애 시 단락 — 없으면 500파일 × 접속타임아웃만큼 기동이 정지한다

## 진단·테스트
- 진입 reason 에 [진단 ATR=…, 갭=…], 탈락 후보 RSI·갭 집계 로그(하루 ~60줄)
- 잔고조회 실패 debug→warning (available_cash=0 → 모든 진입 조용히 차단)
- 벽시계 의존 테스트 재발 방지: autouse 가드(시각 미고정 시 AssertionError)
  + 호출부 전수 테스트
- 소스 문자열 매칭 단언을 동작 검증으로 전환(값·분기 판정 한정)

테스트 848 → 865 passed / 0 failed. 전략 임계값 변경 0건. alembic 불필요.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-30 16:50:54 +09:00
7abbb3411c fix: 재기동 내구성 + DT 진입 복구 + 유입 잔고 격리 + 대시보드 표시 오류
## 재기동 내구성
- scheduler: 복원 직후 reset_if_new_day()가 복원분을 전부 폐기하던 버그
  (begin_restore()로 날짜 경계를 선확정. 복원 성공 로그는 뜨는데 실제로는 지워졌다)
- 일일 안전장치(daily_pnl/loss_streak_today/closed_today) Redis 영속화
  날짜를 키와 페이로드 양쪽에 실어 이중 게이트, 복원은 보수적 병합
  (손익 min/연패 max/청산목록 합집합 — 복원이 안전장치를 약화시킬 수 없다)
- db_worker 종료 drain이 insert_trade_history만 커밋하고 포지션 추적 행은
  DLQ로 버리던 비대칭 제거 + DLQ 재처리 경로 신설(쓰기 전용이었다)
- 장중 기동 시 ORB 소급 수집(backfill), 전일 캔들 혼입 차단
- swing/exit_service "보유 종목 없음" debug→info, 10회 연속 warning

## DT 진입 복구
- 워밍업 회귀 복구: 1b9257c(2026-09-28)가 조회 계층에 당일 필터를 넣어
  5분봉 RSI(14)/BB(20) 워밍업이 죽고 VWAP/RSI_BB가 10:15/10:40 이전
  발화 불가가 됐다. 필터를 삭제하지 않고 용도별로 분리
  (get_minute_chart_full_day_v2 신설, 기존 메서드·3개 호출부 동작 무변경)
- 거래량 게이트 비교 대상 교정: 형성 중 봉 → 완성봉 (5분봉·ORB 1분봉 양쪽)
  임계값(1.3/1.5)은 불변, 고친 것은 비교 대상뿐
- 세션 경계 차단: _last_session_candles()로 거래량 비교가 전일 봉을 잡아
  개장 직후 게이트가 자동 통과하는 것을 방지
- 스캔 선조회(동시성 4)·종목 타임아웃·시간예산·라운드로빈 + 잘린 종목 수 로그
- watchlist 30분 갱신 시 ORB 보유 종목 우선 유지

## 유입 잔고 격리
- sync_portfolio()가 시스템 매수 근거 없는 KIS 잔고를 자기 스윙 포지션으로
  편입해 자동 손절로 처분하던 문제 → strategy_type='external' 격리
  (승격만 허용, 강등 없음. 기존 행은 수동 SQL 필요)
- 격리분 손절선 이탈은 집행 없이 warning + summary 노출
- 수동 청산 경로에 record_trade_result 누락 보강, 전략 간 당일 쿨다운

## 4% 상한
- market_data 표시 경로 클램프 누락 + confidence 배율 후 재클램프
- 회귀 방지 테스트(AST로 가드가 try 밖에 있는지 검증)

## 프론트
- 총자산 보정 가드(3%·종목수·가격결손·국내분 한정), 원인 미규명 명시
- cumulative_realized_pl이 /portfolio/combined에 없어 항상 0이던 문제

테스트 579 → 677 passed / 0 failed (소켓 차단 하 통과, 실호출 없음)
전략 임계값 변경 0건

⚠️ 워밍업 복구의 효과 크기는 미확정 — KIS 분봉 API에 날짜 파라미터가 없어
   미래 ref_time에 전일 데이터를 주는지가 미검증 전제다. 배포 후
   "[분봉] 워밍업 5분봉 N봉 (당일 a + 전일 b)" 로그의 b로 확정할 것.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-29 15:49:25 +09:00
85cb5cbc09 fix: 감사 잔여 항목 — 잔량 출처 DB화·ORB 영속화·미국 부분청산·듀얼전략 태깅·NULL 가격
- portfolio_cache_bridge: 잔량 산출을 Portfolio(DB) 기준으로. 캐시는 sync 실패 시 최대
  5분 낡아 실제와 다른 잔량이 기록될 수 있었음(감사 #14). 조회 실패 시 캐시 폴백.
- ORB 캐시 Redis 영속화(save/load_orb_cache) — 재배포 시 인메모리가 날아가 당일 ORB
  전략이 통째로 불가능했음. 수집 후 저장, 스케줄러 기동·수집 시 복원.
- us_market_scheduler: 부분청산이 Portfolio.quantity 를 갱신하지 않던 문제 → reflect_sell 연동.
- 듀얼 전략(MOMENTUM_BREAKOUT/PULLBACK_BUY)이 ActiveTrade.strategy_name 에 저장되지 않아
  CLAUDE.md §8.4 모멘텀 전용 청산(+10%/+20% 분할)이 한 번도 실행된 적 없었음 → 태깅 전달.
  매도 시에는 실제 행의 strategy_name 으로 close (swing 으로 닫으면 유령 행 잔존).
- 수동매도: current_price NULL 시 TypeError 로 주문 후 상태 갱신이 통째로 누락되던 엣지 수정.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-23 11:11:20 +09:00
70def8fe0b fix(P0): 인증 누락·레거시 DT 엔진 병존·혼재 종목 재태깅·자정 후 스테일 청산 무력화
감사 CRITICAL 4건:
- /daytrading/{watchlist,execute,close-all,cycle} 에 require_api_key 누락 → 인증 없이
  전량 청산 가능했음 (CLAUDE.md §4 위반)
- 레거시 day_trading._positions 가 엔진 day_trading_state 와 별도로 기동 시 복원돼
  같은 포지션이 두 엔진에 존재 → /close-all 시 이중 매도. 복원 제거 + close-all/cycle 을
  엔진에 위임, /execute 실매수는 410(엔진 미관리 포지션 방지)
- reflect_buy 가 기존 행을 daytrading 으로 재태깅 → 같은 종목 스윙 보유분이
  get_swing_holdings 에서 빠져 손절·익절 정지. 신규 행일 때만 태깅
- close_stale_positions 가 인메모리만 순회 → 자정 reset 후 대상이 사라져 무력.
  ActiveTrade(day_trade/active/전일) 조회 기반으로 변경하고 인메모리가 비어도 실행

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-23 10:49:30 +09:00
f32bf412f3 fix: 로그 분석으로 찾은 버그 4건 (과다매도·수량 0 오염·LSTM 무력화·sync 충돌)
1) DT 분할매도가 ActiveTrade.quantity를 갱신하지 않아 재기동 복원 시 원래 수량이 되살아남
   → 잔량 4주인데 7주 매도되어 같은 종목 스윙 보유분 3주까지 청산됨(2026-09-23 LG씨엔에스).
2) reflect_sell이 '전략 포지션 잔량'을 Portfolio 행 잔량으로 사용 → DT 전량청산이 스윙
   보유분까지 0으로 만듦. 행 잔량은 항상 (행 수량 - 매도 수량)으로 계산.
3) LSTM 체크포인트에 numpy 배열이 들어가 weights_only=True 로드가 매번 실패 → 앙상블에서
   LSTM이 조용히 빠진 채 예측 중이었음. 저장을 list로 바꾸고, 기존 파일은 MODEL_DIR 내부일
   때만 완화 재시도.
4) 수동 매도(행 삭제)와 sync 동시 실행 시 StaleDataError로 sync 전체 실패 → 전량 매도는
   행 삭제 대신 수량 0, sync는 충돌 시 실패 기록 없이 다음 주기 재시도.
   추가로 비강제 sync는 락 점유 중이면 30초 대기 없이 캐시 반환.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-23 10:24:45 +09:00
a57c720559 feat: 데이트레이딩 포지션 화면 표시 + 모든 매도 즉시 화면 반영
- portfolio_cache_bridge 신설: 매매 엔진 체결 시 Portfolio(db_worker 큐) + 인메모리 캐시 즉시 갱신.
  DT 매수는 strategy_type='daytrading'으로 태깅되어 보유 종목 패널의 '데이트레이딩' 그룹에 표시되고
  스윙 청산 로직(strategy_type='swing' 대상)에서 제외됨. (기존엔 KIS 동기화로 'swing'으로 유입돼
  DT 포지션이 안 보이고 스윙 엔진이 같이 관리하는 문제)
- DT 매도·스윙 자동 매도도 캐시에서 즉시 제거/차감 + 최근매도 기록(동기화 되살림 방지).
- 동기화 시 DT 엔진 보유 종목은 'daytrading'으로 태깅.
- 프론트: 수동 매도 성공 시 낙관적 갱신으로 서버 재조회 전 즉시 목록에서 제거.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-22 16:49:46 +09:00