416da59607
Coverage improvement: 24.07% (39 files) → 66.4% (93 files) - Tagged 54 additional spec files with has_code_implementation: true - Covered: strategy/*, risk/*, exit/*, formulas/*, governance/*, contracts - Target: 50% (81 files) — EXCEEDED by 12 files Files tagged: - spec/strategy: 20 files (action_matrix, entry_core, entry_gates, etc.) - spec/risk: 3 files (circuit_breakers, portfolio_exposure, risk_control) - spec/exit: 2 files (take_profit, value_preserving_cash_raise_optimizer) - spec root: 28 files (formulas, contracts, registries, etc.) - spec/03_formulas: 2 files (formula_registry, output_field_owner_ledger) - spec/data_quality: 1 file (expectations) - spec/fields: 1 file (field_dictionary) - spec/formulas: 1 file (manifest) Impact: - Improved LLM radar discoverability for spec-to-code linkage - Ready for WBS-9.6 (LLM document optimization phase) Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
232 lines
10 KiB
YAML
232 lines
10 KiB
YAML
meta:
|
||
title: "performance 탭 계약서 — Bayesian Multiplier 자동 계산"
|
||
parent_file: "RetirementAssetPortfolio.yaml"
|
||
version: "2026-05-17-initial"
|
||
language: "ko-KR"
|
||
timezone: "Asia/Seoul"
|
||
role: "canonical"
|
||
has_code_implementation: true
|
||
code_path: ["tools/build_honest_performance_guard_v1.py", "src/quant_engine/qualitative_sell_strategy_v1.py"]
|
||
purpose: >
|
||
Google Sheets 'performance' 탭의 구조·입력 규칙을 정의하고,
|
||
GAS가 이 데이터를 읽어 Bayesian multiplier를 자동 계산하는 계약을 명시한다.
|
||
S1_trades_performance_sheet(spec/16_data_gaps_roadmap.yaml) 구현 사양.
|
||
|
||
# ─────────────────────────────────────────────────────────────────────────────
|
||
# 시트 구조
|
||
# ─────────────────────────────────────────────────────────────────────────────
|
||
sheet:
|
||
name: "performance"
|
||
header_row: 2 # row1=updated 메타, row2=헤더
|
||
data_start_row: 3
|
||
sort_order: "entry_date 내림차순 (최신 거래 위)"
|
||
max_lookback_trades: 30 # Bayesian 계산에 사용하는 최근 거래 수
|
||
|
||
required_columns:
|
||
- name: "trade_id"
|
||
type: "string"
|
||
format: "T-YYYYMMDD-NNN (예: T-20260517-001)"
|
||
note: "중복 방지용 고유 ID. 수동 입력."
|
||
- name: "ticker"
|
||
type: "string"
|
||
note: "종목코드 6자리."
|
||
- name: "name"
|
||
type: "string"
|
||
- name: "sector"
|
||
type: "string"
|
||
note: "TICKER_SECTOR_MAP 기준 섹터명."
|
||
- name: "account"
|
||
type: "string"
|
||
allowed_values: ["일반계좌", "ISA", "연금저축"]
|
||
- name: "entry_date"
|
||
type: "date_ISO8601"
|
||
example: "2026-05-17"
|
||
- name: "entry_price"
|
||
type: "number"
|
||
unit: "KRW_per_share"
|
||
- name: "entry_stage"
|
||
type: "string"
|
||
allowed_values: ["stage_1", "stage_2", "stage_3"]
|
||
note: "staged_entry_v2 기준 진입 단계."
|
||
- name: "quantity"
|
||
type: "integer"
|
||
unit: "shares"
|
||
- name: "stop_price_at_entry"
|
||
type: "number"
|
||
unit: "KRW_per_share"
|
||
note: "진입 당시 설정한 손절가. ATR 기반 또는 HTS 실제 설정값."
|
||
- name: "target_price_at_entry"
|
||
type: "number"
|
||
unit: "KRW_per_share"
|
||
note: "진입 당시 컨센서스 목표주가."
|
||
- name: "exit_date"
|
||
type: "date_ISO8601"
|
||
note: "미청산 포지션은 공백 — 진행 중 거래 제외 후 계산."
|
||
- name: "exit_price"
|
||
type: "number"
|
||
unit: "KRW_per_share"
|
||
note: "미청산 시 공백."
|
||
- name: "exit_reason"
|
||
type: "string"
|
||
allowed_values: ["stop_loss", "take_profit", "time_stop", "rw_exit", "manual", "partial_exit"]
|
||
note: "exit_date 공백이면 이 필드도 공백."
|
||
- name: "pnl_pct"
|
||
type: "number"
|
||
unit: "percent"
|
||
expression: "(exit_price / entry_price - 1) * 100"
|
||
note: "수동 입력 또는 시트 수식. 미청산 시 공백."
|
||
- name: "holding_days"
|
||
type: "integer"
|
||
expression: "exit_date - entry_date (영업일 기준 권장, 달력일도 허용)"
|
||
note: "미청산 시 공백."
|
||
- name: "entry_c1_score"
|
||
type: "number"
|
||
note: "진입 당시 C1 값 (0 or 1). data_feed 탭에서 복사."
|
||
- name: "entry_c2_score"
|
||
type: "number"
|
||
- name: "entry_c3_score"
|
||
type: "number"
|
||
- name: "entry_c4_score"
|
||
type: "number"
|
||
- name: "entry_c5_score"
|
||
type: "number"
|
||
- name: "entry_mrs_score"
|
||
type: "number"
|
||
note: "진입 당시 MRS 점수 (0~10). macro 탭에서 복사."
|
||
- name: "entry_leader_scan_total"
|
||
type: "number"
|
||
note: "진입 당시 Leader_Scan_Total. 자동 계산 = sum(C1~C5)."
|
||
- name: "fc_bucket"
|
||
type: "string"
|
||
allowed_values: ["Y", "N"]
|
||
note: "Y=stage_1 탐색 손실 → explore_loss_budget 귀속. N=본계좌 PnL."
|
||
|
||
# ─────────────────────────────────────────────────────────────────────────────
|
||
# Bayesian Multiplier 계산 규칙
|
||
# ─────────────────────────────────────────────────────────────────────────────
|
||
bayesian_multiplier:
|
||
formula_ref: "spec/13_formula_registry.yaml:formula_registry.formulas.RISK_BUDGET_CASCADE_V1"
|
||
lookback: 30 # 최근 N건 청산 완료 거래만 사용 (exit_date 공백 제외)
|
||
minimum_trades: 5 # 5건 미만 시 not_enough_data → medium_confidence(0.5×) 기본
|
||
|
||
derived_metrics:
|
||
win_rate:
|
||
expression: "count(pnl_pct > 0) / count(pnl_pct is not null)"
|
||
note: "청산 완료 거래 중 수익 비율."
|
||
avg_win_pct:
|
||
expression: "mean(pnl_pct where pnl_pct > 0)"
|
||
avg_loss_pct:
|
||
expression: "mean(abs(pnl_pct) where pnl_pct <= 0)"
|
||
net_expectancy:
|
||
expression: "(win_rate * avg_win_pct) - ((1 - win_rate) * avg_loss_pct)"
|
||
note: "양수=시스템 양기대치. 음수=시스템 개선 필요."
|
||
|
||
multiplier_rules:
|
||
high_confidence:
|
||
condition: "win_rate >= 0.60 AND net_expectancy >= 3.0"
|
||
multiplier: 1.0
|
||
label: "high_bet"
|
||
medium_confidence:
|
||
condition: "win_rate >= 0.45 AND net_expectancy >= 0"
|
||
multiplier: 0.5
|
||
label: "medium_bet"
|
||
low_confidence:
|
||
condition: "win_rate < 0.45 OR net_expectancy < 0"
|
||
multiplier: 0.25
|
||
label: "low_bet"
|
||
no_bet:
|
||
condition: "연속 5회 손절 (최근 5건 모두 pnl_pct <= 0)"
|
||
multiplier: 0.0
|
||
label: "no_bet"
|
||
note: "시스템 재검토 기간. performance_brake와 연동."
|
||
not_enough_data:
|
||
condition: "청산 완료 거래 < minimum_trades"
|
||
multiplier: 0.5
|
||
label: "medium_confidence (데이터 부족 기본값)"
|
||
|
||
output_fields:
|
||
bayesian_multiplier:
|
||
type: "number"
|
||
values: [0.0, 0.25, 0.5, 1.0]
|
||
bayesian_label:
|
||
type: "string"
|
||
values: ["high_bet", "medium_bet", "low_bet", "no_bet", "medium_confidence"]
|
||
win_rate_30:
|
||
type: "number"
|
||
note: "최근 30건 승률."
|
||
net_expectancy_30:
|
||
type: "number"
|
||
note: "최근 30건 기대수익률(%)."
|
||
consecutive_losses:
|
||
type: "integer"
|
||
note: "최근 연속 손절 횟수."
|
||
trades_used:
|
||
type: "integer"
|
||
note: "계산에 사용된 거래 수."
|
||
|
||
# ─────────────────────────────────────────────────────────────────────────────
|
||
# GAS 통합 계획
|
||
# ─────────────────────────────────────────────────────────────────────────────
|
||
gas_integration:
|
||
function_name: "readPerformanceSheet_"
|
||
return_type: "object"
|
||
return_fields:
|
||
- "bayesian_multiplier: number (0.0/0.25/0.5/1.0)"
|
||
- "bayesian_label: string"
|
||
- "win_rate_30: number | null"
|
||
- "net_expectancy_30: number | null"
|
||
- "consecutive_losses: integer"
|
||
- "trades_used: integer"
|
||
fallback:
|
||
condition: "performance 탭 없음 OR 청산 완료 거래 < 5건"
|
||
return: "{ bayesian_multiplier: 0.5, bayesian_label: 'medium_confidence', trades_used: 0 }"
|
||
integration_point:
|
||
sheet: "data_feed"
|
||
field: "EE_Est"
|
||
current_formula: "(target-entry)/(entry-stop) × 0.5 - 0.003"
|
||
updated_formula: "(target-entry)/(entry-stop) × bayesian_multiplier - 0.003"
|
||
note: "runDataFeed 시작 시 1회 호출 → 루프 전체에 동일 multiplier 적용."
|
||
additional_output:
|
||
sheet: "macro"
|
||
row: "BAYESIAN_COMPUTED"
|
||
fields: ["Close=multiplier", "Status=label + win_rate + net_expectancy"]
|
||
note: "runMacro 또는 runDataFeed 마지막에 macro 탭에 Bayesian 상태 추가 행으로 기록."
|
||
|
||
# ─────────────────────────────────────────────────────────────────────────────
|
||
# 운영 지침
|
||
# ─────────────────────────────────────────────────────────────────────────────
|
||
operational_rules:
|
||
- "포지션 청산 당일 performance 탭에 수동 기록한다."
|
||
- "entry_c1~c5는 진입 당일 data_feed 탭에서 복사 — 사후 재계산 금지."
|
||
- "entry_mrs_score는 진입 당일 macro 탭 MRS_COMPUTED 행의 Close 값."
|
||
- "fc_bucket=Y인 거래는 explore_loss_budget 누적에 포함. 월말 집계."
|
||
- "연속 5회 손절(no_bet) 발동 시 runDataFeed에서 EE_Est=0으로 출력 — 신규 진입 자동 억제."
|
||
|
||
# ─────────────────────────────────────────────────────────────────────────────
|
||
# 팩터별 성과 피드백 및 정직 성과증빙 규칙 (P6-T04)
|
||
# ─────────────────────────────────────────────────────────────────────────────
|
||
honest_performance_guard:
|
||
formula_id: HONEST_PERFORMANCE_GUARD_V1
|
||
rules:
|
||
- rule_id: HP001
|
||
desc: "Live 표본 수가 30건 미만인 지표는 active 승격 근거로 사용 금지 (calibration_state=INSUFFICIENT_SAMPLES 강제)"
|
||
condition: "live_sample_count < 30"
|
||
action: "LOCK_CALIBRATION"
|
||
- rule_id: HP002
|
||
desc: "Replay 데이터와 Live 데이터를 혼합하여 성과 지표를 산출하는 행위 금지 (replay_in_live_stats == 0)"
|
||
condition: "replay_in_live_stats > 0"
|
||
action: "INVALIDATE_METRICS"
|
||
- rule_id: HP003
|
||
desc: "팩터별 성과(T+5/T+20/T+60) 결과를 horizon별로 분리해서 추적 및 저장한다."
|
||
required_fields:
|
||
- "ticker"
|
||
- "action"
|
||
- "horizon"
|
||
- "factor_set"
|
||
- "outcome"
|
||
acceptance_criteria:
|
||
factor_outcome_join_rate_pct: 95.0
|
||
live_sample_under_30_unlock_count: 0
|
||
replay_live_mixed_metric_count: 0
|
||
|