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 유지(추정 금지 원칙)."
