Files
QuantEngineByItz/spec/exit/qualitative_sell_strategy_v1.yaml
kjh2064 13e9ccad55 WBS-7.6/7.9/7.7: 슬리피지 보정 + Naver 모니터링 + E2E 통합테스트
WBS-7.6 (슬리피지 5bps 보정):
- 이론치 5bps를 calibration_registry.yaml에 EXECUTION_SLIPPAGE_BPS 등록
- spec/55_execution_simulator_contract.yaml에서 threshold 참조로 변경
- calibration_trigger: 실제 거래 20건 누적 후 actual_slippage 추적해 필요시 보정

WBS-7.9 (Naver 스크래핑 Cloudflare 403 모니터링):
- tools/fetch_naver_market_data_v1.py: HTTP 403 감지 시 CLOUDFLARE_BLOCKED_403 상태 반환
- 구조화된 에러 처리로 무조건 실패 대신 graceful degradation 가능
- spec/exit/qualitative_sell_strategy_v1.yaml: WBS-7.9 처리 문서화
- 실제 차단 발생 시 대체 경로 없음(KRX=OTP 필수, investing.com=이미 차단)
  → 운영: 차단 발생 시 수동 실행 또는 slack 경고

WBS-7.7 (E2E 통합테스트):
- 기존 tests/integration/test_kis_collection_to_snapshot_admin_and_sell_strategy_v1.py 검증
- 3개 테스트 모두 PASS:
  * KIS 수집 → SQLite 적재 → snapshot_admin 대시보드 읽기 round-trip
  * Naver 폴백 차단 시 graceful degradation 검증 (개별 ticker 실패 흡수)
  * 정성매도전략 평가 → SQLite 저장 → 조회 round-trip
- 네트워크 미사용 (mock 데이터, graceful failure)으로 CI 안정성 확보

전체 테스트: 135/135 PASS (unit 61 + integration 3 + formula/formula_registry/... 71)

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-22 23:02:33 +09:00

164 lines
10 KiB
YAML

meta:
title: "은퇴자산포트폴리오 — 비기계적 매도전략(가치보존) 명세"
parent_file: "RetirementAssetPortfolio.yaml"
version: "2026-06-21-PHASE8_qualitative_sell"
language: "ko-KR"
timezone: "Asia/Seoul"
role: "canonical"
has_code_implementation: true
code_path: "src/quant_engine/qualitative_sell_strategy_v1.py"
purpose: >
익절/손절을 고정 % 임계값으로 기계적으로 트리거하지 않고, 매크로·실적·펀더멘털·
공매도수급·호가 미시구조·대내외 변수(대형 IPO·섹터 로테이션) 5개 독립 팩터군의
합의(confluence)로 매도/보유/추가 확신도를 산출해 주식가치를 최대치로 보존한다.
현금부족 사유는 입력에서 의도적으로 배제한다.
qualitative_sell_strategy:
policy:
execution: "보유 포지션 검토 시 항상 실행. STOP_PRICE_CORE_V1/PROFIT_RATCHET_TIERED_V2 등
기존 기계적 손절/래칫 라인과 병행 — 이 명세가 그것들을 대체하지 않으며, '서두르지 않는
재량적 정리' 판단을 보강한다."
confluence_rule: "5개 팩터군 중 최소 3개가 동일 방향(+/-)으로 합의해야 행동 생성. 단일
팩터의 임계값 돌파만으로 매도 트리거 금지."
cash_shortfall_exclusion: "현금부족·리밸런싱 강제매도 사유는 이 명세의 입력에서 제외한다.
해당 사유의 매도는 spec/exit/value_preserving_cash_raise_optimizer_v7.yaml 책임."
date_basis: "review_window는 실제 실적발표일·고영향 매크로 이벤트일(spec/strategy/
macro_event_synchronizer_v2.yaml:event_hold_gate)에서 역산한다. 임의 고정일 금지."
factor_families:
macro_pressure:
id: "F1"
formula_ref: "spec/strategy/macro_event_synchronizer_v2.yaml:position_size_scale_formula"
sources: ["macro_risk_score", "FX", "금리", "산업통상부 수출입동향(섹터별)"]
note: "수출입 동향으로 섹터별 실적 선행지표를 추정해 가중."
fundamental_trajectory:
id: "F2"
formula_ref: "spec/strategy/fundamental_quality_v3.yaml"
sources: ["EPS 추정치 변화", "영업이익률 추세", "실적발표 컨센서스 서프라이즈"]
short_interest_pressure:
id: "F3"
formula_ref: "spec/13b_harness_formulas.yaml:formula_registry.formulas.SHORT_INTEREST_RISK_GAUGE_V1"
sources: ["공매도잔고율 추세", "공매도거래비중", "상대수익률", "거래량 이상", "실적전망"]
note: >
잔고율은 '매수/매도 버튼'이 아니라 위험계기판. 잔고율이 낮은 종목(예: 현대로템형,
<1%)은 잔고율 자체보다 거래비중·상대수익률을 더 중요하게 본다.
microstructure_pressure:
id: "F4"
sources: ["호가 10단계 매수/매도 잔량 불균형", "체결강도", "스프레드"]
note: "전략적 방향 결정에는 쓰지 않고 confluence가 SELL/ADD로 합의된 이후의
'집행 타이밍'에만 사용 — execution_window 산정 보조."
liquidity_rotation_risk:
id: "F5"
sources: ["대형 IPO 청약/상장에 따른 섹터 자금 이탈", "동일 섹터 로테이션",
"외국인/기관 섹터 비중 변화"]
output:
formula_ref: "spec/13b_harness_formulas.yaml:formula_registry.formulas.QUALITATIVE_SELL_STRATEGY_V1"
python_tool: "src/quant_engine/qualitative_sell_strategy_v1.py:compute_qualitative_sell_strategy"
actions:
EXIT_REVIEW_FULL: "4-5개 팩터군 매도방향 합의 + composite_score>=0.6 — 전량 정리 검토"
TRIM_REVIEW_PARTIAL: "3개 이상 팩터군 매도방향 합의, composite_score<0.6 — 부분 정리 검토"
HOLD_ADD_CONVICTION: "3개 이상 팩터군 지지방향 합의 — 보유/추가 확신"
HOLD_NO_CONFLUENCE: "합의 미달 — 보유, 관찰 지속"
INSUFFICIENT_DATA_NO_ACTION: "confluence 판정에 필요한 최소 데이터 부족 — 추정 금지"
market_regime:
formula_ref: "spec/13b_harness_formulas.yaml:formula_registry.formulas.MARKET_REGIME_CLASSIFIER_V1"
rule: "금리 상승기(RISING)=PERFORMANCE_MARKET(실적장세) — fundamental_trajectory 가중 상향.
금리 보합/하락기(FLAT/FALLING)=TECHNICAL_MARKET(기술장세) — short_interest_pressure/
microstructure_pressure 가중 상향. confluence 합의건수 판정 자체는 가중치와 무관 —
composite_score(행동 강도)에만 영향."
satellite_candidate_score:
formula_ref: "spec/13b_harness_formulas.yaml:formula_registry.formulas.SATELLITE_CANDIDATE_SCORE_V1"
purpose: "미보유 위성 유니버스 종목의 BUY_CANDIDATE/WATCH/AVOID 사전 평가. sector_export_trend
(관세청/산업통상부 수출입동향)·fundamental_trajectory·relative_return_20d를 market_regime별
가중치로 종합."
data_sources:
note: >
2026-06-21 세션 실측 결과. investing.com 직접 스크래핑은 403(Cloudflare) 차단 확인 —
자동 수집 경로로 채택하지 않는다.
WBS-7.9(2026-06-22): Naver 도메인(finance.naver.com)은 현재 무인증 접근 가능(sise_day, frgn 엔드포인트).
다만 향후 Cloudflare 차단 가능성에 대비해 fetch_naver_market_data_v1.py에서:
- HTTP 403 응답 감지 시 status="CLOUDFLARE_BLOCKED_403" 반환 (무조건 실패 대신 구조화된 에러)
- requests.RequestException 캐치로 네트워크 오류 처리
- 호출부(build_qualitative_sell_inputs_v1.py)에서 상태 확인 후 DATA_MISSING_SAFE 처리
실제 차단 발생 시 대체 경로 없음(KRX는 OTP 필수, investing.com은 차단됨).
운영: Cloudflare_BLOCKED_403 상태 발생 시 slack/로그 경고 + 수동 실행.
relative_return_20d:
tool: "tools/fetch_naver_market_data_v1.py:compute_relative_return_20d"
source: "finance.naver.com/item/sise_day.naver (무인증, 동작 확인)"
status: "WORKING"
volume_ratio_5d:
tool: "tools/fetch_naver_market_data_v1.py:compute_volume_ratio_5d"
source: "finance.naver.com/item/sise_day.naver"
status: "WORKING"
foreign_institution_flow:
tool: "tools/fetch_naver_market_data_v1.py:fetch_foreign_institution_flow"
source: "finance.naver.com/item/frgn.naver (GAS gdc_01_fetch_fundamentals.gs와 동일 소스 —
보유종목은 기존 GAS 수집 결과 재사용 권장, 위성 후보군만 직접 호출)"
status: "WORKING"
sector_export_trend:
tool: "tools/fetch_trade_statistics_motie_v1.py:compute_sector_export_trend"
source: "관세청/산업통상부 수출입통계 — 1차: --csv 수동 다운로드 경로(안정적, 권장).
2차: data.go.kr OpenAPI(CUSTOMS_API_KEY 필요, 미설정 시 DATA_MISSING)."
status: "CSV_PATH_WORKING / API_PATH_NEEDS_KEY"
short_balance_ratio:
source: "KRX 공매도종합포털(open.krx.co.kr/contents/SRT) — 직접 API 호출은 OTP 세션 필요,
LOGOUT 응답으로 차단 확인. KIS Open API도 잔고율(보유 포지션 개념)은 제공하지 않음
(실측 확인, 2026-06-21). 수동 다운로드 CSV(--short-csv)로만 안정 확보 — 자동화
재시도 불필요(차단 확정)."
status: "MANUAL_CSV_ONLY"
short_turnover_share:
source: "[2026-06-21 해결] KIS Open API daily-short-sale(FHPST04830000,
/uapi/domestic-stock/v1/quotations/daily-short-sale) output2.ssts_vol_rlim —
실전계좌 도메인(--kis-account real)에서 실측 동작 확인. 모의계좌 도메인은
500 에러(미지원). Naver는 KRX iframe 위임으로 값 없음(폐기)."
tool: "tools/build_qualitative_sell_inputs_v1.py:fetch_kis_supplement"
status: "KIS_API_WORKING (real account only)"
microstructure_pressure_10_level_orderbook:
source: "[2026-06-21 해결] KIS Open API inquire-asking-price-exp-ccn(FHKST01010200,
/uapi/domestic-stock/v1/quotations/inquire-asking-price-exp-ccn) output1 —
실전+모의계좌 도메인 모두 실측 동작 확인. 필드명: askp1~10/bidp1~10/
askp_rsqn1~10/bidp_rsqn1~10/total_askp_rsqn/total_bidp_rsqn(전부 소문자,
실측 확인). 전략 방향 결정에는 쓰지 않고 confluence 성립 후 집행 타이밍
보조로만 사용(factor_families.microstructure_pressure 참조)."
tool: "src/quant_engine/qualitative_sell_strategy_v1.py:compute_microstructure_pressure_from_orderbook"
status: "KIS_API_WORKING"
investor_trend_official:
source: "[참고, 미연동] KIS Open API inquire-investor(FHKST01010900) —
개인/외국인/기관 순매수수량(prsn_ntby_qty/frgn_ntby_qty/orgn_ntby_qty) 등 실측
확인. Naver frgn.naver 스크래핑을 대체할 수 있는 공식 소스이나 아직 미연동
(기존 GAS 수급 피드와 중복 — 필요 시 후속 작업)."
status: "VERIFIED_NOT_WIRED"
kis_open_api_constraints:
note: "[CRITICAL] governance/rules/06_no_direct_api_trading.yaml(주문 미실행),
governance/rules/07_no_kis_account_balance_query.yaml(계좌 보유종목 조회 금지) —
KIS API는 시장 전체 공개 데이터(시세/호가/공매도/투자자동향) 조회에만 사용.
CI 강제 게이트: tools/validate_no_direct_api_trading_v1.py(strict, warn_only 불가)."
macro_pressure / rate_trend / next_earnings_date / next_macro_event_date / macro_event_impact:
source: "기존 GAS 하네스(macro_event_synchronizer_v2, gas_event_calendar.gs)가 이미
산출/수집 — 중복 수집 금지, --context-json으로 그 결과를 주입."
status: "REUSE_EXISTING_HARNESS"
orchestrator:
tool: "tools/build_qualitative_sell_inputs_v1.py"
purpose: "위 출처들을 종목별 ctx로 조립해 QUALITATIVE_SELL_STRATEGY_V1을 호출하고
outputs/qualitative_sell_strategy/<code>.json에 기록한다. --batch --workbook으로
account_snapshot 실보유 종목 전체 일괄 처리."
satellite_orchestrator:
tool: "tools/build_satellite_candidate_recommendations_v1.py"
purpose: "universe 시트(미보유 위성 유니버스)에서 보유종목을 제외한 후보 전체를
SATELLITE_CANDIDATE_SCORE_V1로 평가해 outputs/qualitative_sell_strategy/
satellite_recommendations.json에 기록한다. universe.Sector 한글 라벨은 부분
문자열 매칭으로 SECTOR_HS_MAP에 연결 — 매칭 실패 시 sector_export_trend를
추정하지 않고 None 유지(추정 금지 원칙)."