candidate 없는 해외 홀딩 종목(NVDA 등)에 대해 이미 호출된
get_overseas_price 응답의 high/low를 활용하여 scanner와 동일한 방식으로
volume_ratio 계산:
intraday_range_pct = (high - low) / price * 100
volume_ratio = max(1.0, volatility_pct / 2.0)
high/low 미제공 시(국내 종목, API 미응답) 기존 기본값 1.0 유지.
implied_rsi는 이미 실API price_change_pct(rate 필드) 기반.
tests/test_main.py: 해외 홀딩 종목 volume_ratio 계산 검증 테스트 추가
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1. WARNING → DEBUG: fallback_stocks 없어도 overseas ranking API로 scanner
정상 동작하므로 오해를 주는 WARNING 레벨을 DEBUG로 낮춤 (2곳)
2. 홀딩 종목 market_data 보강: scanner를 통하지 않은 종목(NVDA 등)에
price_change_pct 기반 implied_rsi와 volume_ratio=1.0 기본값 설정,
scenario_engine 조건 평가 완전화
3. test_main.py: 새로운 동작에 맞게 관련 테스트 2개 업데이트
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
## 변경사항
### #264 — 해외 매수가능금액 조회 API 교체 (frcr_dncl_amt_2 → inquire-psamount)
- TTTS3012R (해외주식 잔고) output2에 frcr_dncl_amt_2 필드가 존재하지 않아
총 가용 현금이 항상 0.00으로 산출되는 문제 수정
- OverseasBroker에 get_overseas_buying_power() 메서드 추가
(TR_ID: 실전 TTTS3007R / 모의 VTTS3007R, ord_psbl_frcr_amt 반환)
- main.py trading_cycle() 및 daily cycle 모두 수정
- 출처: 한국투자증권 오픈API 전체문서 (20260221) — 해외주식 매수가능금액조회 시트
### #265 — get_open_position() HOLD 레코드 필터링 추가
- HOLD 결정도 trades 테이블에 저장되어 BUY 이후 HOLD 기록 시
최신 레코드가 HOLD → get_open_position이 None 반환하는 문제 수정
- 쿼리에 AND action IN ('BUY', 'SELL') 필터 추가
- HOLD 레코드를 제외하고 마지막 BUY/SELL 기록만 확인
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
trading_cycle()의 market_data에 보유 포지션 정보가 없어
Condition requires 'unrealized_pnl_pct' but key missing from market_data 경고 발생.
보유 종목(NVDA 등)의 take-profit/stop-loss 시나리오가 평가 불가하여 HOLD(confidence=0) 고착.
- get_open_position()에 timestamp 컬럼 추가
- market_data 구성 시 open_position 조회 후 아래 키 추가:
- unrealized_pnl_pct: (current_price - entry_price) / entry_price * 100
- holding_days: 매수일로부터 경과 일수
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
KIS 공식 문서(20260221) 기준 KEYB(NEXT KEY BUFF)는 Required=Y이나
누락되어 있어 항상 rt_cd=2 오류 발생, fallback 경로로만 실행됨.
- fluctuation/volume 양쪽 params에 KEYB: '' 추가
- GUBN 주석 수정: 0=하락율, 1=상승율 (문서 기준)
- GUBN 값 0→1 수정: 상승율 기준으로 변동성 급등 종목 스캔
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1. stop-loss/take-profit 가드에 current_price > 0 조건 추가 (#251)
- 현재가 API 실패(0.0 반환) 시 loss_pct=-100% 계산으로 오발동되던 문제 수정
- if entry_price > 0 → if entry_price > 0 and current_price > 0
- LLY '주문구분 입력오류'는 이 오발동의 연쇄 결과(overseas_price=0 → ORD_DVSN='01')
2. 해외 주문 가격 소수점을 $1 이상은 2자리로 제한 (#252)
- round(x, 4) → $1+ 종목은 round(x, 2), 페니스탁은 round(x, 4) 유지
- KIS '1$이상 소수점 2자리까지만 가능' 오류(TQQQ) 수정
테스트:
- test_stop_loss_not_triggered_when_current_price_is_zero 추가
- test_overseas_buy_price_rounded_to_2_decimals_for_dollar_plus_stock 추가
- test_overseas_penny_stock_price_keeps_4_decimals 추가
- 기존 overseas limit price 테스트 expected_price 2자리로 갱신
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
sync_positions_from_broker()에서 price=0.0 하드코딩으로 인해
stop-loss/take-profit이 외부 매수 종목에 작동하지 않던 문제를 수정한다.
- _extract_avg_price_from_balance() 헬퍼 추가 (pchs_avg_pric 추출)
- sync_positions_from_broker()에서 avg_price를 price 필드에 저장
- TestExtractAvgPriceFromBalance 단위 테스트 11개 추가
- TestSyncPositionsFromBroker 통합 테스트 3개 추가 (price 검증)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
리뷰 지적 사항 반영:
- _total_decisions 카운트 제거 (플레이북 생성은 거래 결정이 아님 → 메트릭 왜곡 방지)
- "Gemini raw response received" INFO 로그 추가 (완료 추적 가능)
- test_prompt_override_takes_priority_over_optimization 신규 추가
(enable_optimization=True 상태에서도 prompt_override 우선됨을 검증)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
pre_market_planner처럼 prompt_override를 사용하는 호출자는 플레이북 JSON 등
TradeDecision이 아닌 raw 텍스트를 기대한다. 기존에는 parse_response를 통과시켜
항상 "Missing fields" 경고가 발생했다.
decide()에서 prompt_override 감지 시 parse_response를 건너뛰고 raw 응답을
rationale에 담아 직접 반환하도록 수정한다.
정상 응답인데 경고가 뜨는 문제가 해결된다.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
pre_market_planner는 prompt_override로 Gemini에 플레이북 JSON을 요청한다.
Gemini가 플레이북 JSON을 반환해도 parse_response가 action/confidence/rationale 키가
없다는 이유로 rationale="Missing required fields"를 반환해 실제 응답이 버려졌다.
이로 인해 플레이북 생성이 항상 실패하고 RSI 기반 기본 폴백이 사용됐으며,
RSI가 없는 해외 시장 데이터와 매칭되지 않아 모든 결정이 HOLD(confidence=0)였다.
수정: missing fields 시 rationale=raw로 설정해 실제 Gemini 응답을 보존한다.
pre_market_planner가 decision.rationale에서 플레이북 JSON을 추출하여 정상 파싱 가능.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- prompt_optimizer: build_compressed_prompt의 JSON 키를 act/conf/reason에서
action/confidence/rationale로 수정 (parse_response와 일치시킴)
→ Gemini 응답 100% HOLD로 처리되던 버그 수정
- overseas: fetch_overseas_rankings의 GUBN 파라미터를 1(상승)에서 0(전체)으로 변경
→ 변동성 스캐너가 상승/하락 모두 대상으로 NASDAQ 후보 발견 가능
- test: GUBN==0 검증, build_compressed_prompt 키 이름 검증 추가
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
os.getenv("MODE")는 .env 파일을 읽지 못해 항상 paper를 반환함.
create_dashboard_app에 mode 파라미터 추가 후 main.py에서
settings.MODE를 직접 전달하도록 수정.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- /api/status 응답에 MODE 환경변수 기반 mode 필드 추가
- 대시보드 헤더에 모드 배지 표시 (live=빨간색 깜빡임, paper=노란색)
- 모드 관련 테스트 3개 추가 (total 26 passed)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- _extract_held_codes_from_balance / _extract_held_qty_from_balance:
해외 잔고 수량 필드를 ovrs_cblc_qty(총 보유수량) → ord_psbl_qty(주문가능수량)
우선으로 변경. KIS 공식 문서(VTTS3012R) 확인 결과 ord_psbl_qty가 실제
매도 가능 수량이며, ovrs_cblc_qty는 만료/결제 미완료 포지션을 포함함.
MLECW 등 만료된 Warrant는 ovrs_cblc_qty=289456이지만 ord_psbl_qty=0이라
startup sync 대상에서 제외되고 SELL 수량도 0이 됨.
- trading_cycle: 해외 SELL이 '잔고내역이 없습니다'로 실패할 때 DB 포지션을
ghost-close SELL 로그로 닫아 무한 재시도 방지. exchange code 불일치 등
예외 상황에서 DB가 계속 open 상태로 남는 문제 해소.
- docstring: _extract_held_qty_from_balance 해외 필드 설명 업데이트
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- KISBroker에 get_domestic_pending_orders (TTTC0084R, 실전전용)
및 cancel_domestic_order (실전 TTTC0013U / 모의 VTTC0013U) 추가
- main.py 국내 주문 price=0 → 지정가 전환 (2곳):
· BUY +0.2% / SELL -0.2%, kr_round_down으로 KRX 틱 반올림 적용
- handle_domestic_pending_orders 함수 추가:
· BUY 미체결 → 취소 + buy_cooldown 설정
· SELL 미체결 → 취소 후 -0.4% 재주문 (최대 1회)
- daily/realtime 두 모드 market 루프 내 domestic pending 호출 추가
(sell_resubmit_counts는 해외용과 공유, key prefix "KR:" vs 거래소코드)
- 테스트 14개 추가:
· test_broker.py: TestGetDomesticPendingOrders 3개 + TestCancelDomesticOrder 5개
· test_main.py: TestHandleDomesticPendingOrders 4개 + TestDomesticLimitOrderPrice 2개
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- OverseasBroker에 get_overseas_pending_orders (TTTS3018R, 실전전용)
및 cancel_overseas_order (거래소별 TR_ID, hashkey 필수) 추가
- TelegramClient에 notify_unfilled_order 추가
(BUY취소=MEDIUM, SELL미체결=HIGH 우선순위)
- handle_overseas_pending_orders 함수 추가:
· BUY 미체결 → 취소 + 쿨다운 설정
· SELL 미체결 → 취소 후 -0.4% 재주문 (최대 1회)
· 미국 거래소(NASD/NYSE/AMEX) 중복 조회 방지
- daily/realtime 두 모드 모두 market 루프 시작 전 호출
- 테스트 13개 추가 (test_overseas_broker.py 8개, test_main.py 5개)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
기존 정책(BUY +0.5%, SELL 현재가)의 두 가지 문제를 해결:
- BUY 0.5% 버퍼는 대형주에서 불필요한 과다 지불 유발 ($50K 규모에서 연간 수십 달러 손실)
- SELL 현재가 지정가는 가격이 소폭 하락 시 미체결 위험 (bid < last_price 구간)
변경:
- BUY: current_price * 1.005 → current_price * 1.002 (+0.2%)
대형주 기준 90%+ 체결률 유지하면서 과다 지불 최소화
- SELL: current_price → current_price * 0.998 (-0.2%)
bid가 last_price 아래일 때도 체결 보장
- VTS(paper)와 live 동일 정책 적용 — 더 현실적인 시뮬레이션
- KIS 시장가 주문은 상한가 기준 수량 계산 버그로 사용 안 함(유지)
테스트:
- test_overseas_buy_order_uses_limit_price: 1.005 → 1.002 업데이트
- test_overseas_sell_order_uses_limit_price_below_current: 신규 추가
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- sync_positions_from_broker() 함수 추가
- 시스템 시작 시 브로커 잔고를 조회해 DB에 없는 포지션을 BUY 레코드로 삽입
- 국내: get_balance(), 해외: get_overseas_balance(exchange_code) 순회
- ConnectionError는 경고 로그만 남기고 계속 진행 (non-fatal)
- 동일 exchange_code 중복 조회 방지 (seen_exchange_codes 집합)
- run() 초기화 후 최초 한 번 자동 호출
- 국내주식 BUY 이중 방지 로직 확장
- trading_cycle 및 run_daily_session에서 기존에 해외 전용(not market.is_domestic)
으로만 적용하던 broker balance 체크를 국내/해외 공통으로 변경
- _extract_held_qty_from_balance(is_domestic=market.is_domestic)
- 테스트 (827 passed)
- TestSyncPositionsFromBroker (6개): 국내/해외 동기화, 중복 skip, 공란, ConnectionError, dedup
- TestDomesticBuyDoublePreventionTradingCycle (1개): 국내 보유 주식 BUY 억제
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- run_daily_session에 daily_start_eval 파라미터 추가 (반환 타입: float)
- 세션 첫 잔고 조회 시 total_eval을 baseline으로 캡처
- 이후 세션에서 pnl_pct = (total_eval - daily_start_eval) / daily_start_eval
- 기존 purchase_total(누적) 기반 계산 제거
- run 함수 daily 루프에서 날짜 변경 시 baseline 리셋 (_cb_last_date 추적)
- early return 시 daily_start_eval 반환하도록 버그 수정 (None 반환 방지)
- TestDailyCBBaseline 클래스 4개 테스트 추가
- no_markets: 0.0/기존값 그대로 반환
- first session: total_eval을 baseline으로 캡처
- subsequent session: 기존 baseline 유지 (덮어쓰기 방지)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
AI가 evaluate() 메서드 내부에 또 다른 evaluate() 함수를 중첩 정의하는
실수로 생성된 IndentationError 수정.
각 파일별 수정 내용:
- v20260220_210124_evolved.py: 중첩 def evaluate 제거, 상수/로직 8칸으로 정규화
- v20260220_210159_evolved.py: 중첩 def evaluate 제거, 16칸→8칸 들여쓰기 수정
- v20260220_210244_evolved.py: 12칸→8칸 들여쓰기 수정
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- _retry_connection() 헬퍼 추가: MAX_CONNECTION_RETRIES(3회) 지수 백오프
(2^attempt 초) 재시도, 읽기 전용 API 호출에만 적용 (주문 제외)
- run_daily_session(): get_current_price / get_overseas_price 호출에 적용
- run_daily_session(): get_balance / get_overseas_balance 호출에 적용
- 잔고 조회 전체 실패 시 해당 마켓을 skip하고 다른 마켓은 계속 처리
- 테스트 5개 추가: TestRetryConnection 클래스
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- trades 테이블에 mode TEXT DEFAULT 'paper' 컬럼 추가
- 기존 DB 마이그레이션: ALTER TABLE으로 mode 컬럼 자동 추가
- log_trade() 함수에 mode 파라미터 추가 (기본값 'paper')
- trading_cycle(), run_daily_session()에서 settings.MODE 전달
- 테스트 5개 추가 (mode 저장, 기본값, 스키마 검증, 마이그레이션)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- #210: init_db()에 WAL 저널 모드 적용 (파일 DB에만, :memory: 제외)
- 대시보드(READ)와 거래루프(WRITE) 동시 접근 시 SQLite 락 오류 방지
- busy_timeout=5000ms 설정
- #213: RATE_LIMIT_RPS 기본값 2.0으로 통일 (.env.example이 5.0으로 잘못 표기됨)
- #216: .env.example 중요 변수 추가 및 정리
- KIS_BASE_URL 모의/실전 URL 주석 명시 (포트 29443 수정 포함)
- MODE, TRADE_MODE, ENABLED_MARKETS, PAPER_OVERSEAS_CASH 추가
- GEMINI_MODEL 업데이트 (gemini-pro → gemini-2.0-flash-exp)
- DASHBOARD 설정 섹션 추가
테스트 2개 추가 (WAL 파일 DB 적용, 메모리 DB 미적용 검증)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
KIS VTS는 SELL 지정가 주문을 접수 즉시 rt_cd=0으로 반환하지만
실제 체결은 시장가 도달 시까지 지연된다. 이 기간 동안 DB는 포지션을
"종료"로 기록해 다음 사이클에서 이중 매수가 발생할 수 있었다.
- trading_cycle(): BUY 게이팅에 브로커 잔고 추가 확인 로직 삽입
- run_daily_session(): 동일 패턴의 BUY 중복 방지 로직 추가
- 두 함수 모두 이미 fetch된 balance_data 재사용 (추가 API 호출 없음)
- TestOverseasBrokerIntegration 클래스에 테스트 2개 추가
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Playbook(/api/playbook/{date}): 프리마켓 플레이북 아코디언 패널 추가
- Scorecard(/api/scorecard/{date}): 일간 스코어카드 KPI 카드 그리드 추가
- Scenarios(/api/scenarios/active): 활성 시나리오 매칭 테이블 추가
- Context(/api/context/{layer}): L1-L7 컨텍스트 트리 테이블 추가
모든 패널 decisions-panel 아래에 섹션 추가 방식으로 배치.
refreshAll()에 4개 함수 포함하여 30초 자동 갱신 지원.
보안:
- esc() 헬퍼로 innerHTML 삽입 값 XSS 방지
- ctx limit 값 parseInt + 범위 클램핑(1-200) 적용
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
대시보드 표시 전용 데이터를 AI 의사결정용 context tree에 저장하는 것은
관심사 분리 위반. system_metrics 경량 테이블을 신설하여 완전히 분리. (PR #197 코드리뷰 반영)
- db.py: system_metrics 테이블 추가 (key/value/updated_at)
- main.py: context_store.set_context(L6_DAILY) → db_conn.execute(system_metrics)
- app.py: contexts 쿼리 → system_metrics 쿼리
- tests: _seed_cb_context를 system_metrics 삽입으로 변경
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>