Files
QuantEngineByItz/spec/15_account_snapshot_contract.yaml
T
kjh2064 5166750b53 WBS-7.3/7.4/7.5/7.11: 거버넌스 문서 정합성 정리 + spec-코드 동기화 게이트
2026-06-21 비판적 리뷰에서 spec/governance YAML이 코드 상태와 어긋난
채로 방치되던 3개 구체적 사례를 발견하고 정정했다. 근본 원인(동기화를
보장하는 장치 없음)에 대응하는 신규 CI 게이트도 함께 추가한다.

- spec/aliases.yaml: deprecated alias 17건 제거(활성 참조 0건 확인 후,
  2026-06-30 데드라인 전). role: deprecated_redirect인 spec/03_risk_policy.yaml,
  spec/04_strategy_rules.yaml 2개만 실삭제 — spec/06_exit_policy.yaml은
  role: compatibility_index(영구유지 설계)였음을 재확인해 보존
- governance/gas_logic_migration_ledger_v1.yaml: 존재하지 않는 파일을
  canonical 구현으로 인용하던 오류 2건 발견·정정, parity 테스트 부재로
  GAS 코드 삭제 보류(F12/F13/F14)
- spec/13_formula_registry.yaml: OVERHANG_PRESSURE_V1의 "-500000"
  절대값 폴백을 avg_volume_5d 비례식으로 교체(EXPERT_PRIOR 등록)
- tools/validate_specs.py: validate_spec_code_sync() 신규 — has_code_implementation/
  code_path 필드가 있는 spec만 검사(점진적 롤아웃, 기존 PASS 상태 비파괴),
  12개 파일 1차 태깅
2026-06-21 20:08:48 +09:00

343 lines
19 KiB
YAML
Raw 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: "계좌 이미지 캡처 — Account Snapshot Contract"
parent_file: "RetirementAssetPortfolio.yaml"
version: "2026-05-16-F15_account_snapshot_contract"
language: "ko-KR"
timezone: "Asia/Seoul"
role: "canonical"
has_code_implementation: true
code_path: "src/quant_engine/snapshot_admin_store_v1.py"
purpose: >
이미지 캡처로 제공되는 계좌·잔고·현금 데이터를 구조화하는 계약.
HTS 입력 가능 주문수량은 이 계약을 통과한 account_snapshot 없이는 산출 금지.
account_snapshot_contract:
source_type: "image_capture"
priority: "highest_for_account_holdings_cash"
relationship_to_market_raw:
market_raw_json: "GatherTradingData.json"
market_raw_workbook_source: "GatherTradingData.xlsx"
rule: "market raw JSON/xlsx는 시장 데이터, account_snapshot은 계좌 데이터. 서로 대체하지 않는다."
required_capture_groups:
holdings_screen:
purpose: "보유수량·평단·평가금액 확인"
required_fields:
- "account"
- "account_type"
- "ticker_or_name"
- "holding_quantity"
- "average_cost"
- "current_price"
- "market_value"
cash_screen:
purpose: "즉시현금·D+2·주문가능금액 확인"
required_fields:
- "account"
- "immediate_cash"
- "settlement_cash_d2"
- "available_cash"
open_orders_screen:
purpose: "미체결 주문·예약 주문금액 확인"
required_fields:
- "account"
- "ticker_or_name"
- "open_order_quantity"
- "open_order_amount"
- "order_side"
contribution_limit_screen:
purpose: "ISA/연금저축 납입 가능액·사용액 확인"
required_fields:
- "account"
- "account_type"
- "monthly_contribution_limit"
- "monthly_contribution_used"
- "remaining_contribution_capacity"
canonical_fields:
captured_at: {type: "datetime", timezone: "Asia/Seoul", required: true, column_position: 1}
account: {type: "string", required: true}
account_type: {type: "enum", allowed: ["일반계좌", "ISA", "연금저축"], required: true}
ticker: {type: "string", required_for: ["position_match"], missing_action: "match_by_name_then_warn"}
name: {type: "string", required_for: ["position_match"]}
holding_quantity: {type: "integer", unit: "shares", required_for: ["sell_quantity", "total_heat", "take_profit"], note: "국내주식 정수 필수. 해외주식(foreign_equity_flag=true)은 AS002A 예외 적용, 소수 4자리까지 허용."}
available_quantity: {type: "integer", unit: "shares", note: "HTS 매도가능수량. 미표시 시 빈칸."}
foreign_equity_flag: {type: "boolean", optional: true, default: false, note: "해외주식 여부. true이면 AS002A 예외 적용."}
foreign_currency: {type: "string", optional: true, note: "해외주식 원화환산 전 통화 코드. 예: USD, HKD."}
fx_rate_at_capture: {type: "number", optional: true, unit: "KRW_per_foreign", note: "캡처 시점 환율. 원화평가액 = foreign_quantity × foreign_price × fx_rate."}
krw_estimated_value: {type: "number", optional: true, unit: "KRW", note: "환율 적용 원화 평가금액. market_value와 교차검증."}
estimated_withholding_tax_rate_pct: {type: "number", optional: true, unit: "percent", note: "해외주식 배당 원천징수세율. 예: 15.0 (미국 기본세율)."}
country_code: {type: "string", optional: true, note: "종목 상장 국가코드. ISO 3166-1 alpha-2. 예: US, HK, JP."}
average_cost: {type: "number", unit: "KRW_per_share", required_for: ["profit_pct", "take_profit"]}
total_cost: {type: "number", unit: "KRW", note: "매입금액 = holding_quantity × average_cost. HTS 직접 값 우선."}
current_price: {type: "number", unit: "KRW_per_share", required_for: ["position_value_cross_check"]}
market_value: {type: "number", unit: "KRW", required_for: ["cross_check"]}
profit_loss: {type: "number", unit: "KRW", note: "평가손익. HTS 화면 값."}
return_pct: {type: "number", unit: "percent", note: "수익률%. 예: 7.5 (% 기호 제외)."}
immediate_cash: {type: "number", unit: "KRW", required_for: ["cash_floor"], note: "일반계좌 기준 포트폴리오 현금 원장. ISA/연금저축 행은 참고잔액일 수 있으나 포트폴리오 cash_floor/buy_power 합산 금지."}
settlement_cash_d2: {type: "number", unit: "KRW", required_for: ["buy_power"], note: "일반계좌 기준 D+2 원장. ISA/연금저축 행은 일반계좌 매수재원으로 합산 금지."}
available_cash: {type: "number", unit: "KRW", required_for: ["position_sizing"], note: "계좌별 주문 가능 금액. 일반계좌 외 계좌는 동일 계좌 내부 의사결정 참고용으로만 사용."}
open_order_amount: {type: "number", unit: "KRW", default: 0}
monthly_contribution_limit: {type: "number", unit: "KRW", required_for: ["ISA", "연금저축"]}
monthly_contribution_used: {type: "number", unit: "KRW", required_for: ["ISA", "연금저축"]}
parse_status: {type: "enum", allowed: ["CAPTURE_READ_OK", "CAPTURE_READ_FAILED", "CAPTURE_PROVIDED_BUT_NOT_HOLDINGS", "NOT_PROVIDED"], required: true}
stop_price: {type: "number", unit: "KRW_per_share", optional: true, note: "선택 손절가. 미입력 시 ATR 기반 추정."}
highest_price_since_entry: {type: "number", unit: "KRW_per_share", optional: true}
entry_date: {type: "date_ISO8601", optional: true}
entry_stage: {type: "string", optional: true, allowed: ["stage_1", "stage_2", "stage_3", "mixed"]}
position_type: {type: "string", optional: true, allowed: ["core", "satellite"], default: "satellite"}
last_updated: {type: "date_ISO8601", optional: true}
capture_priority_rules:
principle: >
캡처 이미지로 제공된 데이터는 JSON data.account_snapshot의
기존 값보다 항상 우선한다. 충돌 시 캡처값이 정답이며 GAS값은 무효.
conflict_detection:
trigger: "GatherTradingData.json 또는 account_snapshot 데이터가 같은 대화에 업로드된 경우"
compare_fields: ["holding_quantity", "average_cost"]
output: "capture_parse_prompt.md STEP 2b 충돌 감지 표"
override_fields:
- holding_quantity: "캡처값 → Sell_Qty 재산출 기준"
- average_cost: "캡처값 → Profit_Pct·Unrealized_PnL 재산출 기준"
- market_value: "캡처값 → Weight_Pct 재산출 기준"
recalculate_on_override:
- "Profit_Pct = (Close - 캡처평단) / 캡처평단 × 100"
- "Unrealized_PnL = (Close - 캡처평단) × 캡처수량"
- "Weight_Pct = (Close × 캡처수량) / 총자산 × 100"
- "Sell_Qty = 캡처수량 × Sell_Ratio_Pct / 100 (반올림, available_quantity 상한)"
no_recalculate:
- "SS001_Grade, RW_Partial, Flow_Credit, ATR20, MA20, Final_Action, Sell_Ratio_Pct"
- "이 항목들은 시장데이터 기반 — JSON data.data_feed 값 그대로 사용"
display:
tag: "(캡처재산출)"
rule: "재산출된 값 옆에 태그를 붙여 GAS값과 구분"
isa_pension_scope:
rule: >
ISA·연금저축 계좌 캡처의 금액은 일반계좌 현금이 아니라
이미 매입·운용이 진행 중인 계좌의 잔액/평가 reference로 취급한다.
따라서 해당 행의 immediate_cash, settlement_cash_d2, available_cash, open_order_amount는
포트폴리오 레벨 cash_floor, immediate_cash_pct, buy_power_krw 합산에 포함하지 않는다.
개별 종목 보유수량·평단이 account_snapshot에 포함되지 않으면
해당 계좌 종목의 Sell_Qty 산출 불가 → "캡처확인후기재".
routing_rule:
portfolio_cash_ledger: "일반계좌 only"
restricted_account_types: ["ISA", "연금저축"]
restricted_account_treatment:
- "포트폴리오 cash_floor 계산 제외"
- "포트폴리오 buy_power 계산 제외"
- "일반계좌 신규매수 재원으로 전용 금지"
- "계좌 유형 식별·한도 추적·보유평가 참고용으로만 유지"
validation_rules:
- id: "AS001_LABEL_FIRST"
rule: "숫자보다 라벨을 먼저 판독한다. 라벨 없는 숫자는 사용 금지."
- id: "AS002_QUANTITY_INTEGER"
rule: "holding_quantity와 open_order_quantity는 정수. 소수면 CAPTURE_READ_FAILED."
- id: "AS002A_FOREIGN_EQUITY_DECIMAL"
rule: "foreign_equity_flag=true인 종목은 holding_quantity가 소수 가능. decimal_precision: 4. CAPTURE_READ_OK 처리. open_order_quantity도 동일 예외 적용."
condition: "foreign_equity_flag == true"
decimal_precision: 4
note: "해외주식 소수주(미국 증권사 fractional share, ISA 해외ETF 등) 대응. 국내주식에는 이 예외 미적용."
- id: "AS003_MARKET_VALUE_CROSS_CHECK"
rule: "abs(market_value - holding_quantity * current_price) / max(market_value, 1) <= 0.01 이어야 한다."
fail_action: "DATA_CONFLICT"
- id: "AS004_CASH_NON_NEGATIVE"
rule: "현금 관련 필드는 0 이상이어야 한다."
fail_action: "CAPTURE_READ_FAILED"
- id: "AS005_ACCOUNT_SCOPE"
rule: "계좌별 주문수량은 같은 계좌의 보유수량·현금만 사용한다. 계좌 간 현금 합산 금지."
- id: "AS005A_RESTRICTED_ACCOUNT_CASH_EXCLUSION"
rule: "account_type in [ISA, 연금저축] 행의 현금성 숫자는 포트폴리오 immediate_cash, settlement_cash_d2, buy_power 집계에서 제외한다."
fail_action: "HARNESS_CASH_LEDGER_CONFLICT"
- id: "AS006_AUTO_INVEST_SCREEN_EXCLUSION"
rule: "자동투자/적립식 설정 화면은 보유수량·평단·현금 원장으로 사용 금지."
output_required:
table_name: "account_snapshot"
total_columns: 27
column_order_locked: true
columns:
- "captured_at"
- "account"
- "account_type"
- "ticker"
- "name"
- "holding_quantity"
- "available_quantity"
- "average_cost"
- "total_cost"
- "current_price"
- "market_value"
- "profit_loss"
- "return_pct"
- "immediate_cash"
- "settlement_cash_d2"
- "available_cash"
- "open_order_amount"
- "monthly_contribution_limit"
- "monthly_contribution_used"
- "parse_status"
- "user_confirmed"
- "stop_price"
- "highest_price_since_entry"
- "entry_date"
- "entry_stage"
- "position_type"
- "last_updated"
excel_paste_output:
purpose: >
캡처 판독 후 분석보다 먼저 엑셀 붙여넣기용 TSV 표를 출력한다.
사용자가 account_snapshot 탭에 복사·붙여넣기만으로 원장을 갱신할 수 있게 한다.
trigger: "HTS 캡처 이미지가 대화에 첨부된 모든 경우"
prompt_file: "prompts/capture_parse_prompt.md"
output_sequence:
- step: 1
output: "화면 종류 판별 + 검증 결과 (AS001~AS006)"
- step: 2
output: "account_snapshot 탭 붙여넣기용 TSV (헤더 없이, A3 셀 기준)"
format: "TSV (탭구분), 코드블록으로 감싸기"
paste_target: "account_snapshot 탭 A3 셀"
column_order_locked: true
total_columns: 27
column_sequence: "captured_at|account|account_type|ticker|name|holding_quantity|available_quantity|average_cost|total_cost|current_price|market_value|profit_loss|return_pct|immediate_cash|settlement_cash_d2|available_cash|open_order_amount|monthly_contribution_limit|monthly_contribution_used|parse_status|user_confirmed|stop_price|highest_price_since_entry|entry_date|entry_stage|position_type|last_updated"
user_confirmed_value: "Y"
- step: 3
output: "account_snapshot 선택 포지션 상태 갱신 표 (ticker | stop_price | highest_price_since_entry | entry_stage | position_type | last_updated)"
note: "보유수량·평단은 account_snapshot 캡처 TSV가 담당하며 positions 탭은 사용하지 않음"
- step: 4
output: "현금 요약 1줄 + settings 탭 settlement_cash_d2_krw 입력 안내"
- step: 5
output: "다음 단계 안내 (account_snapshot 붙여넣기 → GAS 재실행)"
prohibition:
- "캡처 파싱 표 생략 후 분석부터 시작 금지"
- "TSV 대신 마크다운 표만 출력 금지 (마크다운 표는 Excel 붙여넣기 불가)"
- "캡처 데이터를 분석에만 사용하고 원장 갱신 표를 누락하는 행위 금지"
hard_stops:
- "parse_status != CAPTURE_READ_OK인 계좌는 주문수량 산출 금지"
- "holding_quantity 미확인 종목은 매도수량 산출 금지"
- "available_cash 미확인 계좌는 매수수량 산출 금지"
- "open_order_amount 미확인 시 중복주문 위험을 표시하고 validation_status=REVIEW_REQUIRED"
# ─────────────────────────────────────────────────────────────────────────────
# account_snapshot 포지션 상태 계약 (S2_stop_price_tracking — 2026-05-18)
# ─────────────────────────────────────────────────────────────────────────────
account_snapshot_position_state:
sheet_name: "account_snapshot"
status: "canonical"
replaces: "positions"
purpose: >
보유수량·평단·현금 원장과 선택 포지션 상태(stop_price, highest_price_since_entry,
entry_stage, position_type)를 account_snapshot에 통합한다.
positions 탭은 deprecated이며 신규 분석·GAS 계산 입력으로 사용하지 않는다.
optional_state_columns:
- "stop_price"
- "highest_price_since_entry"
- "entry_date"
- "entry_stage"
- "position_type"
- "last_updated"
validation_rules:
- id: "AS007_STOP_BELOW_AVERAGE_COST"
rule: "stop_price가 있으면 stop_price < average_cost 이어야 한다. 위반 시 GAS가 경고 로그 출력."
- id: "AS008_TOTAL_HEAT_CONFIRMED_ROWS_ONLY"
rule: "TOTAL_HEAT_V1은 parse_status=CAPTURE_READ_OK AND user_confirmed=Y인 account_snapshot 보유행만 사용한다."
gas_integration:
function_name: "readAccountSnapshotHeat_"
purpose: "account_snapshot을 읽어 TOTAL_HEAT_V1 계산."
formula: >
For each confirmed holding where holding_quantity > 0:
heat_i = (average_cost - stop_price_or_atr_estimate) * holding_quantity
total_heat_krw = sum(heat_i)
total_heat_pct = total_heat_krw / total_asset_krw * 100
fallback:
condition: "stop_price 미입력 포지션 존재"
return: >
ATR 기반 추정: stop_price_est = average_cost - ATR20 * 1.5 (data_feed에서 ATR20 조회).
ATR20도 없으면 average_cost * 0.92 보수 추정.
추정값 사용 시 hf005_status에 "(ATR추정)" 표기.
freshness_policy:
update_frequency: "매일 장마감 직후 1회 (16:30~17:00)"
required_fields_to_update: ["holding_quantity", "average_cost", "stop_price", "highest_price_since_entry", "last_updated"]
staleness_threshold_days: 1
staleness_action: >
getDailyBrief()가 account_snapshot last_updated/captured_at 기준으로 경과일을 계산해
1일 초과 시 brief_text에 'account_snapshot STALE' 경고를 출력한다.
gas_check_function: "checkAccountSnapshotFreshness_()"
positions_tab:
status: "deprecated"
prohibition:
- "신규 분석, TOTAL_HEAT, stop_price, 수량, 평단 입력 원장으로 사용 금지"
- "사용자에게 positions 탭 수동 입력을 요구 금지"
# ─────────────────────────────────────────────────────────────────────────────
# 해외주식 스냅샷 계약 확장 (foreign_holdings_snapshot_extension — 2026-06-10)
# ─────────────────────────────────────────────────────────────────────────────
foreign_holdings_snapshot_extension:
status: "active"
applies_to: "account_snapshot rows where foreign_equity_flag=true"
purpose: >
해외주식(미국·홍콩·일본 등) 포지션의 원화환산·환율·세금 정보를 구조화.
국내주식 account_snapshot 계약(AS001~AS008)을 기반으로 해외 특화 필드를 추가 정의.
required_additional_fields:
- name: "foreign_equity_flag"
type: "boolean"
value: true
note: "해외주식 임을 명시. AS002A 소수점 예외 및 이 계약 적용 트리거."
- name: "foreign_currency"
type: "string"
examples: ["USD", "HKD", "JPY"]
note: "종목 원본 통화. HTS 화면에서 확인. 미확인 시 CAPTURE_READ_FAILED."
- name: "fx_rate_at_capture"
type: "number"
unit: "KRW_per_foreign"
note: "캡처 시점 매매기준율. 예: 1380.5 (1달러=1380.5원). 동일 통화는 동일 환율 일관성 필수."
optional_additional_fields:
- name: "krw_estimated_value"
type: "number"
unit: "KRW"
formula: "holding_quantity × foreign_price × fx_rate_at_capture"
note: "엔진 내부 원화 환산 평가액. market_value와 1% 이내 일치 확인(AS003 확장)."
- name: "estimated_withholding_tax_rate_pct"
type: "number"
unit: "percent"
defaults: {"US": 15.0, "HK": 0.0, "JP": 15.315}
note: "배당금 원천징수세율. 시세차익세금과 별도. 운용 알파 계산 시 세후 수익률 보정에 사용."
- name: "capital_gains_tax_applicable"
type: "boolean"
note: "해외주식 양도소득세(250만원 공제 후 22%) 적용 여부. 일반계좌=true, ISA 내=false."
- name: "country_code"
type: "string"
format: "ISO 3166-1 alpha-2"
examples: ["US", "HK", "JP"]
note: "상장 국가코드. 세율·환율 룩업 키로 사용."
validation_rules:
- id: "ASFE001_FX_RATE_POSITIVE"
rule: "fx_rate_at_capture > 0 이어야 한다. 0 또는 미입력 시 CAPTURE_READ_FAILED."
- id: "ASFE002_CURRENCY_CONSISTENT"
rule: "동일 통화 포지션은 동일 캡처 세션에서 같은 fx_rate_at_capture 사용. 다르면 DATA_CONFLICT."
- id: "ASFE003_KRW_VALUE_CROSS_CHECK"
rule: "krw_estimated_value 입력 시 |krw_estimated_value - holding_quantity × foreign_price × fx_rate| / krw_estimated_value <= 0.02. 초과 시 DATA_CONFLICT."
gas_integration:
note: >
GAS parseAccountSnapshot_은 ticker 형식으로 국내/해외를 구분:
국내 티커 패턴 = 6자리 숫자(\\d{6}), 해외 티커 패턴 = 영문 1~5자(\\b[A-Z]{1,5}\\b).
foreign_equity_flag=true 행은 원화 환산 후 total_asset에 포함.
fx_rate_at_capture 미입력 시 GAS가 settings 탭의 fx_rate_default 사용, 경고 로그 출력.
function_name: "parseAccountSnapshot_"
required_settings_key: "fx_rate_default"
data_gated_features:
- item: "해외주식 환율 손익 분리 표시"
status: "DATA_GATED"
note: "캡처 시점 환율 이력 누적 필요. 최소 20세션 이상 축적 후 활성화."
- item: "세후 수익률 보정"
status: "DATA_GATED"
note: "양도소득세 적용 여부 확인 후 활성화. 현재는 세전 수익률만 사용."