Files
QuantEngineByItz/spec/17_performance_contract.yaml
kjh2064 ee3e799de1 feat: 리밸런싱 엔진 V1 + GAS 버그 수정 (2026-06-13)
주요 변경:
- tools/build_rebalance_engine_v1.py: REBALANCE_ENGINE_V1 신규
  * account_snapshot 직접 합산(_build_snap_position_map) → 소수주 분리 행 병합
  * 레짐 소스 macro.REGIME_PRELIM 최우선 (GAS 와 동일)
- src/gas_adapter_parts/gdf_06_rebalance.gs: runRebalanceSheet_() 신규
  * Logger.log / getSpreadsheet_() 로 run_all 연동 수정
- src/gas_adapter_parts/gdc_01_fetch_fundamentals.gs
  * _mergePositionRecord_(): 소수주 중복 행 합산 신규
  * parseInt → parseFloat (qty, availQty)
- src/gas_adapter_parts/gdf_01_price_metrics.gs
  * 미보유 종목 SELL_READY → WATCH_EXIT_SIGNAL
- spec/41_release_dag.yaml: build_rebalance_sheet 노드 추가 (step_count 63)
- spec/51_formula_lifecycle_registry.yaml: REBALANCE_ENGINE_V1 등록

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 13:20:14 +09:00

202 lines
8.7 KiB
YAML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
meta:
title: "performance 탭 계약서 — Bayesian Multiplier 자동 계산"
parent_file: "RetirementAssetPortfolio.yaml"
version: "2026-05-17-initial"
language: "ko-KR"
timezone: "Asia/Seoul"
role: "canonical"
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으로 출력 — 신규 진입 자동 억제."