feat: 안전형 스윙 개선(서킷브레이커·own_trend) + 매수 현금해결 방어
퀀트 리서치(공정 유니버스+walk-forward)로 검증된 안전 게이트 2종과 매수 경로 현금 해결 방어 코드 추가. 모두 기존 시그니처 유지(§4). - 지수 서킷브레이커: KODEX200 종가 < MA20이면 신규 스윙 진입 전면 중단 (하락장 상관붕괴=2024H2형 방어). execute_swing_entries가 기존 check_market_regime의 drop_from_ma20을 재사용(추가 API 없음). 백테스트 루프에도 동일 적용. 검증: 최근1년 Sharpe 0.82→1.13, CAGR 7.2→8.6%, MDD 개선. - own_trend 게이트: 평균회귀·눌림목 매수 시 종목 MA20 >= MA60×0.97일 때만 진입(하락추세 물타기 방지). 실서비스는 recommendations.is_mean_reversion_candidate 에서 StockAnalysis.ma20/ma60로, check_buy_signal엔 ma20/ma60 선택 파라미터 (기본 None=하위호환) 추가. 백테스트(kr_backtest_engine, run_backtest)도 평균회귀 한정. - 매수 현금 해결 방어(trade_executor._execute_buy): 0-현금 캐시를 신뢰하지 않고 현금<=0이면 자산과 무관하게 KIS 예수금 재조회, 실현금(>0)일 때만 캐시. 0-현금 고착으로 수량 0이 되는 잠재 무한루프 방어. 검증: code-verifier 통과(CONFIRMED 버그 없음, None-safe, §3/§4/§5 준수), pytest 442 통과, 6파일 컨테이너 컴파일·리로드 정상. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HBmzf1HDnvFHLraJ8eaaKJ
This commit is contained in:
@@ -501,7 +501,9 @@ async def _execute_buy(
|
||||
|
||||
# ── 3. 현금·자산 조회 (30초 캐시 → portfolio_cache → KIS API 직접) ─────
|
||||
cached = _get_cached_account()
|
||||
if cached:
|
||||
# [FIX] 현금이 0인 캐시는 신뢰하지 않는다. 기존엔 available_cash=0이 캐시되면
|
||||
# 그대로 재사용해 수량이 0으로 계산되어 매수 주문이 영영 안 나갔다(무한루프).
|
||||
if cached and cached[0] > 0:
|
||||
available_cash, total_assets = cached
|
||||
else:
|
||||
available_cash, total_assets = 0.0, 0.0
|
||||
@@ -514,8 +516,11 @@ async def _execute_buy(
|
||||
total_assets = float(summary_copy.get("total_assets", 0))
|
||||
except Exception:
|
||||
pass
|
||||
# 2차: 캐시 미스 → KIS API 직접 조회 (잔고 0이면 미초기화로 판단)
|
||||
if available_cash <= 0 and total_assets <= 0:
|
||||
# 2차: 현금이 0이면 KIS API로 직접 조회.
|
||||
# [FIX] 기존 조건은 (cash<=0 AND assets<=0) 이라, 평가자산은 있는데 캐시 현금만
|
||||
# 0인 경우(portfolio_cache 요약이 available_cash=0을 준 케이스) KIS 재조회를
|
||||
# 건너뛰어 현금 0이 고착됐다. 현금이 0이면 자산과 무관하게 실제 예수금을 읽는다.
|
||||
if available_cash <= 0:
|
||||
try:
|
||||
balance = await korea_investment_api.get_balance()
|
||||
output2 = balance.get("output2", [{}])
|
||||
@@ -526,7 +531,8 @@ async def _execute_buy(
|
||||
logger.info(f"[CASH_INIT] KIS API 직접 조회: cash={available_cash:,.0f} assets={total_assets:,.0f}")
|
||||
except Exception as e:
|
||||
logger.warning(f"[CASH_INIT] KIS 잔고 조회 실패: {e}")
|
||||
if available_cash > 0 or total_assets > 0:
|
||||
# [FIX] 실제 현금(>0)일 때만 캐시한다. 0 현금을 캐시하면 위 무한루프가 재발한다.
|
||||
if available_cash > 0:
|
||||
_set_account_cache(available_cash, total_assets)
|
||||
|
||||
# [이슈1 수정] TradeHistory.created_at은 UTC 저장(datetime.utcnow)이므로
|
||||
|
||||
Reference in New Issue
Block a user