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>
This commit is contained in:
2026-06-13 13:20:14 +09:00
commit ee3e799de1
1474 changed files with 176087 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
"""Canonical quant_engine package."""
@@ -0,0 +1,91 @@
from __future__ import annotations
import json
import sys
from pathlib import Path
from typing import Any
def _load(path: Path) -> dict[str, Any]:
return json.loads(path.read_text(encoding="utf-8"))
def _parse_jsonish(value: Any) -> Any:
if isinstance(value, str):
s = value.strip()
if (s.startswith("{") and s.endswith("}")) or (s.startswith("[") and s.endswith("]")):
try:
return json.loads(s)
except Exception:
return value
return value
def _extract_harness(payload: dict[str, Any]) -> dict[str, Any]:
primary = payload.get("hApex")
fallback = (payload.get("data") or {}).get("_harness_context")
if isinstance(primary, dict) and isinstance(fallback, dict):
merged = dict(fallback)
merged.update(primary)
return merged
if isinstance(primary, dict):
return primary
if isinstance(fallback, dict):
return fallback
return {}
def validate_harness_v4(payload: dict[str, Any]) -> tuple[bool, list[str]]:
h = _extract_harness(payload)
required = [
"fundamental_quality_json",
"horizon_allocation_json",
"smart_money_liquidity_json",
"routing_serving_trace_v2_json",
]
errors: list[str] = []
for k in required:
if k not in h:
errors.append(f"missing key: {k}")
fq = _parse_jsonish(h.get("fundamental_quality_json", {}))
if not isinstance(fq, dict) or not isinstance(fq.get("rows"), list):
errors.append("fundamental_quality_json.rows missing")
hz = _parse_jsonish(h.get("horizon_allocation_json", {}))
if not isinstance(hz, dict) or not isinstance(hz.get("bucket_summary"), list):
errors.append("horizon_allocation_json.bucket_summary missing")
sml = _parse_jsonish(h.get("smart_money_liquidity_json", {}))
if not isinstance(sml, dict) or not isinstance(sml.get("rows"), list):
errors.append("smart_money_liquidity_json.rows missing")
tr = _parse_jsonish(h.get("routing_serving_trace_v2_json", {}))
if not isinstance(tr, dict):
errors.append("routing_serving_trace_v2_json invalid")
else:
for k in ("request_route", "json_validation_status"):
if not tr.get(k):
errors.append(f"routing_serving_trace_v2_json.{k} missing")
if tr.get("llm_serving_budget") != 0:
errors.append("routing_serving_trace_v2_json.llm_serving_budget must be 0")
return (len(errors) == 0), errors
def main() -> int:
if len(sys.argv) < 2:
print("Usage: python tools/apply_engine_upgrade_v4.py <input_json>")
return 1
payload = _load(Path(sys.argv[1]))
ok, errors = validate_harness_v4(payload)
if not ok:
print("ENGINE_UPGRADE_V4_FAIL")
for e in errors:
print(f"- {e}")
return 1
print("ENGINE_UPGRADE_V4_OK")
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,79 @@
"""
apply_engine_upgrade_v7.py
목적: V4.0 고도화 업데이트에 따라 산출된 모든 신규 P0 하네스들이
JSON output에 100% 정상적으로 포함되었는지 검증하고,
특히 LLM 통제를 위한 llm_serving_budget=0 설정이 올바른지 강제 검증합니다.
"""
from __future__ import annotations
import json
import sys
from pathlib import Path
from typing import Any
def load_payload(path: Path) -> dict[str, Any]:
return json.loads(path.read_text(encoding="utf-8"))
def validate_harness_v7(payload: dict[str, Any]) -> bool:
data = payload.get("data", {})
harness_context = data.get("_harness_context", {})
print("=== Engine Upgrade V7 Validator (Strict Mode) ===")
required_keys = [
"fundamental_quality_json",
"smart_money_liquidity_json",
"predictive_alpha_dialectic_json",
"horizon_allocation_json",
"dynamic_value_preservation_json",
"routing_serving_trace_v2_json"
]
is_valid = True
for key in required_keys:
if key not in harness_context:
print(f"[FAIL] Missing required P0 harness key: {key}")
# Depending on environment, missing keys might trigger an automatic ABORT_PIPELINE
else:
print(f"[PASS] Found {key}")
# 동적 비율 검증
dvp = harness_context.get("dynamic_value_preservation_json", {})
if dvp:
imm = dvp.get("immediate_sell_ratio", 0)
reb = dvp.get("rebound_wait_ratio", 0)
if abs((imm + reb) - 1.0) > 0.001:
print(f"[FAIL] DVP ratios do not sum to 1.0 (imm={imm}, reb={reb})")
is_valid = False
else:
print(f"[PASS] DVP ratio sum is exactly 1.0 (imm={imm}, reb={reb})")
# LLM 자유도 0% 검증
trace = harness_context.get("routing_serving_trace_v2_json", {})
if trace:
budget = trace.get("llm_serving_budget")
if budget != 0:
print(f"[FAIL] llm_serving_budget is {budget}. MUST BE 0 to prevent hallucination.")
is_valid = False
else:
print("[PASS] llm_serving_budget is strictly locked to 0.")
print("=================================================")
return is_valid
def main() -> int:
if len(sys.argv) < 2:
print("Usage: python tools/apply_engine_upgrade_v7.py <input_json>")
return 1
payload = load_payload(Path(sys.argv[1]))
success = validate_harness_v7(payload)
if not success:
return 1
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,173 @@
from __future__ import annotations
import argparse
import json
import re
from collections import defaultdict
from pathlib import Path
from typing import Any
import yaml
ROOT = Path(__file__).resolve().parents[2]
SOURCE = ROOT / "spec" / "13_formula_registry.yaml"
def load_yaml(path: Path) -> dict[str, Any]:
return yaml.safe_load(path.read_text(encoding="utf-8")) or {}
def to_snake(name: str) -> str:
slug = re.sub(r"[^0-9A-Za-z]+", "_", name).strip("_").lower()
return slug or "formula"
def write_text(path: Path, text: str) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(text, encoding="utf-8")
def build_stub(formula_id: str, spec: dict[str, Any]) -> str:
inputs = spec.get("inputs") or []
outputs = spec.get("outputs") or spec.get("output_fields") or []
owner = spec.get("owner", "TODO_REQUIRED")
status = spec.get("status", "TODO_REQUIRED")
input_fields = [item.get("field") for item in inputs if isinstance(item, dict) and item.get("field")]
return (
f'"""Auto-generated formula stub for {formula_id}."""\n'
f"\n"
f"FORMULA_ID = {formula_id!r}\n"
f"FORMULA_OWNER = {owner!r}\n"
f"FORMULA_STATUS = {status!r}\n"
f"FORMULA_INPUT_FIELDS = {input_fields!r}\n"
f"FORMULA_OUTPUT_FIELDS = {outputs!r}\n"
f"\n"
f"def execute(inputs: dict[str, object]) -> dict[str, object]:\n"
f" raise NotImplementedError({formula_id!r} + ' is a generated stub.')\n"
)
def build_golden_test(formula_id: str, spec: dict[str, Any]) -> str:
slug = to_snake(formula_id)
outputs = spec.get("outputs") or spec.get("output_fields") or []
return (
f'"""Auto-generated golden test stub for {formula_id}."""\n'
f"\n"
f"def test_{slug}_golden_stub_exists() -> None:\n"
f" assert {formula_id!r}\n"
f"\n"
f"def test_{slug}_declares_outputs() -> None:\n"
f" outputs = {outputs!r}\n"
f" assert isinstance(outputs, list)\n"
f" assert outputs\n"
)
def build_schema_fragment(formula_id: str, spec: dict[str, Any]) -> dict[str, Any]:
inputs = spec.get("inputs") or []
outputs = spec.get("outputs") or spec.get("output_fields") or []
return {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": f"schema://formula/{formula_id}",
"title": formula_id,
"type": "object",
"properties": {
"formula_id": {"const": formula_id},
"owner": {"type": "string"},
"status": {"type": "string"},
"inputs": {"type": "array", "items": {"type": "string"}},
"outputs": {"type": "array", "items": {"type": "string"}},
},
"required": ["formula_id", "owner", "status", "inputs", "outputs"],
"x_formula_inputs": [
item.get("field")
for item in inputs
if isinstance(item, dict) and item.get("field")
],
"x_formula_outputs": outputs,
}
def main() -> int:
parser = argparse.ArgumentParser(description="Compile formula registry stubs and artifacts.")
parser.add_argument("--dry-run", action="store_true", help="Validate inputs without writing files.")
parser.add_argument("--out-report", default=str(ROOT / "Temp" / "formula_compile_report_v1.json"))
parser.add_argument("--out-graph", default=str(ROOT / "Temp" / "formula_dependency_graph_v1.json"))
args = parser.parse_args()
source = load_yaml(SOURCE)
formulas = (source.get("formula_registry") or {}).get("formulas") or {}
if not isinstance(formulas, dict):
raise TypeError("formula_registry.formulas must be a mapping")
runtime_dir = ROOT / "runtime" / "python" / "core" / "formulas" / "generated"
golden_dir = ROOT / "tests" / "golden" / "generated"
schema_dir = ROOT / "schemas" / "generated"
output_field_map: dict[str, set[str]] = defaultdict(set)
for formula_id, spec in formulas.items():
if not isinstance(spec, dict):
continue
outputs = spec.get("outputs") or spec.get("output_fields") or []
for field in outputs:
if isinstance(field, str):
output_field_map[field].add(formula_id)
dependency_graph: dict[str, list[str]] = {}
generated_count = 0
active_count = 0
for formula_id in sorted(formulas):
spec = formulas[formula_id] or {}
status = str(spec.get("status", "active")).lower()
if status not in {"deprecated", "removed"}:
active_count += 1
stub_name = f"{to_snake(formula_id)}.py"
golden_name = f"{to_snake(formula_id)}_golden.py"
schema_name = f"{to_snake(formula_id)}.schema.json"
input_fields = [
item.get("field")
for item in (spec.get("inputs") or [])
if isinstance(item, dict) and item.get("field")
]
dependencies = sorted(
{
producer
for field in input_fields
for producer in output_field_map.get(field, set())
if producer != formula_id
}
)
dependency_graph[formula_id] = dependencies
if not args.dry_run:
write_text(runtime_dir / stub_name, build_stub(formula_id, spec))
write_text(golden_dir / golden_name, build_golden_test(formula_id, spec))
write_text(schema_dir / schema_name, json.dumps(build_schema_fragment(formula_id, spec), ensure_ascii=False, indent=2) + "\n")
generated_count += 1
report = {
"source": str(SOURCE.relative_to(ROOT)),
"formula_count": len(formulas),
"active_formula_count": active_count,
"generated_stub_count": generated_count,
"golden_stub_count": generated_count,
"schema_fragment_count": generated_count,
"dependency_graph_node_count": len(dependency_graph),
"status": "OK",
}
if not args.dry_run:
write_text(Path(args.out_report), json.dumps(report, ensure_ascii=False, indent=2) + "\n")
write_text(Path(args.out_graph), json.dumps(dependency_graph, ensure_ascii=False, indent=2) + "\n")
for package_dir in (runtime_dir, golden_dir, schema_dir):
init_file = package_dir / "__init__.py"
if not init_file.exists():
write_text(init_file, '"""Auto-generated package."""\n')
print(json.dumps(report, ensure_ascii=False, indent=2))
return 0
if __name__ == "__main__":
raise SystemExit(main())
+812
View File
@@ -0,0 +1,812 @@
#!/usr/bin/env python3
"""
compute_formula_outputs.py
───────────────────────────────────────────────────────────────────────────────
Python 공식 계산 엔진
GAS가 아직 구현하지 않은 핵심 공식을 Python으로 결정론적으로 계산한다.
동일 입력 → 동일 출력. 텍스트 판단 없음. 수치만 출력.
계산 대상:
- VELOCITY_V1 : velocity_1d, velocity_5d
- PROFIT_LOCK_STAGE : profit_pct → 단계 분류
- ANTI_CHASING_VELOCITY_V1 : velocity_1d 기반 뒷박 차단
- PULLBACK_ENTRY_TRIGGER_V1 : MA20 기반 눌림목 기준가
- SELL_PRICE_SANITY_V1 : 매도가 역전·비현실가 검증
- TICK_NORMALIZER_V1 : KRX 호가 단위 정규화
- CASH_RECOVERY_OPTIMIZER_V1 : 최소 주식가치 훼손 매도조합
- PROFIT_RATCHET_TIERED_V2 : APEX_SUPER ATR×1.2 trailing
사용법:
python tools/compute_formula_outputs.py [GatherTradingData.json]
python tools/compute_formula_outputs.py --output computed_harness.json
"""
from __future__ import annotations
import json
import math
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
if str(ROOT) not in sys.path:
sys.path.insert(0, str(ROOT))
from src.quant_engine.exit_decisions import (
compute_cash_shortfall_harness as _compute_cash_shortfall_harness,
compute_dynamic_heat_thresholds as _compute_dynamic_heat_thresholds,
compute_final_decision as _compute_final_decision,
compute_sell_decision as _compute_sell_decision,
compute_timing_decision as _compute_timing_decision,
compute_stop_action_ladder as _compute_stop_action_ladder,
compute_stop_price_core as _compute_stop_price_core,
normalize_tick as _normalize_tick,
)
# Windows cp949 터미널 호환
if sys.stdout.encoding and sys.stdout.encoding.lower() not in ("utf-8", "utf8"):
sys.stdout = open(sys.stdout.fileno(), mode="w", encoding="utf-8", buffering=1)
if sys.stderr.encoding and sys.stderr.encoding.lower() not in ("utf-8", "utf8"):
sys.stderr = open(sys.stderr.fileno(), mode="w", encoding="utf-8", buffering=1)
SEP = "=" * 70
# ─────────────────────────────────────────────────────────────────────────────
# KRX 호가 단위 테이블 (TICK_NORMALIZER_V1)
# ─────────────────────────────────────────────────────────────────────────────
KRX_TICK_TABLE = [
( 2_000, 1),
( 5_000, 5),
( 20_000, 10),
( 50_000, 50),
( 200_000, 100),
( 500_000, 500),
(math.inf, 1000),
]
def krx_tick_unit(price: float) -> int:
for threshold, tick in KRX_TICK_TABLE:
if price < threshold:
return tick
return 1000
def normalize_tick(price: float) -> int:
return _normalize_tick(price)
# ─────────────────────────────────────────────────────────────────────────────
# PROFIT_LOCK_STAGE 분류기
# ─────────────────────────────────────────────────────────────────────────────
def classify_profit_lock_stage(profit_pct: float) -> str:
if profit_pct >= 60:
return "APEX_SUPER"
elif profit_pct >= 40:
return "APEX_TRAILING"
elif profit_pct >= 30:
return "PROFIT_LOCK_30"
elif profit_pct >= 20:
return "PROFIT_LOCK_20"
elif profit_pct >= 10:
return "PROFIT_LOCK_10"
elif profit_pct >= 0:
return "BREAKEVEN_RATCHET"
else:
return "NORMAL"
# ─────────────────────────────────────────────────────────────────────────────
# PROFIT_RATCHET_TIERED_V2
# ─────────────────────────────────────────────────────────────────────────────
def compute_trailing_stop_v2(
profit_pct: float,
highest_close: float,
atr20: float,
ratchet_stop: float | None,
average_cost: float,
) -> dict:
stage = classify_profit_lock_stage(profit_pct)
ratchet_stop = ratchet_stop or average_cost
if stage == "APEX_SUPER":
raw = highest_close - 1.2 * atr20
trailing_stop = normalize_tick(max(ratchet_stop, raw))
tp_action = "강제 10% 익절 권고"
elif stage == "APEX_TRAILING":
raw = highest_close - 1.5 * atr20
trailing_stop = normalize_tick(max(ratchet_stop, raw))
tp_action = "부분익절 검토"
elif stage in ("PROFIT_LOCK_30", "PROFIT_LOCK_20"):
raw = highest_close - 2.0 * atr20
trailing_stop = normalize_tick(max(ratchet_stop, raw))
tp_action = "래칫 유지"
else:
trailing_stop = None
tp_action = "적용 안함"
return {
"ratchet_stage_v2": stage,
"auto_trailing_stop_v2": trailing_stop,
"tp_ladder_action": tp_action,
"apex_super_active": stage == "APEX_SUPER",
}
def compute_stop_price_core(entry_price: float | None, atr20: float | None, current_price: float | None) -> dict:
return _compute_stop_price_core(entry_price, atr20, current_price)
def compute_stop_action_ladder(context: dict) -> dict:
return _compute_stop_action_ladder(context)
def compute_dynamic_heat_thresholds(regime: str) -> dict:
return _compute_dynamic_heat_thresholds(regime)
def compute_cash_shortfall_harness(as_result: dict, total_asset: float, cash_floor_info: dict, mrs_score: float) -> dict:
return _compute_cash_shortfall_harness(as_result, total_asset, cash_floor_info, mrs_score)
def compute_timing_decision(ctx: dict) -> dict:
return _compute_timing_decision(ctx)
def compute_sell_decision(ctx: dict) -> dict:
return _compute_sell_decision(ctx)
def compute_final_decision(ctx: dict) -> dict:
return _compute_final_decision(ctx)
# ─────────────────────────────────────────────────────────────────────────────
# ANTI_CHASING_VELOCITY_V1
# ─────────────────────────────────────────────────────────────────────────────
def compute_anti_chasing(velocity_1d: float) -> dict:
if velocity_1d >= 3.0:
verdict = "BLOCK_CHASE"
status = "BLOCKED"
elif velocity_1d >= 1.5:
verdict = "PULLBACK_WAIT"
status = "WAIT"
else:
verdict = "CLEAR"
status = "PASS"
return {
"anti_chasing_verdict": verdict,
"anti_chasing_velocity_status": status,
"velocity_1d_input": round(velocity_1d, 4),
}
# ─────────────────────────────────────────────────────────────────────────────
# PULLBACK_ENTRY_TRIGGER_V1
# ─────────────────────────────────────────────────────────────────────────────
def compute_pullback_trigger(close: float, ma20: float, atr20: float) -> dict:
trigger_price = normalize_tick(ma20 - 0.5 * atr20)
upper_band = ma20 * 1.03
if close <= upper_band:
verdict = "PULLBACK_ZONE"
state = "PASS"
else:
verdict = "ABOVE_PULLBACK_ZONE"
state = "BLOCKED"
return {
"pullback_entry_verdict": verdict,
"pullback_state": state,
"pullback_entry_trigger_price": trigger_price,
"pullback_upper_band": normalize_tick(upper_band),
}
# ─────────────────────────────────────────────────────────────────────────────
# SELL_PRICE_SANITY_V1
# ─────────────────────────────────────────────────────────────────────────────
def check_sell_price_sanity(
sell_limit_price: float,
stop_loss_price: float | None,
prev_close: float,
ticker: str = "",
) -> dict:
issues: list[str] = []
status = "PASS"
if stop_loss_price is not None and sell_limit_price < stop_loss_price:
issues.append(
f"INVALID_PRICE_INVERSION: sell={sell_limit_price:,} < stop={stop_loss_price:,}"
)
status = "INVALID_PRICE_INVERSION"
upper_limit = prev_close * 1.30
if sell_limit_price > upper_limit:
issues.append(
f"INVALID_UNREALISTIC_PRICE: sell={sell_limit_price:,} > prev_close*1.30={upper_limit:,.0f}"
)
if status == "PASS":
status = "INVALID_UNREALISTIC_PRICE"
tick_unit = krx_tick_unit(sell_limit_price)
if sell_limit_price % tick_unit != 0:
corrected = normalize_tick(sell_limit_price)
issues.append(
f"INVALID_TICK: sell={sell_limit_price:,} 호가단위={tick_unit}원 → 정규화={corrected:,}"
)
if status == "PASS":
status = "INVALID_TICK"
return {
"sell_price_sanity_status": status,
"sell_price_sanity_issues": issues,
"hts_allowed": status == "PASS",
"shadow_ledger": status != "PASS",
"ticker": ticker,
}
# ─────────────────────────────────────────────────────────────────────────────
# CASH_RECOVERY_OPTIMIZER_V1
# ─────────────────────────────────────────────────────────────────────────────
def compute_cash_recovery_optimizer(
sell_candidates: list[dict], # sorted by h2_priority_rank asc
cash_shortfall_min_krw: float,
) -> dict:
plan: list[dict] = []
cumulative_krw = 0.0
for cand in sell_candidates:
if cumulative_krw >= cash_shortfall_min_krw:
break
ticker = cand.get("Ticker", "")
name = cand.get("Name", "")
qty = cand.get("Sell_Qty") or 0
limit_price = cand.get("Sell_Limit_Price") or cand.get("current_price", 0)
preserve_ratio = cand.get("Cash_Preserve_Ratio", 100)
style = cand.get("Cash_Preserve_Style", "FULL")
if qty and limit_price:
expected_krw = qty * limit_price * (preserve_ratio / 100)
else:
expected_krw = 0
plan.append({
"ticker": ticker,
"name": name,
"qty": qty,
"limit_price": normalize_tick(limit_price) if limit_price else None,
"preserve_style": style,
"preserve_ratio": preserve_ratio,
"expected_krw": round(expected_krw),
})
cumulative_krw += expected_krw
shortfall_met = cumulative_krw >= cash_shortfall_min_krw
return {
"cash_recovery_plan_json": {
"sell_sequence": plan,
"expected_total_krw": round(cumulative_krw),
"cash_shortfall_min_krw": cash_shortfall_min_krw,
"shortfall_met": shortfall_met,
"items_needed": len(plan),
}
}
# ─────────────────────────────────────────────────────────────────────────────
# 메인 계산 루틴
# ─────────────────────────────────────────────────────────────────────────────
def main() -> int:
output_path: Path | None = None
args = sys.argv[1:]
if "--output" in args:
idx = args.index("--output")
output_path = Path(args[idx + 1])
args = [a for i, a in enumerate(args) if i != idx and i != idx + 1]
json_path = Path(args[0]) if args else ROOT / "GatherTradingData.json"
if not json_path.exists():
print(f"[ERROR] {json_path} not found")
return 1
raw = json.loads(json_path.read_text(encoding="utf-8"))
hc = None
try:
hc = raw["data"]["_harness_context"]
except (KeyError, TypeError):
pass
if not isinstance(hc, dict):
hc = {}
account_snapshot = raw.get("data", {}).get("account_snapshot", []) or []
sell_priority = raw.get("data", {}).get("sell_priority", []) or []
core_satellite = raw.get("data", {}).get("core_satellite", []) or []
# ── TOTAL ASSET CALCULATION ─────────────────────────────────────────────
# Sum market_value of all holdings + available_cash (if present in context)
holdings_value = sum(float(r.get("market_value", 0) or 0) for r in account_snapshot if r.get("market_value"))
# Try to find cash in snapshot or context
cash_d2 = float(hc.get("settlement_cash_d2_krw") or hc.get("available_cash") or 0)
total_asset = holdings_value + cash_d2
hc["total_asset_krw"] = round(total_asset)
hc["total_asset"] = round(total_asset)
regime = str(hc.get("market_regime", "NEUTRAL"))
# ticker → account row lookup
acct_map: dict[str, dict] = {
row["ticker"]: row
for row in account_snapshot
if row.get("ticker")
}
# ticker → core_satellite row lookup
cs_map: dict[str, dict] = {
row["Ticker"]: row
for row in core_satellite
if row.get("Ticker")
}
computed: dict[str, object] = {}
per_ticker: list[dict] = []
cash_shortfall = hc.get("cash_shortfall_min_krw", 0) or 0
for sp_row in sell_priority:
ticker = sp_row.get("Ticker", "")
if not ticker:
continue
acct = acct_map.get(ticker, {})
cs = cs_map.get(ticker, {})
close = cs.get("Close") or acct.get("current_price") or 0
prev_close = cs.get("PrevClose") or close
ma20 = cs.get("MA20") or close
atr20 = cs.get("ATR20") or 0
avg_cost = acct.get("average_cost") or 0
qty_held = acct.get("holding_quantity") or 0
highest = acct.get("highest_price_since_entry") or close
stop_price = acct.get("stop_price")
sell_limit = sp_row.get("Sell_Limit_Price") or 0
ret5d = cs.get("Ret5D") or 0
# ── VELOCITY ────────────────────────────────────────────────────────
velocity_1d = ((close - prev_close) / prev_close * 100) if prev_close else 0
velocity_5d = float(ret5d) if ret5d else 0
# ── PROFIT PCT & STAGE ──────────────────────────────────────────────
profit_pct = ((close - avg_cost) / avg_cost * 100) if avg_cost else (
acct.get("return_pct") or 0
)
profit_lock = classify_profit_lock_stage(profit_pct)
# ── RATCHET V2 ──────────────────────────────────────────────────────
ratchet = compute_trailing_stop_v2(
profit_pct=profit_pct,
highest_close=highest,
atr20=atr20,
ratchet_stop=stop_price,
average_cost=avg_cost,
)
# ── ANTI_CHASING ────────────────────────────────────────────────────
anti_chase = compute_anti_chasing(velocity_1d)
# ── PULLBACK TRIGGER ─────────────────────────────────────────────────
pullback = compute_pullback_trigger(close, ma20, atr20)
# ── SELL PRICE SANITY ────────────────────────────────────────────────
sanity = {}
if sell_limit:
sanity = check_sell_price_sanity(
sell_limit_price=sell_limit,
stop_loss_price=stop_price,
prev_close=prev_close or close,
ticker=ticker,
)
# ── SELL DECISION ───────────────────────────────────────────────────
sell_ctx = {
"close": close,
"stopPrice": stop_price,
"trailingStop": ratchet.get("auto_trailing_stop_v2"),
"tp1Price": sp_row.get("TP1_Price"),
"tp2Price": sp_row.get("TP2_Price"),
"profitPct": profit_pct,
"rwPartial": sp_row.get("RW_Partial"),
"timingExitScore": sp_row.get("Timing_Score_Exit"),
"daysToTimeStop": sp_row.get("Days_To_Time_Stop"),
"timingAction": sp_row.get("Timing_Action"),
"regime": regime,
"atr20": atr20,
}
r_sell = compute_sell_decision(sell_ctx)
# ── FINAL DECISION ──────────────────────────────────────────────────
decision_ctx = {
"sellAction": r_sell.get("action"),
"sellValidation": r_sell.get("validation"),
"allowedAction": sp_row.get("Allowed_Action"),
"timingAction": sp_row.get("Timing_Action"),
"timingScoreEntry": sp_row.get("Timing_Score_Entry"),
"timingExitScore": sp_row.get("Timing_Score_Exit"),
"ss001Total": sp_row.get("SS001_Total"),
"flowCredit": sp_row.get("Flow_Credit"),
"leaderTotal": sp_row.get("Leader_Scan_Total"),
"rwPartial": sp_row.get("RW_Partial"),
"profitPct": profit_pct,
"daysToTimeStop": sp_row.get("Days_To_Time_Stop"),
"weightPct": sp_row.get("Weight_Pct"),
"acGate": sp_row.get("AC_Gate"),
"liquidityStatus": sp_row.get("Liquidity_Status"),
"spreadStatus": sp_row.get("Spread_Status"),
"dartRisk": bool(sp_row.get("DART_Risk")),
"missingFields": sp_row.get("Missing_Fields"),
}
r_final = compute_final_decision(decision_ctx)
row_result = {
"ticker": ticker,
"name": sp_row.get("Name", ""),
"close": close,
"prev_close": prev_close,
"ma20": ma20,
"atr20": atr20,
"avg_cost": avg_cost,
"profit_pct": round(profit_pct, 2),
"velocity_1d": round(velocity_1d, 4),
"velocity_5d": round(velocity_5d, 4),
"profit_lock_stage": profit_lock,
**ratchet,
**anti_chase,
**pullback,
**(sanity if sanity else {}),
**r_sell,
**r_final,
}
per_ticker.append(row_result)
# ── ORDER BLUEPRINT GENERATION ──────────────────────────────────────────
blueprint_rows = []
for r in per_ticker:
if r.get("final_action") == "SELL_READY":
blueprint_rows.append({
"ticker": r["ticker"],
"name": r["name"],
"order_type": r.get("order_type", "LIMIT_SELL"),
"limit_price": r.get("limit_price"),
"quantity": int(acct_map.get(r["ticker"], {}).get("holding_quantity", 0) * (r.get("ratio_pct", 0) / 100)),
"validation_status": "PASS",
"rationale_code": r.get("reason"),
"formula_id": "ORDER_BLUEPRINT_V1",
})
computed["order_blueprint_json"] = blueprint_rows
# ── CASH_RECOVERY_OPTIMIZER ─────────────────────────────────────────────
if cash_shortfall > 0:
recovery = compute_cash_recovery_optimizer(sell_priority, cash_shortfall)
computed.update(recovery)
computed["per_ticker"] = per_ticker
computed["meta"] = {
"source_file": str(json_path.name),
"computed_by": "tools/compute_formula_outputs.py",
"formulas_run": [
"VELOCITY_V1", "PROFIT_LOCK_STAGE_V1", "PROFIT_RATCHET_TIERED_V2",
"ANTI_CHASING_VELOCITY_V1", "PULLBACK_ENTRY_TRIGGER_V1",
"SELL_PRICE_SANITY_V1", "CASH_RECOVERY_OPTIMIZER_V1",
],
"deterministic": True,
"llm_computed": False,
}
# ── 출력 ────────────────────────────────────────────────────────────────
print(SEP)
print(" Python 공식 계산 엔진 — compute_formula_outputs")
print(f" 소스: {json_path.name} | 현금부족: {cash_shortfall:,.0f}")
print(SEP)
print(f"\n{'ticker':<10} {'종목명':<14} {'수익률%':>7} {'단계':<20} "
f"{'velocity_1d%':>12} {'뒷박판정':<15} {'trailing_stop':>14}")
print("-" * 100)
for r in per_ticker:
ts = r.get("auto_trailing_stop_v2") or r.get("auto_trailing_stop_v2")
ts_str = f"{ts:>14,}" if ts else f"{'(없음)':>14}"
print(
f"{r['ticker']:<10} {r['name']:<14} {r['profit_pct']:>7.1f}% "
f"{r['profit_lock_stage']:<20} {r['velocity_1d']:>12.2f}% "
f"{r['anti_chasing_verdict']:<15} {ts_str}"
)
print()
# 매도가 역전 경보
invalid_prices = [r for r in per_ticker if r.get("sell_price_sanity_status", "PASS") != "PASS"]
if invalid_prices:
print(f"[!] SELL_PRICE_SANITY 경보 — {len(invalid_prices)}개 종목 HTS 입력 차단")
for r in invalid_prices:
for issue in r.get("sell_price_sanity_issues", []):
print(f" {r['ticker']} {r['name']}: {issue}")
else:
print("[✔] SELL_PRICE_SANITY — 전 종목 가격 정상")
# 현금회복 계획
crp = computed.get("cash_recovery_plan_json")
if crp:
print(f"\n[현금회복 최적 매도조합] 부족분 {cash_shortfall:,.0f}")
print(f" {'#':<3} {'ticker':<10} {'종목명':<14} {'수량':>6} {'지정가':>10} {'예상회수':>12}")
print(" " + "-" * 58)
for i, s in enumerate(crp.get("sell_sequence", []), 1):
lp = s.get("limit_price") or 0
ek = s.get("expected_krw") or 0
print(f" {i:<3} {s['ticker']:<10} {s['name']:<14} {s.get('qty', 0):>6} "
f"{lp:>10,} {ek:>12,}")
met_str = "✔ 달성" if crp.get("shortfall_met") else "✗ 부족"
print(f" 합계: {crp.get('expected_total_krw', 0):>12,}원 ({met_str})")
# ── JSON 파일 저장 ─────────────────────────────────────────────────────
if output_path is None:
output_path = ROOT / "Temp" / "computed_harness.json"
output_path.parent.mkdir(parents=True, exist_ok=True)
output_path.write_text(
json.dumps(computed, ensure_ascii=False, indent=2), encoding="utf-8"
)
print(f"\n → 결과 저장: {output_path}")
print(f" → 결정론적 계산 완료. LLM 추정 없음.\n")
return 0
# ─────────────────────────────────────────────────────────────────────────────
# IMPUTED_DATA_EXPOSURE_GATE_V1
# weighted_coverage = Σ(weight × coverage); ifr = 1 - wc; ech = raw × (0.4 + 0.6 × wc)
# gate: ifr ≥ 0.50 → BLOCK / ≥ 0.25 → WARN / else PASS
# ─────────────────────────────────────────────────────────────────────────────
_IDEG_WEIGHTS = {"fundamental_core": 0.30, "realized_outcome": 0.30,
"trade_quality": 0.15, "pattern": 0.10, "alpha_eval": 0.15}
def compute_imputed_data_exposure(domain_coverage: dict, raw_cap: float) -> dict:
wc = sum(_IDEG_WEIGHTS.get(k, 0) * v for k, v in domain_coverage.items())
ifr = round(1.0 - wc, 4)
ech = round(raw_cap * (0.4 + 0.6 * wc), 1)
gate = "IMPUTED_DATA_BLOCK" if ifr >= 0.50 else ("IMPUTED_DATA_WARN" if ifr >= 0.25 else "PASS")
return {"gate_status": gate, "imputed_field_ratio": round(ifr, 4),
"weighted_coverage": round(wc, 4), "effective_confidence_honest": ech}
# ─────────────────────────────────────────────────────────────────────────────
# TRAILING_STOP_PRICE_V1
# trailing_stop = highest_price - atr20 × multiplier
# ─────────────────────────────────────────────────────────────────────────────
def compute_trailing_stop_price(highest_price_since_entry: float, atr20: float,
trailing_atr_multiplier: float) -> dict:
return {"trailing_stop_price": round(highest_price_since_entry - atr20 * trailing_atr_multiplier)}
# ─────────────────────────────────────────────────────────────────────────────
# EXPECTED_EDGE_V1
# edge = ((tp - entry) / (entry - stop)) × confidence
# ─────────────────────────────────────────────────────────────────────────────
def compute_expected_edge(target_price: float, entry_price: float,
stop_price: float, bayesian_confidence: float) -> dict:
r_multiple = (target_price - entry_price) / (entry_price - stop_price) if (entry_price - stop_price) != 0 else 0
edge = round(r_multiple * bayesian_confidence, 4)
return {"expected_edge": edge, "r_multiple": round(r_multiple, 4)}
# ─────────────────────────────────────────────────────────────────────────────
# TP_VALIDITY_CHECK_V1
# tp_price > current_price → valid; else null (triggered)
# ─────────────────────────────────────────────────────────────────────────────
def compute_tp_validity(tp_price: float | None, current_price: float) -> dict:
if tp_price is None:
return {"tp_validated_price": None, "tp_state": "UNKNOWN_NO_CLOSE"}
if tp_price > current_price:
return {"tp_validated_price": tp_price, "tp_state": "PENDING"}
return {"tp_validated_price": None, "tp_state": "TP1_ALREADY_TRIGGERED"}
# ─────────────────────────────────────────────────────────────────────────────
# RS_RATIO_V1
# rs_ratio = stock_5d_return / kospi_5d_return
# ─────────────────────────────────────────────────────────────────────────────
def compute_rs_ratio(stock_close_5d_return: float, kospi_close_5d_return: float) -> dict:
if kospi_close_5d_return == 0:
return {"rs_ratio": None, "note": "kospi_return=0"}
return {"rs_ratio": round(stock_close_5d_return / kospi_close_5d_return, 4)}
# ─────────────────────────────────────────────────────────────────────────────
# RATCHET_TRAILING_AUTO_V1
# PROFIT_LOCK_20: max(ratchet_stop, highest_close - 1.5×ATR20)
# PROFIT_LOCK_30/APEX: max(ratchet_stop, highest_close - 2.0×ATR20)
# Others: null
# ─────────────────────────────────────────────────────────────────────────────
def compute_ratchet_trailing_auto(profit_lock_stage: str, ratchet_stop: float,
highest_close: float, atr20: float) -> dict:
_mult = {"PROFIT_LOCK_20": 1.5, "PROFIT_LOCK_30": 2.0, "APEX_TRAILING": 2.0}
m = _mult.get(profit_lock_stage)
if m is None:
return {"auto_trailing_stop": None, "note": f"{profit_lock_stage}: no trailing"}
atr_stop = highest_close - m * atr20
result = max(ratchet_stop, atr_stop)
return {"auto_trailing_stop": round(result), "atr_stop": round(atr_stop),
"ratchet_stop": ratchet_stop, "multiplier": m}
# ─────────────────────────────────────────────────────────────────────────────
# STOP_PRICE_CORE_V1
# max(entry × 0.92, entry - atr20 × multiplier)
# ─────────────────────────────────────────────────────────────────────────────
def compute_stop_price_core(entry_price: float, atr20: float, atr_multiplier: float) -> dict:
return _compute_stop_price_core(entry_price, atr20, atr_multiplier=atr_multiplier)
# ─────────────────────────────────────────────────────────────────────────────
# TARGET_CASH_PCT_V1
# max(5 + (mrs/10)×15, cash_floor_regime_min_pct)
# ─────────────────────────────────────────────────────────────────────────────
def compute_target_cash_pct(market_risk_score: float, cash_floor_regime_min_pct: float) -> dict:
formula_result = 5 + (market_risk_score / 10) * 15
target = max(formula_result, cash_floor_regime_min_pct)
return {"target_cash_pct": round(target, 2), "formula_result": round(formula_result, 2)}
# ─────────────────────────────────────────────────────────────────────────────
# FLOW_CREDIT_V1
# C1×0.30 + C2×0.30 + C3×0.40
# ─────────────────────────────────────────────────────────────────────────────
def compute_flow_credit(c1_price_action: float, c2_volume_action: float,
c3_flow_action: float) -> dict:
credit = round(c1_price_action * 0.30 + c2_volume_action * 0.30 + c3_flow_action * 0.40, 4)
return {"flow_credit": credit}
# ─────────────────────────────────────────────────────────────────────────────
# MARKET_RISK_SCORE_V1
# min(10, vix + kospi + usd_krw + usd_jpy + credit)
# ─────────────────────────────────────────────────────────────────────────────
def compute_market_risk_score(vix_score: float, kospi_score: float, usd_krw_score: float,
usd_jpy_score: float, credit_score: float) -> dict:
raw = vix_score + kospi_score + usd_krw_score + usd_jpy_score + credit_score
return {"market_risk_score": min(10, raw), "raw_sum": raw}
# ─────────────────────────────────────────────────────────────────────────────
# PORTFOLIO_BETA_V1
# beta = Σ(beta_i × mv_i) / total_equity
# ─────────────────────────────────────────────────────────────────────────────
def compute_portfolio_beta(holdings: list, total_equity_value: float) -> dict:
if total_equity_value <= 0:
return {"portfolio_beta": None}
weighted = sum(h.get("beta", 0) * h.get("market_value", 0) for h in holdings
if h.get("beta") is not None)
return {"portfolio_beta": round(weighted / total_equity_value, 4)}
# ─────────────────────────────────────────────────────────────────────────────
# RISK_BUDGET_CASCADE_V1
# base × feedback_mult × brake_mult
# ─────────────────────────────────────────────────────────────────────────────
def compute_risk_budget_cascade(base_risk_budget: float, net_return_feedback_multiplier: float,
performance_brake_multiplier: float) -> dict:
result = base_risk_budget * net_return_feedback_multiplier * performance_brake_multiplier
return {"risk_budget": round(result, 6)}
# ─────────────────────────────────────────────────────────────────────────────
# MEAN_REVERSION_GATE_V1
# deviation_ratio = close / ma20; gate by ratio
# ─────────────────────────────────────────────────────────────────────────────
def compute_mean_reversion_gate(close_price: float, ma20: float) -> dict:
if ma20 <= 0:
return {"deviation_ratio": None, "gate": "DATA_MISSING"}
ratio = round(close_price / ma20, 4)
gate = "OVEREXTENDED" if ratio >= 1.10 else ("NORMAL" if ratio >= 0.95 else "OVERSOLD")
return {"deviation_ratio": ratio, "gate": gate}
# ─────────────────────────────────────────────────────────────────────────────
# T1_FORCED_SELL_RISK_V1
# min(100, sell_action_active*40 + timing_exit_ge_50*25 + rw_ge_2*25 + dist_ge_70*30)
# ─────────────────────────────────────────────────────────────────────────────
def compute_t1_forced_sell_risk(sell_action_active: int, timing_exit_ge_50: int,
rw_ge_2: int, distribution_ge_70: int) -> dict:
score = min(100, sell_action_active * 40 + timing_exit_ge_50 * 25 +
rw_ge_2 * 25 + distribution_ge_70 * 30)
gate = "HIGH_SELL_RISK" if score >= 70 else ("MODERATE" if score >= 40 else "LOW")
return {"t1_forced_sell_risk_score": score, "gate": gate}
# ─────────────────────────────────────────────────────────────────────────────
# SELL_CONFLICT_AWARE_RECOMMENDATION_V1
# min(100, sell_signal_active*55 + cash_preserve_active*20 + no_add_gate*20)
# ─────────────────────────────────────────────────────────────────────────────
def compute_sell_conflict_recommendation(sell_signal_active: int, cash_preserve_active: int,
no_add_gate: int) -> dict:
score = min(100, sell_signal_active * 55 + cash_preserve_active * 20 + no_add_gate * 20)
recommendation = "SELL_PRIORITY" if score >= 55 else ("REDUCE" if score >= 20 else "HOLD")
return {"conflict_score": score, "recommendation": recommendation}
# ─────────────────────────────────────────────────────────────────────────────
# DIVERGENCE_SCORE_V1
# divergence = price_above_ma20 × (frg_sell×0.40 + inst_sell×0.35 + vol_surge×0.25)
# ─────────────────────────────────────────────────────────────────────────────
def compute_divergence_score(price_above_ma20: int, foreign_net_sell: float,
institution_net_sell: float, vol_surge: float) -> dict:
score = price_above_ma20 * (foreign_net_sell * 0.40 + institution_net_sell * 0.35 +
vol_surge * 0.25)
score = round(min(100, max(-100, score)), 2)
gate = "DISTRIBUTION_SIGNAL" if score >= 50 else ("NEUTRAL" if score >= 0 else "ACCUMULATION")
return {"divergence_score": score, "gate": gate}
# ─────────────────────────────────────────────────────────────────────────────
# SEMICONDUCTOR_CLUSTER_SYNC_V1
# is_mandatory = cluster_pct > cluster_limit_pct × 2
# ─────────────────────────────────────────────────────────────────────────────
def compute_semiconductor_cluster_sync(cluster_pct: float, cluster_limit_pct: float) -> dict:
is_mandatory = cluster_pct > cluster_limit_pct * 2
ratio = round(cluster_pct / cluster_limit_pct, 3) if cluster_limit_pct > 0 else None
gate = "MANDATORY_REDUCE" if is_mandatory else "PASS"
return {"is_mandatory": is_mandatory, "cluster_ratio": ratio, "gate": gate}
# ─────────────────────────────────────────────────────────────────────────────
# GOAL_RETIREMENT_V1
# achievement_pct = round(total_asset / GOAL_KRW * 1000) / 10
# goal_remaining_krw = max(0, GOAL_KRW - total_asset)
# ─────────────────────────────────────────────────────────────────────────────
def compute_goal_retirement(total_asset_krw: float, goal_krw: float = 500_000_000) -> dict:
achievement_pct = round(total_asset_krw / goal_krw * 1000) / 10
remaining_krw = max(0.0, goal_krw - total_asset_krw)
status = "ACHIEVED" if achievement_pct >= 100 else "IN_PROGRESS"
return {
"goal_achievement_pct": achievement_pct,
"goal_remaining_krw": round(remaining_krw),
"goal_status": status,
}
# ─────────────────────────────────────────────────────────────────────────────
# VELOCITY_1D_V1 (renamed from VELOCITY_V1 for clarity)
# velocity = (close - prev_close) / prev_close × 100
# ─────────────────────────────────────────────────────────────────────────────
def compute_velocity_1d(close: float, prev_close: float) -> dict:
if prev_close <= 0:
return {"velocity_1d": None}
v = round((close - prev_close) / prev_close * 100, 4)
return {"velocity_1d": v}
# ─────────────────────────────────────────────────────────────────────────────
# POSITION_SIZE_V1
# position_size = min(atr_qty, cash_limit_qty, weight_limit_qty, sector_limit_qty)
# atr_qty = floor(risk_budget_krw / (atr20 × atr_mult × close))
# ─────────────────────────────────────────────────────────────────────────────
def compute_position_size(risk_budget_krw: float, atr20: float, atr_mult: float,
close: float, cash_limit_qty: int,
weight_limit_qty: int, sector_limit_qty: int) -> dict:
import math
if atr20 <= 0 or close <= 0 or atr_mult <= 0:
return {"position_size_qty": 0, "binding_constraint": "DATA_MISSING"}
atr_qty = math.floor(risk_budget_krw / (atr20 * atr_mult * close))
constraints = {
"atr": atr_qty, "cash": cash_limit_qty,
"weight": weight_limit_qty, "sector": sector_limit_qty
}
min_val = min(constraints.values())
binding = [k for k, v in constraints.items() if v == min_val][0]
return {"position_size_qty": max(0, min_val), "atr_qty": atr_qty,
"binding_constraint": binding, "constraints": constraints}
if __name__ == "__main__":
raise SystemExit(main())
File diff suppressed because it is too large Load Diff
+564
View File
@@ -0,0 +1,564 @@
from __future__ import annotations
import math
from typing import Any
KRX_TICK_TABLE: tuple[tuple[float, int], ...] = (
(2_000, 1),
(5_000, 5),
(20_000, 10),
(50_000, 50),
(200_000, 100),
(500_000, 500),
(math.inf, 1000),
)
def krx_tick_unit(price: float) -> int:
for threshold, tick in KRX_TICK_TABLE:
if price < threshold:
return tick
return 1000
def normalize_tick(price: float) -> int:
tick = krx_tick_unit(price)
return int(math.floor(price / tick) * tick)
def compute_stop_price_core(
entry_price: float | None,
atr20: float | None,
current_price: float | None = None,
atr_multiplier: float | None = None,
) -> dict[str, Any]:
if entry_price is None:
return {
"stop_price": None,
"stop_price_status": "NO_STOP_PRICE",
"data_missing": ["entry_price"],
}
if atr20 is None and atr_multiplier is None:
return {
"stop_price": entry_price * 0.92,
"stop_price_status": "DATA_MISSING — 하네스 업데이트 필요",
"data_missing": ["atr20"],
}
if atr_multiplier is None and current_price in (None, 0):
return {
"stop_price": entry_price * 0.92,
"stop_price_status": "DATA_MISSING — 하네스 업데이트 필요",
"data_missing": [name for name, value in (("atr20", atr20), ("current_price", current_price)) if value in (None, 0)],
}
if atr_multiplier is None:
atr20_pct = (atr20 / current_price) * 100
atr_multiplier = 2.0 if atr20_pct >= 8 else 1.5
else:
atr20_pct = (atr20 / current_price) * 100 if current_price not in (None, 0) else None
return {
"stop_price": max(entry_price * 0.92, entry_price - atr20 * atr_multiplier),
"stop_price_status": "PASS",
"atr20_pct": atr20_pct,
"atr_multiplier": atr_multiplier,
}
def compute_stop_action_ladder(context: dict[str, Any]) -> dict[str, Any]:
timing_action = str(context.get("timingAction") or context.get("timing_action") or "").upper()
rw_partial = int(context.get("rw_partial") or 0)
rw_partial_excluding_rw2b = int(context.get("rw_partial_excluding_rw2b") or 0)
regime_prelim = str(context.get("REGIME_PRELIM") or context.get("regime_prelim") or "").upper()
timing_exit_score = float(context.get("timingExitScore") or context.get("timing_exit_score") or 0.0)
profit_pct = float(context.get("profitPct") or context.get("profit_pct") or 0.0)
days_to_time_stop = int(context.get("daysToTimeStop") or context.get("days_to_time_stop") or 9999)
trailing_stop_breach = bool(context.get("trailingStopBreach") or context.get("trailing_stop_breach") or False)
rw2b_fast_track = bool(context.get("RW2b_5d_rapid_weakness") or context.get("rw2b_5d_rapid_weakness") or False)
if timing_action == "STOP_OR_TIME_EXIT_READY" or rw_partial >= 4:
return {
"action": "EXIT_100",
"reason": "STOP_OR_TIME_EXIT_READY" if timing_action == "STOP_OR_TIME_EXIT_READY" else "RW_EXIT_STRONG",
"quantity_pct": 100,
"priority": 1,
}
if regime_prelim in {"RISK_OFF", "RISK_OFF_CANDIDATE"}:
return {
"action": "REGIME_TRIM_50",
"reason": "REGIME_RISK_OFF" if regime_prelim == "RISK_OFF" else "REGIME_RISK_OFF_CANDIDATE",
"quantity_pct": 50,
"priority": 2,
}
if rw2b_fast_track and rw_partial_excluding_rw2b >= 1:
return {
"action": "TRIM_50",
"reason": "RW2B_FAST_TRACK",
"quantity_pct": 50,
"priority": 2.5,
}
if rw_partial >= 3 or timing_exit_score >= 75:
return {
"action": "TRIM_70",
"reason": "RW_EXIT" if rw_partial >= 3 else "TIMING_EXIT_SCORE",
"quantity_pct": 70,
"priority": 3,
}
if trailing_stop_breach or rw_partial >= 2 or (rw_partial >= 1 and timing_exit_score >= 50):
return {
"action": "TRIM_50",
"reason": "TRAILING_STOP_BREACH" if trailing_stop_breach else "RW_OR_TIMING_EXIT",
"quantity_pct": 50,
"priority": 4,
}
if profit_pct >= 10:
return {
"action": "TAKE_PROFIT_TIER1",
"reason": "PROFIT_PCT_THRESHOLD",
"quantity_pct": 25,
"priority": 5,
}
if days_to_time_stop <= 0:
return {
"action": "TIME_EXIT_100",
"reason": "TIME_STOP_EXPIRED",
"quantity_pct": 100,
"priority": 6,
}
return {
"action": "REVIEW_HUMAN",
"reason": "NO_FORCED_EXIT",
"quantity_pct": 0,
"priority": 7,
}
def compute_dynamic_heat_thresholds(regime: str | None) -> dict[str, float]:
r = str(regime or "").upper()
if "EVENT_SHOCK" in r:
return {"hardBlock": 5.0, "halve": 3.5}
if "RISK_OFF" in r:
return {"hardBlock": 7.0, "halve": 5.0}
if "SECULAR_LEADER" in r and "RISK_ON" in r:
return {"hardBlock": 13.0, "halve": 9.0}
if "RISK_ON" in r:
return {"hardBlock": 12.0, "halve": 8.5}
return {"hardBlock": 10.0, "halve": 7.0}
def compute_cash_shortfall_harness(
as_result: dict[str, Any],
total_asset: float,
cash_floor_info: dict[str, Any],
mrs_score: float,
) -> dict[str, Any]:
asset = total_asset if math.isfinite(total_asset) and total_asset > 0 else 0.0
d2_krw = float(as_result.get("settlementCashD2Krw") or 0.0)
min_pct = float(cash_floor_info.get("minPct") or 0.0)
target_cash_pct = max(5 + (mrs_score / 10) * 15, min_pct)
return {
"cash_current_pct_d2": round((d2_krw / asset * 100), 2) if asset > 0 else 0,
"cash_target_pct": target_cash_pct,
"cash_shortfall_min_krw": max(0, round(asset * min_pct / 100 - d2_krw)),
"cash_shortfall_target_krw": max(0, round(asset * target_cash_pct / 100 - d2_krw)),
}
def compute_timing_decision(ctx: dict[str, Any]) -> dict[str, Any]:
reasons: list[str] = []
entry_score = 0
exit_score = 0
entry_gate = str(ctx.get("entryModeGate") or "")
entry_mode = str(ctx.get("entryMode") or "")
leader_gate = str(ctx.get("leaderGate") or "")
ac_gate = str(ctx.get("acGate") or "")
exit_signal = str(ctx.get("exitSignalDetail") or "")
flow_credit = ctx.get("flowCredit")
leader_total = ctx.get("leaderTotal")
rw_partial = ctx.get("rwPartial")
rsi14 = ctx.get("rsi14")
disparity = ctx.get("disparity")
ma20_slope = ctx.get("ma20Slope")
spread_pct = ctx.get("spreadPct")
avg_trade_value_5d = ctx.get("avgTradeValue5D")
profit_pct = ctx.get("profitPct")
days_to_time_stop = ctx.get("daysToTimeStop")
if entry_gate == "PASS":
entry_score += 25
reasons.append(f"entry_{entry_mode}")
elif entry_gate == "BLOCK":
entry_score -= 25
reasons.append("entry_block")
if isinstance(leader_total, (int, float)) and math.isfinite(float(leader_total)):
if leader_total >= 4:
entry_score += 20
reasons.append("leader_scan>=4")
elif leader_total >= 3:
entry_score += 10
reasons.append("leader_watch")
if leader_gate in {"PASS", "EXPLORE_CANDIDATE"}:
entry_score += 10
if isinstance(flow_credit, (int, float)) and math.isfinite(float(flow_credit)):
if flow_credit >= 0.7:
entry_score += 20
reasons.append("flow_strong")
elif flow_credit >= 0.4:
entry_score += 10
reasons.append("flow_partial")
if ac_gate == "CLEAR":
entry_score += 15
reasons.append("anti_climax_clear")
elif ac_gate == "CAUTION":
entry_score += 5
reasons.append("anti_climax_caution")
elif ac_gate == "BLOCK":
entry_score -= 35
exit_score += 15
reasons.append("anti_climax_block")
if isinstance(ma20_slope, (int, float)) and math.isfinite(float(ma20_slope)):
if ma20_slope > 0:
entry_score += 8
else:
entry_score -= 8
exit_score += 8
reasons.append("ma20_down")
if isinstance(disparity, (int, float)) and math.isfinite(float(disparity)):
if -5 <= disparity <= 4:
entry_score += 10
elif 4 < disparity <= 8:
entry_score += 5
elif disparity > 12:
entry_score -= 25
exit_score += 20
reasons.append("overextended")
elif disparity < -10:
entry_score -= 10
exit_score += 10
reasons.append("trend_damage")
if isinstance(rsi14, (int, float)) and math.isfinite(float(rsi14)):
if 40 <= rsi14 <= 65:
entry_score += 10
elif 65 < rsi14 <= 72:
entry_score += 4
elif rsi14 > 75:
entry_score -= 25
exit_score += 20
reasons.append("rsi_overbought")
elif rsi14 < 35:
entry_score -= 5
exit_score += 8
reasons.append("weak_rsi")
if (
isinstance(avg_trade_value_5d, (int, float))
and math.isfinite(float(avg_trade_value_5d))
and avg_trade_value_5d >= 50
and (not isinstance(spread_pct, (int, float)) or not math.isfinite(float(spread_pct)) or spread_pct <= 0.8)
):
entry_score += 10
else:
entry_score -= 15
reasons.append("liquidity_or_spread_fail")
if isinstance(rw_partial, (int, float)) and math.isfinite(float(rw_partial)):
exit_score += min(100, max(0, int(rw_partial) * 25))
if exit_signal:
exit_score += len([part for part in exit_signal.split("|") if part]) * 10
if isinstance(days_to_time_stop, (int, float)) and 0 <= float(days_to_time_stop) <= 7:
exit_score += 20
reasons.append("time_stop_near")
if isinstance(profit_pct, (int, float)) and profit_pct >= 10:
exit_score += 15
reasons.append("profit_protect_zone")
entry_score = max(0, min(100, round(entry_score)))
exit_score = max(0, min(100, round(exit_score)))
action = "HOLD_NO_TIMING_EDGE"
atr20 = ctx.get("atr20")
price_status = str(ctx.get("priceStatus") or "")
if price_status != "PRICE_OK" or not isinstance(atr20, (int, float)) or not math.isfinite(float(atr20)):
action = "OBSERVE_DATA_MISSING"
elif exit_score >= 75 or (isinstance(rw_partial, (int, float)) and rw_partial >= 4):
action = "STOP_OR_TIME_EXIT_READY"
elif exit_score >= 50 or (isinstance(rw_partial, (int, float)) and rw_partial >= 3):
action = "EXIT_REVIEW"
elif entry_gate == "BLOCK" or ac_gate == "BLOCK" or entry_mode == "OVERBOUGHT":
action = "NO_BUY_OVERHEATED"
elif entry_score >= 75 and entry_gate == "PASS" and isinstance(leader_total, (int, float)) and leader_total >= 4:
action = "BUY_BREAKOUT_PILOT_ONLY" if entry_mode == "BREAKOUT" else "BUY_STAGE1_READY"
elif entry_score >= 60 and entry_gate == "PASS":
action = "BUY_BREAKOUT_PILOT_ONLY" if entry_mode == "BREAKOUT" else "BUY_PULLBACK_WAIT"
elif (
(isinstance(leader_total, (int, float)) and leader_total >= 3)
or (isinstance(flow_credit, (int, float)) and flow_credit >= 0.4)
):
action = "WATCH_TIMING_SETUP"
return {
"entry_score": entry_score,
"exit_score": exit_score,
"action": action,
"reason": "|".join(reasons[:6]),
}
def compute_sell_decision(ctx: dict[str, Any]) -> dict[str, Any]:
close = ctx.get("close")
stop_price = ctx.get("stopPrice")
trailing_stop = ctx.get("trailingStop")
tp1_price = ctx.get("tp1Price")
tp2_price = ctx.get("tp2Price")
profit_pct = ctx.get("profitPct")
rw_partial = ctx.get("rwPartial")
timing_exit_score = ctx.get("timingExitScore")
days_to_time_stop = ctx.get("daysToTimeStop")
timing_action = str(ctx.get("timingAction") or "")
regime = str(ctx.get("regime") or "")
atr20 = ctx.get("atr20")
close_f = float(close) if isinstance(close, (int, float)) else float("nan")
stop_f = float(stop_price) if isinstance(stop_price, (int, float)) else float("nan")
trailing_f = float(trailing_stop) if isinstance(trailing_stop, (int, float)) else float("nan")
tp1_f = float(tp1_price) if isinstance(tp1_price, (int, float)) else float("nan")
tp2_f = float(tp2_price) if isinstance(tp2_price, (int, float)) else float("nan")
profit_f = float(profit_pct) if isinstance(profit_pct, (int, float)) else float("nan")
rw_f = float(rw_partial) if isinstance(rw_partial, (int, float)) else float("nan")
timing_exit_f = float(timing_exit_score) if isinstance(timing_exit_score, (int, float)) else float("nan")
days_f = float(days_to_time_stop) if isinstance(days_to_time_stop, (int, float)) else float("nan")
atr_f = float(atr20) if isinstance(atr20, (int, float)) else float("nan")
action = "HOLD"
ratio = 0
reason = ""
price: Any = ""
price_source = ""
price_basis = ""
execution_window = ""
order_type = ""
stop_candidate = trailing_f if math.isfinite(trailing_f) and trailing_f > 0 else stop_f
if not (math.isfinite(stop_candidate) and stop_candidate > 0) and math.isfinite(close_f) and close_f > 0:
stop_candidate = close_f * 0.995
protective_limit = round(min(close_f * 0.995, stop_candidate if math.isfinite(stop_candidate) else close_f * 0.995)) if math.isfinite(close_f) and close_f > 0 else ""
atr_buffer = atr_f * 0.3 if math.isfinite(atr_f) and atr_f > 0 else (close_f * 0.005 if math.isfinite(close_f) else 0)
close_protect_limit = round(close_f - atr_buffer) if math.isfinite(close_f) and close_f > 0 else ""
if timing_action == "STOP_OR_TIME_EXIT_READY" or (math.isfinite(rw_f) and rw_f >= 4):
action = "EXIT_100"
ratio = 100
reason = "RW_EXIT_STRONG" if math.isfinite(rw_f) and rw_f >= 4 else "STOP_OR_TIME_EXIT_READY"
price = protective_limit
price_source = "TRAILING_STOP" if math.isfinite(trailing_f) else "STOP_OR_CLOSE"
price_basis = "TRAILING_STOP_TRIGGER" if math.isfinite(trailing_f) else "STOP_OR_CLOSE_PROTECT"
execution_window = "INTRADAY_ON_TRIGGER"
order_type = "PROTECTIVE_LIMIT_SELL"
elif math.isfinite(rw_f) and rw_f >= 3 or (math.isfinite(timing_exit_f) and timing_exit_f >= 75):
action = "TRIM_70"
ratio = 70
reason = "RW_EXIT" if math.isfinite(rw_f) and rw_f >= 3 else "TIMING_EXIT_SCORE"
price = protective_limit
price_source = "RISK_REDUCTION"
price_basis = "RISK_REDUCTION_CLOSE_PROTECT"
execution_window = "INTRADAY_AFTER_09_30"
order_type = "PROTECTIVE_LIMIT_SELL"
elif math.isfinite(trailing_f) and trailing_f > 0 and math.isfinite(close_f) and close_f <= trailing_f:
action = "TRAILING_STOP_BREACH"
ratio = 70
reason = "TRAILING_STOP_PRICE_BREACH"
price = round(trailing_f)
price_source = "TRAILING_STOP_PRICE"
price_basis = "TRAILING_STOP_TRIGGER"
execution_window = "INTRADAY_ON_TRIGGER"
order_type = "PROTECTIVE_LIMIT_SELL"
elif (math.isfinite(rw_f) and rw_f >= 2) or (math.isfinite(rw_f) and rw_f >= 1 and math.isfinite(timing_exit_f) and timing_exit_f >= 50):
action = "TRIM_50"
ratio = 50
reason = "RW_REVIEW" if math.isfinite(rw_f) and rw_f >= 2 else "TIMING_EXIT_REVIEW"
price = close_protect_limit
price_source = "RELATIVE_WEAKNESS_CLOSE"
price_basis = "PRIOR_CLOSE_X_0.998"
execution_window = "INTRADAY_AFTER_09_30"
order_type = "LIMIT_SELL"
elif math.isfinite(rw_f) and rw_f >= 1 and math.isfinite(timing_exit_f) and timing_exit_f >= 30:
action = "TRIM_33"
ratio = 33
reason = "RW_EARLY_WARNING"
price = close_protect_limit
price_source = "EARLY_WARNING_CLOSE"
price_basis = "PRIOR_CLOSE_X_0.998"
execution_window = "INTRADAY_AFTER_09_30"
order_type = "LIMIT_SELL"
elif math.isfinite(rw_f) and rw_f >= 1:
action = "TRIM_25"
ratio = 25
reason = "RW_SIGNAL_ONLY"
price = close_protect_limit
price_source = "SIGNAL_ONLY_CLOSE"
price_basis = "PRIOR_CLOSE_X_0.998"
execution_window = "CLOSE_REVIEW_OR_NEXT_OPEN"
order_type = "LIMIT_SELL"
elif math.isfinite(profit_f) and profit_f >= 50:
action = "PROFIT_TRIM_50"
ratio = 50
reason = "PROFIT_PROTECT_50"
price = round(tp2_f) if math.isfinite(tp2_f) and tp2_f > 0 else close_protect_limit
price_source = "TP2_PRICE" if math.isfinite(tp2_f) else "CLOSE_PROFIT_PROTECT"
price_basis = "TAKE_PROFIT_TIER2_PRICE" if math.isfinite(tp2_f) else "PRIOR_CLOSE_X_0.998"
execution_window = "INTRADAY_LIMIT_OR_CLOSE_REVIEW"
order_type = "LIMIT_SELL"
elif math.isfinite(profit_f) and profit_f >= 30:
action = "PROFIT_TRIM_35"
ratio = 35
reason = "PROFIT_PROTECT_30"
price = round(tp2_f) if math.isfinite(tp2_f) and tp2_f > 0 else close_protect_limit
price_source = "TP2_PRICE" if math.isfinite(tp2_f) else "CLOSE_PROFIT_PROTECT"
price_basis = "TAKE_PROFIT_TIER2_PRICE" if math.isfinite(tp2_f) else "PRIOR_CLOSE_X_0.998"
execution_window = "INTRADAY_LIMIT_OR_CLOSE_REVIEW"
order_type = "LIMIT_SELL"
elif math.isfinite(profit_f) and profit_f >= 20:
action = "PROFIT_TRIM_25"
ratio = 25
reason = "PROFIT_PROTECT_20"
price = round(tp1_f) if math.isfinite(tp1_f) and tp1_f > 0 else close_protect_limit
price_source = "TP1_PRICE" if math.isfinite(tp1_f) else "CLOSE_PROFIT_PROTECT"
price_basis = "TAKE_PROFIT_TIER1_PRICE" if math.isfinite(tp1_f) else "PRIOR_CLOSE_X_0.998"
execution_window = "INTRADAY_LIMIT_OR_CLOSE_REVIEW"
order_type = "LIMIT_SELL"
elif math.isfinite(profit_f) and profit_f >= 10:
action = "TAKE_PROFIT_TIER1"
ratio = 25
reason = "TP1_PROFIT_10PCT"
price = round(tp1_f) if math.isfinite(tp1_f) and tp1_f > 0 else close_protect_limit
price_source = "TP1_PRICE" if math.isfinite(tp1_f) else "CLOSE_PROFIT_PROTECT"
price_basis = "TAKE_PROFIT_TIER1_PRICE" if math.isfinite(tp1_f) else "PRIOR_CLOSE_X_0.998"
execution_window = "INTRADAY_LIMIT_OR_CLOSE_REVIEW"
order_type = "LIMIT_SELL"
elif math.isfinite(days_f) and days_f <= 0:
action = "TIME_EXIT_100"
ratio = 100
reason = "TIME_STOP_EXPIRED"
price = protective_limit
price_source = "TIME_STOP_CLOSE"
price_basis = "TIME_STOP_CLOSE_PROTECT"
execution_window = "CLOSE_REVIEW_OR_NEXT_OPEN"
order_type = "PROTECTIVE_LIMIT_SELL"
elif math.isfinite(days_f) and days_f <= 7:
action = "TIME_TRIM_50"
ratio = 50
reason = "TIME_STOP_NEAR"
price = close_protect_limit
price_source = "TIME_STOP_NEAR_CLOSE"
price_basis = "ATR_PROTECT_LIMIT"
execution_window = "CLOSE_REVIEW_OR_NEXT_OPEN"
order_type = "LIMIT_SELL"
elif math.isfinite(days_f) and days_f <= 14:
action = "TIME_TRIM_25"
ratio = 25
reason = "TIME_STOP_APPROACHING"
price = close_protect_limit
price_source = "TIME_STOP_APPROACHING_CLOSE"
price_basis = "ATR_PROTECT_LIMIT"
execution_window = "CLOSE_REVIEW_OR_NEXT_OPEN"
order_type = "LIMIT_SELL"
validation = "NO_SELL_ACTION"
if action != "HOLD":
validation = "SIGNAL_CONFIRMED" if isinstance(price, (int, float)) and float(price) > 0 else "NO_SELL_PRICE"
return {
"action": action,
"ratio_pct": ratio,
"limit_price": price,
"price_source": price_source,
"price_basis": price_basis,
"execution_window": execution_window,
"order_type": order_type,
"reason": reason,
"validation": validation,
"cash_preserve_style": "",
"cash_preserve_ratio": 0,
"cash_preserve_reason": [],
}
def compute_final_decision(ctx: dict[str, Any]) -> dict[str, Any]:
sell_action = str(ctx.get("sellAction") or "HOLD")
sell_validation = str(ctx.get("sellValidation") or "")
allowed_action = str(ctx.get("allowedAction") or "")
timing_action = str(ctx.get("timingAction") or "")
timing_entry = ctx.get("timingScoreEntry")
timing_exit = ctx.get("timingScoreExit")
ss001_total = ctx.get("ss001Total")
flow_credit = ctx.get("flowCredit")
leader_total = ctx.get("leaderTotal")
rw_partial = ctx.get("rwPartial")
profit_pct = ctx.get("profitPct")
days_to_time_stop = ctx.get("daysToTimeStop")
weight_pct = ctx.get("weightPct")
ac_gate = str(ctx.get("acGate") or "")
liquidity_status = str(ctx.get("liquidityStatus") or "")
spread_status = str(ctx.get("spreadStatus") or "")
dart_risk = bool(ctx.get("dartRisk"))
missing_fields = str(ctx.get("missingFields") or "")
final_action = "HOLD"
action_priority = 99
decision_source = "RULE_ENGINE"
if sell_action != "HOLD" and sell_validation == "SIGNAL_CONFIRMED":
final_action = "SELL_READY"
action_priority = 10
elif allowed_action == "EXIT_SIGNAL" or timing_action == "STOP_OR_TIME_EXIT_READY":
final_action = "EXIT_SIGNAL"
action_priority = 28
elif allowed_action == "REVIEW_EXIT" or timing_action == "EXIT_REVIEW":
final_action = "EXIT_REVIEW"
action_priority = 32
elif timing_action == "NO_BUY_OVERHEATED" and not dart_risk:
final_action = "NO_BUY_OVERHEATED"
action_priority = 50
elif allowed_action == "BUY_STAGE1_READY" or timing_action == "BUY_STAGE1_READY":
final_action = "BUY_STAGE1_READY"
action_priority = 60
elif allowed_action == "BUY_BREAKOUT_PILOT_ONLY" or timing_action == "BUY_BREAKOUT_PILOT_ONLY":
final_action = "BUY_BREAKOUT_PILOT_ONLY"
action_priority = 70
elif allowed_action == "BUY_PULLBACK_WAIT" or timing_action == "BUY_PULLBACK_WAIT":
final_action = "BUY_PULLBACK_WAIT"
action_priority = 80
elif allowed_action == "WATCH_CANDIDATE":
final_action = "WATCH_TIMING_SETUP"
action_priority = 90
if missing_fields:
decision_source = "RULE_ENGINE_WITH_MISSING_DATA"
def _finite(value: Any) -> bool:
return isinstance(value, (int, float)) and math.isfinite(float(value))
time_stop_urgency = max(0, 20 - min(20, float(days_to_time_stop) * 3)) if _finite(days_to_time_stop) and days_to_time_stop >= 0 else 0
overweight_penalty = 15 if _finite(weight_pct) and weight_pct > 7 else 0
overheat_penalty = 30 if ac_gate == "BLOCK" else 10 if ac_gate == "CAUTION" else 0
liquidity_penalty = 15 if liquidity_status in {"LOW", "DATA_MISSING"} or spread_status in {"BLOCK", "WIDE", "QUOTE_NO_MATCH"} else 0
if action_priority <= 40:
priority_score = (float(timing_exit) if _finite(timing_exit) else 0) * 0.35 + (float(rw_partial) if _finite(rw_partial) else 0) * 15 + max(0, float(profit_pct) if _finite(profit_pct) else 0) * 0.30 + time_stop_urgency + overweight_penalty
elif 50 <= action_priority <= 80:
priority_score = (float(timing_entry) if _finite(timing_entry) else 0) * 0.35 + (float(ss001_total) if _finite(ss001_total) else 0) * 0.30 + (float(flow_credit) if _finite(flow_credit) else 0) * 20 + (float(leader_total) if _finite(leader_total) else 0) * 5 - overheat_penalty - liquidity_penalty
else:
priority_score = (float(timing_entry) if _finite(timing_entry) else 0) * 0.20 + (float(timing_exit) if _finite(timing_exit) else 0) * 0.20 + (float(flow_credit) if _finite(flow_credit) else 0) * 10
return {
"final_action": final_action,
"action_priority": action_priority,
"priority_score": float(max(0, priority_score)),
"decision_source": decision_source,
}
@@ -0,0 +1,120 @@
"""generate_models_from_schema.py — schema model generation
Mirrors schemas/generated/*.schema.json into src/quant_engine/models/generated
as lightweight Python model descriptors.
"""
from __future__ import annotations
import argparse
import json
from pathlib import Path
from typing import Any
ROOT = Path(__file__).resolve().parents[2]
SCHEMA_DIR = ROOT / "schemas" / "generated"
OUT_DIR = ROOT / "src" / "quant_engine" / "models" / "generated"
def load_json(path: Path) -> dict[str, Any]:
try:
obj = json.loads(path.read_text(encoding="utf-8"))
except Exception:
return {}
return obj if isinstance(obj, dict) else {}
def to_module_name(path: Path) -> str:
return path.stem.replace(".", "_").lower()
def render_module(schema_path: Path, schema: dict[str, Any]) -> str:
title = str(schema.get("title") or schema_path.stem)
schema_id = str(schema.get("$id") or f"schema://{title}")
props = schema.get("properties") if isinstance(schema.get("properties"), dict) else {}
required = schema.get("required") if isinstance(schema.get("required"), list) else []
prop_names = list(props.keys())
return (
'"""Auto-generated schema model descriptor."""\n'
"from __future__ import annotations\n\n"
"from dataclasses import dataclass\n"
"import json\n"
"from pathlib import Path\n"
"from typing import Any\n\n"
f"SCHEMA_TITLE = {title!r}\n"
f"SCHEMA_ID = {schema_id!r}\n"
f"SCHEMA_PATH = {str(schema_path.relative_to(ROOT)).replace('\\', '/')!r}\n"
f"SCHEMA_PROPERTIES = {prop_names!r}\n"
f"SCHEMA_REQUIRED = {required!r}\n\n"
"@dataclass(frozen=True)\n"
"class SchemaModel:\n"
" title: str\n"
" schema_id: str\n"
" path: str\n"
" properties: list[str]\n"
" required: list[str]\n\n"
"def load_schema() -> dict[str, Any]:\n"
" return json.loads(Path(__file__).with_suffix('.schema.json').read_text(encoding='utf-8'))\n\n"
"def describe() -> SchemaModel:\n"
" return SchemaModel(\n"
" title=SCHEMA_TITLE,\n"
" schema_id=SCHEMA_ID,\n"
" path=SCHEMA_PATH,\n"
" properties=list(SCHEMA_PROPERTIES),\n"
" required=list(SCHEMA_REQUIRED),\n"
" )\n"
)
def write_text(path: Path, text: str) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(text, encoding="utf-8")
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--schemas", default=str(SCHEMA_DIR))
parser.add_argument("--out", default=str(OUT_DIR))
parser.add_argument("--report", default=str(ROOT / "Temp" / "schema_model_generation_v1.json"))
args = parser.parse_args()
schema_dir = Path(args.schemas)
out_dir = Path(args.out)
schema_files = sorted(schema_dir.glob("*.schema.json"))
modules = []
for schema_file in schema_files:
schema = load_json(schema_file)
module_name = to_module_name(schema_file)
modules.append(module_name)
write_text(out_dir / f"{module_name}.py", render_module(schema_file, schema))
write_text(out_dir / f"{module_name}.schema.json", json.dumps(schema, ensure_ascii=False, indent=2) + "\n")
write_text(out_dir / "__init__.py", '"""Auto-generated schema model package."""\n')
write_text(
out_dir.parent / "__init__.py",
'"""Auto-generated quant_engine.models package."""\n',
)
write_text(
out_dir.parent.parent / "__init__.py",
'"""Canonical quant_engine package."""\n',
)
write_text(
out_dir.parent.parent.parent / "__init__.py",
'"""Canonical src package."""\n',
)
report = {
"status": "OK",
"schema_count": len(schema_files),
"generated_module_count": len(modules),
"package_root": "src/quant_engine/models/generated",
"report_path": str(Path(args.report).relative_to(ROOT)),
}
write_text(Path(args.report), json.dumps(report, ensure_ascii=False, indent=2) + "\n")
print(json.dumps(report, ensure_ascii=False, indent=2))
return 0
if __name__ == "__main__":
raise SystemExit(main())
+234
View File
@@ -0,0 +1,234 @@
from __future__ import annotations
import argparse
import csv
import datetime as dt
import re
from pathlib import Path
from typing import Any
import openpyxl
ROOT = Path(__file__).resolve().parents[2]
DEFAULT_XLSX = ROOT / "GatherTradingData.xlsx"
OUTPUT_HEADERS = [
"ETF_Ticker",
"ETF_Name",
"Close",
"NAV",
"iNAV",
"Premium_Discount_Pct",
"Tracking_Error",
"AUM",
"Source_Date",
"Source",
"Enabled",
"Note",
]
COLUMN_ALIASES = {
"ticker": ["ETF_Ticker", "종목코드", "단축코드", "표준코드", "code", "ticker"],
"name": ["ETF_Name", "종목명", "한글종목명", "Name", "name"],
"close": ["Close", "종가", "현재가", "시장가격", "TDD_CLSPRC", "close"],
"nav": ["NAV", "순자산가치", "기준가격", "기준가", "NAV(원)", "nav"],
"inav": ["iNAV", "추정순자산가치", "실시간기준가", "iNAV(원)", "inav"],
"premium_discount_pct": ["Premium_Discount_Pct", "괴리율", "괴리율(%)", "가격괴리율", "premium_discount_pct"],
"tracking_error": ["Tracking_Error", "추적오차율", "추적오차", "추적오차율(%)", "tracking_error"],
"aum": ["AUM", "순자산총액", "순자산총액(원)", "상장좌수", "aum"],
"source_date": ["Source_Date", "기준일", "일자", "거래일자", "Date", "date"],
}
def normalize_header(value: Any) -> str:
return re.sub(r"\s+", "", str(value or "").strip()).lower()
def normalize_ticker(value: Any) -> str:
text = str(value or "").strip()
if text.endswith(".0"):
text = text[:-2]
text = re.sub(r"[^0-9A-Za-z]", "", text)
if text.isdigit():
return text.zfill(6)
if re.fullmatch(r"[0-9A-Za-z]{1,6}", text):
return text.zfill(6)
return text
def parse_number(value: Any) -> float | None:
if value in (None, ""):
return None
if isinstance(value, (int, float)) and not isinstance(value, bool):
return float(value)
text = str(value).strip()
if not text or text in {"-", "N/A", "nan"}:
return None
text = text.replace(",", "").replace("%", "")
try:
return float(text)
except ValueError:
return None
def parse_date(value: Any) -> str:
if value in (None, ""):
return ""
if isinstance(value, (dt.datetime, dt.date)):
return value.strftime("%Y-%m-%d")
text = str(value).strip()
match = re.search(r"(\d{4})[./-]?(\d{1,2})[./-]?(\d{1,2})", text)
if not match:
return ""
y, m, d = match.groups()
return f"{y}-{int(m):02d}-{int(d):02d}"
def read_source_table(path: Path) -> list[dict[str, Any]]:
if path.suffix.lower() in {".xlsx", ".xlsm"}:
wb = openpyxl.load_workbook(path, data_only=True, read_only=True)
ws = wb[wb.sheetnames[0]]
rows = list(ws.iter_rows(values_only=True))
header_row_idx = 0
best_score = -1
alias_tokens = {normalize_header(a) for aliases in COLUMN_ALIASES.values() for a in aliases}
for i, row in enumerate(rows[:20]):
score = sum(1 for cell in row if normalize_header(cell) in alias_tokens)
if score > best_score:
best_score = score
header_row_idx = i
headers = [str(v or "").strip() for v in rows[header_row_idx]]
return [
dict(zip(headers, row))
for row in rows[header_row_idx + 1 :]
if row and any(v not in (None, "") for v in row)
]
encoding_candidates = ["utf-8-sig", "cp949", "euc-kr"]
last_error: Exception | None = None
for encoding in encoding_candidates:
try:
with path.open("r", encoding=encoding, newline="") as f:
sample = f.read(4096)
f.seek(0)
dialect = csv.Sniffer().sniff(sample, delimiters=",\t;")
return list(csv.DictReader(f, dialect=dialect))
except Exception as exc:
last_error = exc
raise RuntimeError(f"failed to read source file {path}: {last_error}")
def resolve_columns(rows: list[dict[str, Any]]) -> dict[str, str]:
if not rows:
return {}
source_headers = list(rows[0].keys())
normalized = {normalize_header(h): h for h in source_headers}
resolved: dict[str, str] = {}
for field, aliases in COLUMN_ALIASES.items():
for alias in aliases:
key = normalize_header(alias)
if key in normalized:
resolved[field] = normalized[key]
break
return resolved
def existing_etfs(wb: openpyxl.Workbook) -> dict[str, str]:
result: dict[str, str] = {}
if "etf_raw" in wb.sheetnames:
ws = wb["etf_raw"]
headers = [ws.cell(2, c).value for c in range(1, ws.max_column + 1)]
idx = {h: i + 1 for i, h in enumerate(headers) if h}
if "ETF_Ticker" in idx:
for r in range(3, ws.max_row + 1):
ticker = normalize_ticker(ws.cell(r, idx["ETF_Ticker"]).value)
if ticker:
result[ticker] = str(ws.cell(r, idx.get("ETF_Name", idx["ETF_Ticker"])).value or "")
return result
def update_workbook(workbook_path: Path, source_path: Path, enable: bool) -> tuple[int, int]:
rows = read_source_table(source_path)
columns = resolve_columns(rows)
if "ticker" not in columns:
raise RuntimeError(f"source file has no ticker/code column. resolved={columns}")
wb = openpyxl.load_workbook(workbook_path)
targets = existing_etfs(wb)
if "etf_nav_manual" in wb.sheetnames:
del wb["etf_nav_manual"]
insert_at = wb.sheetnames.index("etf_raw") + 1 if "etf_raw" in wb.sheetnames else 1
ws = wb.create_sheet("etf_nav_manual", insert_at)
ws.append([f"updated: imported from {source_path.name}"])
ws.append(OUTPUT_HEADERS)
imported = 0
matched = 0
seen: set[str] = set()
for row in rows:
ticker = normalize_ticker(row.get(columns["ticker"]))
if not ticker or ticker in seen:
continue
seen.add(ticker)
name = str(row.get(columns.get("name", ""), "") or targets.get(ticker, "")).strip()
close = parse_number(row.get(columns.get("close", "")))
nav = parse_number(row.get(columns.get("nav", "")))
inav = parse_number(row.get(columns.get("inav", "")))
premium = parse_number(row.get(columns.get("premium_discount_pct", "")))
if premium is None:
basis_nav = nav if nav and nav > 0 else inav
if close is not None and basis_nav and basis_nav > 0:
premium = ((close / basis_nav) - 1) * 100
tracking_error = parse_number(row.get(columns.get("tracking_error", "")))
aum = parse_number(row.get(columns.get("aum", "")))
source_date = parse_date(row.get(columns.get("source_date", "")))
is_match = not targets or ticker in targets
if is_match:
matched += 1
row_enable = "Y" if enable and is_match and (nav is not None or inav is not None) else "N"
ws.append([
ticker,
name,
close,
nav,
inav,
premium,
tracking_error,
aum,
source_date,
f"import:{source_path.name}",
row_enable,
"matched_etf_raw" if is_match else "not_in_etf_raw_review_before_enable",
])
imported += 1
for row in ws.iter_rows(min_row=1, max_row=ws.max_row):
row[0].number_format = "@"
for cell in ws[2]:
cell.font = openpyxl.styles.Font(bold=True, color="FFFFFF")
cell.fill = openpyxl.styles.PatternFill("solid", fgColor="7030A0")
ws.freeze_panes = "A3"
ws.auto_filter.ref = f"A2:L{ws.max_row}"
widths = [14, 34, 14, 14, 14, 20, 16, 16, 16, 28, 10, 42]
for i, width in enumerate(widths, 1):
ws.column_dimensions[openpyxl.utils.get_column_letter(i)].width = width
wb.save(workbook_path)
return imported, matched
def main() -> int:
parser = argparse.ArgumentParser(description="Import official ETF NAV/iNAV data into etf_nav_manual sheet.")
parser.add_argument("source", type=Path, help="KRX/KIND/issuer CSV or XLSX export")
parser.add_argument("--workbook", type=Path, default=DEFAULT_XLSX)
parser.add_argument("--enable", action="store_true", help="Set Enabled=Y for matched rows with NAV or iNAV")
args = parser.parse_args()
imported, matched = update_workbook(args.workbook, args.source, args.enable)
print(f"ETF NAV IMPORT OK: imported={imported} matched_etf_raw={matched} workbook={args.workbook.name}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
File diff suppressed because it is too large Load Diff
+156
View File
@@ -0,0 +1,156 @@
#!/usr/bin/env python3
"""
lib_trading_calendar.py
───────────────────────────────────────────────────────────────────────────────
KRX 거래일 기반 데이터 신선도 판정 모듈 (결정론)
배경: 한국 주식시장은 주말·공휴일 휴장한다. 금요일 종가 데이터는 토·일 내내
변하지 않으므로, 단순 "24시간 경과" SLA는 토요일 오후만 돼도 거짓 신선도 위반을
일으킨다. 신선도는 "캡처한 종가가 여전히 최신 종가인가"로 판정해야 한다.
핵심 규칙:
데이터가 STALE인 시점 = 캡처 시각 이후 도래하는 첫 거래일 개장(09:00 KST).
→ 금요일 15:35 캡처 → 다음 개장 = 월요일 09:00. 그 전까지 FRESH(페널티 0).
KST(UTC+9), KRX 정규장: 09:00 개장 / 15:30 마감.
공휴일: spec/krx_holidays.yaml 에서 로드. 없으면 주말만 비거래일.
"""
from __future__ import annotations
from datetime import datetime, timedelta, timezone, date, time
from pathlib import Path
import yaml
ROOT = Path(__file__).resolve().parents[2]
HOLIDAYS_PATH = ROOT / "spec" / "krx_holidays.yaml"
KST = timezone(timedelta(hours=9))
MARKET_OPEN_HOUR = 9 # 09:00 KST 개장
MARKET_OPEN_MINUTE = 0
MARKET_CLOSE_HOUR = 15 # 15:30 KST 마감
MARKET_CLOSE_MINUTE = 30
def _load_holidays() -> set[str]:
"""공휴일 YYYY-MM-DD 문자열 집합. 파일 없으면 빈 집합(주말만 적용)."""
if not HOLIDAYS_PATH.exists():
return set()
try:
data = yaml.safe_load(HOLIDAYS_PATH.read_text(encoding="utf-8")) or {}
hols = data.get("krx_market_holidays") or data.get("holidays") or []
result: set[str] = set()
for h in hols:
d = str(h.get("date") if isinstance(h, dict) else h or "").strip()
if d:
result.add(d[:10])
return result
except Exception:
return set()
_HOLIDAYS_CACHE: set[str] | None = None
def _holidays() -> set[str]:
global _HOLIDAYS_CACHE
if _HOLIDAYS_CACHE is None:
_HOLIDAYS_CACHE = _load_holidays()
return _HOLIDAYS_CACHE
def is_trading_day(d: date) -> bool:
"""거래일 여부 — 주말(토5/일6) 및 공휴일 제외."""
if d.weekday() >= 5: # 5=토, 6=일
return False
if d.isoformat() in _holidays():
return False
return True
def next_trading_day(d: date) -> date:
"""d 다음(d 제외) 첫 거래일."""
nxt = d + timedelta(days=1)
for _ in range(30): # 연속 휴장 한계 방어
if is_trading_day(nxt):
return nxt
nxt += timedelta(days=1)
return nxt
def market_open_dt(d: date) -> datetime:
"""거래일 d 의 개장 시각(KST aware)."""
return datetime.combine(d, time(MARKET_OPEN_HOUR, MARKET_OPEN_MINUTE), tzinfo=KST)
def next_market_open_after(dt_utc: datetime) -> datetime:
"""주어진 시각(UTC aware) 이후 도래하는 첫 거래일 개장 시각(KST aware).
예: 금요일 15:35 KST → 월요일 09:00 KST (토·일 건너뜀)
금요일 08:00 KST → 금요일 09:00 KST
월요일 10:00 KST → 화요일 09:00 KST
"""
dt_kst = dt_utc.astimezone(KST)
cur_date = dt_kst.date()
# 오늘이 거래일이고 아직 개장(09:00) 전이면 → 오늘 개장
if is_trading_day(cur_date):
today_open = market_open_dt(cur_date)
if dt_kst < today_open:
return today_open
nd = next_trading_day(cur_date)
return market_open_dt(nd)
def is_data_stale(captured_at_iso: str, now_utc: datetime | None = None) -> dict:
"""거래일 기반 데이터 신선도 판정.
Returns dict: stale(bool), captured_at_kst, stale_deadline_kst, now_kst,
hours_until_stale(양수=FRESH, 음수=STALE), reason.
"""
if now_utc is None:
now_utc = datetime.now(timezone.utc)
try:
dt = datetime.fromisoformat(captured_at_iso.replace("Z", "+00:00"))
if dt.tzinfo is None:
dt = dt.replace(tzinfo=timezone.utc)
except Exception:
return {
"stale": True,
"captured_at_kst": None,
"stale_deadline_kst": None,
"now_kst": now_utc.astimezone(KST).isoformat(),
"hours_until_stale": None,
"reason": "INVALID_CAPTURED_AT",
}
deadline = next_market_open_after(dt)
now_kst = now_utc.astimezone(KST)
stale = now_kst >= deadline
hours_until = (deadline - now_kst).total_seconds() / 3600.0
return {
"stale": stale,
"captured_at_kst": dt.astimezone(KST).isoformat(),
"stale_deadline_kst": deadline.isoformat(),
"now_kst": now_kst.isoformat(),
"hours_until_stale": round(hours_until, 2),
"reason": "STALE_NEW_SESSION_OPENED" if stale else "FRESH_WITHIN_TRADING_SESSION",
}
if __name__ == "__main__":
import sys
if hasattr(sys.stdout, "reconfigure"):
sys.stdout.reconfigure(encoding="utf-8")
# 셀프 테스트 (KST 기준: 금 15:35 = UTC 06:35)
cases = [
("2026-05-29T06:35:00+00:00", "2026-05-30T05:00:00+00:00", "금15:35캡처 → 토14:00 KST: FRESH 기대"),
("2026-05-29T06:35:00+00:00", "2026-05-31T23:30:00+00:00", "금15:35캡처 → 월08:30 KST: FRESH 기대"),
("2026-05-29T06:35:00+00:00", "2026-06-01T00:30:00+00:00", "금15:35캡처 → 월09:30 KST: STALE 기대"),
]
for cap, now, desc in cases:
r = is_data_stale(cap, datetime.fromisoformat(now))
print(f"{desc}\n stale={r['stale']} deadline={r['stale_deadline_kst']} hours_until={r['hours_until_stale']}\n")
@@ -0,0 +1,307 @@
#!/usr/bin/env python3
"""
measure_harness_coverage.py
───────────────────────────────────────────────────────────────────────────────
하네스 커버리지 측정기
"YAML 스펙을 작성해도 GAS가 실제로 계산하지 않으면 LLM이 매번 다른 숫자를 만든다."
이 도구는 현재 harness_context에서 GAS가 실제 채운 수치 필드 vs
LLM이 추정해야 하는 공백 필드를 정량 측정한다.
출력:
- 전체 커버리지 % (GAS 산출 / 전체 필수 필드)
- 공식별 커버리지 표
- LLM 자유도 점수 (낮을수록 결정론적)
- 재현성 위험 필드 목록 (LLM이 계산해야 하는 필드 = 랜덤성 원천)
사용법:
python tools/measure_harness_coverage.py [GatherTradingData.json]
python tools/measure_harness_coverage.py [GatherTradingData.json] --strict-100
"""
from __future__ import annotations
import json
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
# ── 공식별 필수 출력 필드 정의 ──────────────────────────────────────────────
# (field_name, description, data_type)
FORMULA_OUTPUT_FIELDS: dict[str, list[tuple[str, str, str]]] = {
# ── STAGE 0 ──────────────────────────────────────────────────────────────
"HARNESS_DATA_FRESHNESS_GATE_V1": [
("data_freshness_status", "데이터 신선도 상태", "enum"),
],
"INTRADAY_ACTION_MATRIX_V1": [
("intraday_scope", "장중/장전 허용 액션 범위", "enum"),
("intraday_lock", "장중 잠금 여부", "bool"),
],
# ── STAGE 1 ──────────────────────────────────────────────────────────────
"CASH_RATIOS_V1": [
("settlement_cash_d2_krw", "D+2 정산현금(원)", "numeric"),
("settlement_cash_pct", "D+2 현금 비율(%)", "numeric"),
("cash_floor_min_pct", "최소 현금 바닥(%)", "numeric"),
("cash_shortfall_min_krw", "현금 부족분(원)", "numeric"),
],
"TOTAL_HEAT_V1": [
("total_heat_pct", "포트폴리오 총 Heat(%)", "numeric"),
("heat_gate_status", "Heat 게이트 상태", "enum"),
],
# ── STAGE 2 ──────────────────────────────────────────────────────────────
"PROFIT_LOCK_RATCHET_V1": [
("profit_lock_stage", "수익 잠금 단계", "enum"),
("auto_trailing_stop", "ATR 기반 자동 트레일링", "numeric"),
],
"PROFIT_RATCHET_TIERED_V2": [
("auto_trailing_stop_v2", "3RD — APEX_SUPER 래칫", "numeric"),
("ratchet_stage_v2", "래칫 단계 v2", "enum"),
],
# ── STAGE 3 ──────────────────────────────────────────────────────────────
"FLOW_ACCELERATION_V1": [
("flow_acceleration_status", "수급 에너지 소진 상태", "enum"),
],
"DISTRIBUTION_SELL_DETECTOR_V1": [
("distribution_sell_detector_status", "설거지 감지 상태 (6신호)", "enum"),
("signals_count", "트리거된 신호 수", "numeric"),
],
# ── STAGE 4 ──────────────────────────────────────────────────────────────
"BREAKOUT_QUALITY_GATE_V2": [
("breakout_quality_score", "돌파 품질 점수", "numeric"),
],
"ANTI_CHASING_VELOCITY_V1": [
("anti_chasing_verdict", "뒷박 추격 차단 판정", "enum"),
("anti_chasing_velocity_status", "속도 차단 상태", "enum"),
],
"PULLBACK_ENTRY_TRIGGER_V1": [
("pullback_entry_verdict", "눌림목 진입 판정", "enum"),
("pullback_entry_trigger_price", "허용 진입 기준가(원)", "numeric"),
],
# ── STAGE 5 ──────────────────────────────────────────────────────────────
"CASH_RECOVERY_OPTIMIZER_V1": [
("cash_recovery_plan_json", "현금회복 최적 매도조합 JSON", "json"),
],
"SELL_WATERFALL_ENGINE_V1": [
("waterfall_plan_json", "폭포수 매도 계획 JSON", "json"),
],
"SELL_EXECUTION_TIMING_V1": [
("sell_timing_verdict", "매도 실행 타이밍 판정", "enum"),
("sell_execution_window", "실행 허용 시간대", "enum"),
],
"SELL_VALUE_PRESERVATION_TIERED_V2": [
("preservation_verdict", "주식가치 보호 매도 판정", "enum"),
],
# ── STAGE 6 ──────────────────────────────────────────────────────────────
"TICK_NORMALIZER_V1": [
("tick_normalized_price", "호가 정규화 완료 표시", "bool"),
],
"SELL_PRICE_SANITY_V1": [
("sell_price_sanity_status", "매도가 역전/비현실가 검증", "enum"),
],
# ── STAGE 7 ──────────────────────────────────────────────────────────────
"BENCHMARK_RELATIVE_TIMESERIES_V1": [
("brt_verdict", "BRT 상대강도 판정", "enum"),
("brt_rs_slope", "RS 기울기", "numeric"),
],
"RS_VERDICT_V2": [
("rs_verdict", "최종 RS 판정", "enum"),
],
# ── STAGE 8 ──────────────────────────────────────────────────────────────
"SATELLITE_ALPHA_QUALITY_GATE_V1": [
("saqg_verdict", "위성 품질 게이트", "enum"),
],
"SATELLITE_AGGREGATE_PNL_GATE_V1": [
("sapg_verdict", "위성 합산 손익 게이트", "enum"),
],
# ── STAGE 9 ──────────────────────────────────────────────────────────────
"LLM_SERVING_CONSTRAINT_V1": [
("serving_constraint_check", "LLM 제약 검사 결과", "enum"),
],
"DETERMINISTIC_ROUTING_ENGINE_V1": [
("routing_execution_log", "9단계 라우팅 실행 로그", "json"),
],
# ── MONTHLY BATCH ─────────────────────────────────────────────────────────
"TRADE_QUALITY_SCORER_V1": [
("trade_quality_json", "거래 품질 채점 결과 JSON", "json"),
],
"PATTERN_BLACKLIST_AUTO_V1": [
("pattern_blacklist_status", "반복 패턴 블랙리스트 상태", "enum"),
],
# ── 기존 필수 필드 ─────────────────────────────────────────────────────────
"POSITION_SIZE_V1": [
("buy_power_krw", "매수 가용 현금(원)", "numeric"),
("total_asset_krw", "총 자산(원)", "numeric"),
],
"prices_lock": [
("prices_json", "가격 잠금 JSON (stop/tp/current)", "json"),
],
"quantities_lock": [
("sell_quantities_json", "매도 수량 잠금 JSON", "json"),
("buy_qty_inputs_json", "매수 수량 잠금 JSON", "json"),
("order_blueprint_json", "HTS 주문 청사진 JSON", "json"),
],
}
SEP = "=" * 70
SEP2 = "-" * 70
def load_harness_context(json_path: Path) -> dict:
raw = json.loads(json_path.read_text(encoding="utf-8"))
hc = None
try:
hc = raw["data"]["_harness_context"]
except (KeyError, TypeError):
pass
if hc is None:
for key in ["_harness_context", "harness_context"]:
if key in raw and isinstance(raw[key], dict):
hc = raw[key]
break
if hc is None:
print("[ERROR] harness_context를 찾을 수 없음")
sys.exit(1)
return hc
def is_field_present(hc: dict, field: str) -> bool:
val = hc.get(field)
if val is None:
return False
if isinstance(val, str) and val.strip() == "":
return False
return True
def field_is_numeric(hc: dict, field: str) -> bool:
val = hc.get(field)
return isinstance(val, (int, float)) and not isinstance(val, bool)
def compute_coverage(hc: dict) -> dict[str, object]:
total_fields = 0
covered_fields = 0
missing_fields: list[tuple[str, str, str]] = []
covered_list: list[tuple[str, str]] = []
formula_results: list[dict[str, object]] = []
for formula_id, fields in FORMULA_OUTPUT_FIELDS.items():
f_total = len(fields)
f_covered = 0
f_missing: list[str] = []
for field_name, _description, dtype in fields:
total_fields += 1
if is_field_present(hc, field_name):
covered_fields += 1
f_covered += 1
covered_list.append((formula_id, field_name))
else:
f_missing.append(field_name)
missing_fields.append((formula_id, field_name, dtype))
pct = f_covered / f_total * 100 if f_total > 0 else 0
formula_results.append({
"formula_id": formula_id,
"total": f_total,
"covered": f_covered,
"pct": pct,
"missing": f_missing,
})
overall_pct = covered_fields / total_fields * 100 if total_fields > 0 else 0
return {
"total_fields": total_fields,
"covered_fields": covered_fields,
"overall_pct": overall_pct,
"llm_freedom_score": 100 - overall_pct,
"missing_fields": missing_fields,
"covered_list": covered_list,
"formula_results": formula_results,
}
def ensure_utf8_stdio() -> None:
# Windows cp949 터미널 호환
if sys.stdout.encoding and sys.stdout.encoding.lower() not in ("utf-8", "utf8"):
sys.stdout = open(sys.stdout.fileno(), mode="w", encoding="utf-8", buffering=1)
if sys.stderr.encoding and sys.stderr.encoding.lower() not in ("utf-8", "utf8"):
sys.stderr = open(sys.stderr.fileno(), mode="w", encoding="utf-8", buffering=1)
def main() -> int:
ensure_utf8_stdio()
strict_100 = "--strict-100" in sys.argv
argv = [arg for arg in sys.argv[1:] if arg != "--strict-100"]
json_path = Path(argv[0]) if argv else ROOT / "GatherTradingData.json"
if not json_path.exists():
print(f"[ERROR] {json_path} not found")
return 1
hc = load_harness_context(json_path)
coverage = compute_coverage(hc)
print(SEP)
print(" 하네스 커버리지 측정기 — Harness Coverage Report")
print(f" 파일: {json_path.name}")
print(f" harness_version: {hc.get('harness_version', '(missing)')}")
print(f" computed_at: {hc.get('computed_at', '(missing)')}")
print(SEP)
# ── 공식별 커버리지 표 ──────────────────────────────────────────────────
print("\n[공식별 커버리지]")
print(f" {'공식 ID':<45} {'커버':<6} {'전체':<6} {'%':<7} 상태")
print(" " + "-" * 65)
for r in coverage["formula_results"]:
bar = "" * r["covered"] + "" * (r["total"] - r["covered"])
status = "✔ FULL" if r["pct"] == 100 else ("△ PARTIAL" if r["pct"] > 0 else "✗ MISSING")
print(f" {r['formula_id']:<45} {r['covered']:<6} {r['total']:<6} {r['pct']:>5.0f}% {status} {bar}")
# ── 전체 커버리지 요약 ──────────────────────────────────────────────────
overall_pct = coverage["overall_pct"]
llm_freedom_score = coverage["llm_freedom_score"] # 높을수록 LLM이 더 많이 추정
print()
print(SEP)
print(f" 전체 커버리지 : {coverage['covered_fields']}/{coverage['total_fields']} 필드 = {overall_pct:.1f}%")
print(f" LLM 자유도 점수 : {llm_freedom_score:.1f}% ← 낮을수록 결정론적 (목표: 0%)")
print(SEP)
if llm_freedom_score == 0:
print("\n ✔ 완전 결정론적 — LLM이 임의 계산해야 할 필드 없음")
else:
# ── 재현성 위험 필드 목록 ─────────────────────────────────────────
missing_fields = coverage["missing_fields"]
print(f"\n[재현성 위험 필드 — GAS 미계산 = LLM 추정 = 랜덤성 원천] ({len(missing_fields)}개)")
print(" 이 필드들은 LLM 호출마다 다른 값이 나올 수 있습니다.\n")
print(f" {'공식 ID':<45} {'필드명':<40} 타입")
print(" " + "-" * 95)
for formula_id, field_name, dtype in missing_fields:
print(f" {formula_id:<45} {field_name:<40} {dtype}")
# ── 수치 필드 실제 값 확인 (GAS 계산 완료된 필드) ──────────────────────
covered_list = coverage["covered_list"]
formula_results = coverage["formula_results"]
print(f"\n[GAS 계산 완료 수치 필드] ({len(covered_list)}개)")
numeric_present = [
(fid, fn, hc[fn])
for fid, fn in covered_list
if field_is_numeric(hc, fn)
]
for fid, fn, val in numeric_present[:20]:
print(f" {fn:<45} = {val:>15,.0f}" if isinstance(val, (int, float)) else f" {fn:<45} = {val}")
# ── GAS 구현 우선순위 권고 ──────────────────────────────────────────────
print(f"\n[GAS 구현 우선순위 — 커버리지 0% 공식부터]")
zero_coverage = [r for r in formula_results if r["pct"] == 0]
for r in zero_coverage:
print(f" !!! {r['formula_id']} — 출력 필드 {r['total']}개 전부 미계산")
print()
threshold = 100.0 if strict_100 else 80.0
return 0 if overall_pct >= threshold else 1
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,352 @@
from __future__ import annotations
import argparse
import json
import re
from pathlib import Path
from typing import Any
import yaml
ROOT = Path(__file__).resolve().parents[2]
# ── 셀-레벨 커버리지: yaml expected_outputs → operational_report 셀 매핑 ──────
# 각 formula의 expected_outputs 필드가 operational_report의 표 셀에 채워졌는지 측정.
# _CELL_COVERAGE_STUBS: 채워진 것처럼 보이지만 실제 데이터 없는 일률 stub 값들
_CELL_COVERAGE_STUBS = frozenset({
"", "-", "n/a", "N/A", "데이터 누락", "DATA_MISSING", "중립", "NEUTRAL",
"LOSING", "정상", "NORMAL", "MISSING", "WATCH_PENDING_SAMPLE",
})
def load_yaml(path: Path) -> dict[str, Any]:
payload = yaml.safe_load(path.read_text(encoding="utf-8"))
return payload if isinstance(payload, dict) else {}
def load_json_safe(path: Path) -> dict[str, Any]:
if not path.exists():
return {}
try:
v = json.loads(path.read_text(encoding="utf-8"))
return v if isinstance(v, dict) else {}
except Exception:
return {}
def formula_registry() -> dict[str, Any]:
"""formula_id → formula_dict (expected_outputs 포함)."""
registry: dict[str, Any] = {}
for p in (ROOT / "spec" / "13_formula_registry.yaml", ROOT / "spec" / "13b_harness_formulas.yaml"):
y = load_yaml(p)
fm = ((y.get("formula_registry") or {}).get("formulas")) or {}
for k, v in fm.items():
if isinstance(v, dict):
registry[str(k)] = v
return registry
def formula_ids() -> list[str]:
return sorted(formula_registry().keys())
def read_texts(paths: list[Path]) -> str:
chunks: list[str] = []
for p in paths:
if p.exists():
chunks.append(p.read_text(encoding="utf-8", errors="ignore"))
return "\n".join(chunks)
def _extract_table_cells(markdown: str) -> set[str]:
"""GFM 표에서 셀 값 목록을 추출 (헤더 + 데이터 행)."""
cells: set[str] = set()
for line in markdown.split("\n"):
if "|" not in line:
continue
parts = [p.strip() for p in line.split("|")]
for p in parts:
if p and p != "---" and not re.match(r"^-+$", p):
cells.add(p)
return cells
def _is_stub(value: str) -> bool:
return value.strip() in _CELL_COVERAGE_STUBS or value.strip().startswith("-")
def measure_cell_coverage(
formula_reg: dict[str, Any],
report_json: dict[str, Any],
harness_ctx: dict[str, Any],
temp_outputs: dict[str, dict[str, Any]],
) -> dict[str, Any]:
"""yaml expected_outputs → 4경로 커버리지.
출력 필드가 채워진 것으로 인정하는 4가지 경로:
1. GAS harness_context에 output.field 키가 non-null 존재
2. Phase-1 Temp JSON 파일에 expected_output 필드가 non-stub 존재
3. operational_report 섹션 텍스트에 expected_output 이름이 column header로 존재
4. operational_report 표 셀에 non-stub 값으로 필드명=값 패턴 존재
"""
# Collect all report text (markdown)
all_section_text = ""
for sec in report_json.get("sections") or []:
all_section_text += " " + (sec.get("markdown") or "")
# Flatten all Temp output values for quick lookup
temp_flat: dict[str, Any] = {}
for _fname, tdata in temp_outputs.items():
if isinstance(tdata, dict):
# Flatten top-level scalars and row-level fields
for k, v in tdata.items():
if k not in ("rows", "steps", "selected_combo"):
temp_flat[k] = v
# Also include fields from first row of any rows list
for listkey in ("rows", "steps", "selected_combo"):
lst = tdata.get(listkey)
if isinstance(lst, list) and lst and isinstance(lst[0], dict):
for k, v in lst[0].items():
temp_flat.setdefault(k, v)
required_outputs: list[dict[str, Any]] = []
for fid, fdef in formula_reg.items():
if not isinstance(fdef, dict):
continue
# orphan reconcile 공식은 GAS/보고서 셀 검사 제외 (Python harness 전용)
if str(fdef.get("version", "")).endswith("_ORPHAN_RECONCILE"):
continue
exp = fdef.get("expected_outputs")
if not isinstance(exp, list):
continue
out_field = (fdef.get("output") or {}).get("field") if isinstance(fdef.get("output"), dict) else None
# Path 1: GAS harness_context
ctx_present = bool(out_field and harness_ctx.get(out_field) is not None)
for o in exp:
field_name = str(o).strip() if isinstance(o, str) else str(o).strip()
# Path 2: Temp JSON outputs (Phase-1 Python tools)
temp_val = temp_flat.get(field_name)
temp_filled = temp_val is not None and str(temp_val).strip() not in _CELL_COVERAGE_STUBS
# Path 3: Column header in report
in_report_header = bool(field_name and field_name in all_section_text)
# Path 4: Row-level cell value (non-stub)
non_stub_value = False
pat = re.search(
rf"\b{re.escape(field_name)}\b[^|\n]*\|([^|\n]+)", all_section_text
)
if pat:
val_candidate = pat.group(1).strip()
non_stub_value = not _is_stub(val_candidate)
filled = ctx_present or temp_filled or (in_report_header and non_stub_value)
required_outputs.append({
"formula_id": fid,
"output_field": field_name,
"ctx_present": ctx_present,
"temp_filled": temp_filled,
"in_report_header": in_report_header,
"non_stub_value": non_stub_value,
"filled": filled,
})
total = len(required_outputs)
filled_count = sum(1 for r in required_outputs if r["filled"])
cell_coverage_pct = round(filled_count / total * 100, 2) if total > 0 else 0.0
unfilled = [r for r in required_outputs if not r["filled"]]
return {
"total_required_outputs": total,
"filled_outputs": filled_count,
"cell_coverage_pct": cell_coverage_pct,
"unfilled_outputs": unfilled,
"cell_gate": "PASS" if cell_coverage_pct >= 95.0 else ("CAUTION" if cell_coverage_pct >= 75.0 else "FAIL"),
}
def main() -> int:
parser = argparse.ArgumentParser(description="Measure YAML formula coverage in GS and PS governance layers.")
parser.add_argument("--strict-100", action="store_true")
parser.add_argument("--output-json", default=str(ROOT / "Temp" / "yaml_gs_ps_coverage.json"))
parser.add_argument("--report-json", default=str(ROOT / "Temp" / "operational_report.json"))
args = parser.parse_args()
reg = formula_registry()
ids = sorted(reg.keys())
_GS_CORE = [ROOT / "gas_data_feed.gs", ROOT / "gas_harness_rows.gs", ROOT / "gas_lib.gs", ROOT / "gas_data_collect.gs", ROOT / "gas_report.gs"]
_GAS_ADAPTER_DIR = ROOT / "src" / "gas_adapter_parts"
_gs_adapter_files = sorted(_GAS_ADAPTER_DIR.glob("*.gs")) if _GAS_ADAPTER_DIR.is_dir() else []
_GS_ALPHA_WATCH = [ROOT / "gas_apex_alpha_watch.gs", ROOT / "gas_apex_runtime_core.gs"]
gs_text = read_texts(_GS_CORE + _gs_adapter_files + _GS_ALPHA_WATCH)
ps_text = read_texts([ROOT / "tools" / "run_engine_harness_gate.ps1", ROOT / "tools" / "run_yolo_full_cycle.ps1"])
gate_py_text = read_texts([ROOT / "tools" / "validate_engine_harness_gate.py"])
gs_hit = [i for i in ids if i in gs_text]
gs_miss = [i for i in ids if i not in gs_text]
# PS는 공식 직접 계산 계층이 아니라 실행 강제 계층.
ps_required_hooks = [
("run_engine_harness_gate.ps1", ROOT / "tools" / "run_engine_harness_gate.ps1"),
("run_yolo_full_cycle.ps1", ROOT / "tools" / "run_yolo_full_cycle.ps1"),
("validate_engine_harness_gate.py", ROOT / "tools" / "validate_engine_harness_gate.py"),
]
ps_hook_hit = [name for name, path in ps_required_hooks if path.exists()]
ps_hook_miss = [name for name, path in ps_required_hooks if not path.exists()]
total = len(ids) if ids else 1
gs_pct = round(len(gs_hit) / total * 100, 2)
ps_pct = round(len(ps_hook_hit) / len(ps_required_hooks) * 100, 2)
# ── 셀-레벨 커버리지 측정 ──────────────────────────────────────────────────
report_json_path = Path(args.report_json)
if not report_json_path.is_absolute():
report_json_path = ROOT / report_json_path
report_json = load_json_safe(report_json_path)
# harness context from GatherTradingData.json
gtd = load_json_safe(ROOT / "GatherTradingData.json")
hctx = (gtd.get("data") or {}).get("_harness_context") or {}
# Phase-1/2/3 Temp outputs (Python tools)
_TEMP = ROOT / "Temp"
temp_outputs = {
# Phase-1
"ejce_view_renderer_v1": load_json_safe(_TEMP / "ejce_view_renderer_v1.json"),
"smart_cash_recovery_v3": load_json_safe(_TEMP / "smart_cash_recovery_v3.json"),
"ratchet_trailing_v1": load_json_safe(_TEMP / "ratchet_trailing_general_v1.json"),
"value_preservation_v1": load_json_safe(_TEMP / "value_preservation_scorer_v1.json"),
"routing_execution_log_v1": load_json_safe(_TEMP / "routing_execution_log_v1.json"),
"blank_cell_audit_v1": load_json_safe(_TEMP / "blank_cell_audit_v1.json"),
"formula_registry_sync_v1": load_json_safe(_TEMP / "formula_registry_sync_v1.json"),
# Phase-2
"fundamental_raw_v1": load_json_safe(_TEMP / "fundamental_raw_v1.json"),
"fundamental_multifactor_v3": load_json_safe(_TEMP / "fundamental_multifactor_v3.json"),
"horizon_classification_v1": load_json_safe(_TEMP / "horizon_classification_v1.json"),
# Phase-2B
"earnings_quality_signal_v1": load_json_safe(_TEMP / "earnings_quality_signal_v1.json"),
"growth_rate_signal_v1": load_json_safe(_TEMP / "growth_rate_signal_v1.json"),
"cashflow_quality_signal_v1": load_json_safe(_TEMP / "cashflow_quality_signal_v1.json"),
"market_share_signal_v2": load_json_safe(_TEMP / "market_share_signal_v2.json"),
# Phase-3
"smart_money_flow_signal_v2": load_json_safe(_TEMP / "smart_money_flow_signal_v2.json"),
"liquidity_flow_signal_v1": load_json_safe(_TEMP / "liquidity_flow_signal_v1.json"),
"capital_style_allocation_v1": load_json_safe(_TEMP / "capital_style_allocation_v1.json"),
"portfolio_alpha_confidence_per_ticker_v1": load_json_safe(_TEMP / "portfolio_alpha_confidence_per_ticker_v1.json"),
# [Advanced Harness Architecture]
"dynamic_value_preservation_sell_v6": load_json_safe(_TEMP / "dynamic_value_preservation_sell_v6.json"),
"predictive_alpha_engine_v2": load_json_safe(_TEMP / "predictive_alpha_engine_v2.json"),
"capital_style_time_stop_v1": load_json_safe(_TEMP / "capital_style_time_stop_v1.json"),
"execution_integrity_gate_v1": load_json_safe(_TEMP / "execution_integrity_gate_v1.json"),
# Phase-6 Python-tool-only
"final_judgment_gate_v1": load_json_safe(_TEMP / "final_judgment_gate_v1.json"),
"verdict_consistency_lock_v1": load_json_safe(_TEMP / "verdict_consistency_lock_v1.json"),
"data_quality_reconciliation_v1": load_json_safe(_TEMP / "data_quality_reconciliation_v1.json"),
}
cell_cov = measure_cell_coverage(reg, report_json, hctx, temp_outputs)
# ─────────────────────────────────────────────────────────────────────────
# Python-tool-only formulas: not in GAS (implemented as Python tools)
_PYTHON_TOOL_FORMULAS = {
# Phase-1
"BLANK_CELL_AUDIT_V1", "VALUE_PRESERVATION_SCORER_V1",
"SMART_CASH_RECOVERY_V3", "RATCHET_TRAILING_GENERAL_V1",
"EJCE_VIEW_RENDERER_V1", "ROUTING_EXECUTION_LOG_TABLE_V1",
# Phase-2
"FUNDAMENTAL_RAW_INGEST_V1", "FUNDAMENTAL_MULTIFACTOR_V3",
"HORIZON_CLASSIFICATION_V1",
# Phase-2B
"EARNINGS_QUALITY_SIGNAL_V1", "GROWTH_RATE_SIGNAL_V1",
"CASHFLOW_QUALITY_SIGNAL_V1",
# Phase-3
"SMART_MONEY_FLOW_SIGNAL_V2", "LIQUIDITY_FLOW_SIGNAL_V1",
"PORTFOLIO_ALPHA_CONFIDENCE_PER_TICKER_V1",
# Phase-3 Market Share V2
"MARKET_SHARE_SIGNAL_V2",
# [Advanced Harness Architecture]
"DYNAMIC_VALUE_PRESERVATION_SELL_V6", "PREDICTIVE_ALPHA_DIALECTIC_ENGINE_V2",
"CAPITAL_STYLE_TIME_STOP_V1", "EXECUTION_INTEGRITY_GATE_V1",
# Phase-4~5 Python-tool-only 공식 (GAS 구현 없음, Python tools로 구현)
"TRADE_QUALITY_FROM_T5_V1", "PREDICTION_ACCURACY_HARNESS_V2",
"MACRO_EVENT_TICKER_IMPACT_V1", "SELL_WATERFALL_ENGINE_V2",
"LLM_NARRATIVE_TEMPLATE_LOCK_V1", "EJCE_DIVERGENCE_AUDIT_V1",
"PREDICTIVE_ALPHA_REPORT_LOCK_V2",
# Phase-6 Python-tool-only 공식 (판단 결정론 계층)
"FINAL_JUDGMENT_GATE_V1", "VERDICT_CONSISTENCY_LOCK_V1",
"INVESTMENT_QUALITY_HEADLINE_V1",
# Phase-7 단일 진실원천 + 교차섹션 정합성 (Python-tool-only, GAS 구현 불필요)
"CANONICAL_METRICS_V1", "CROSS_SECTION_CONSISTENCY_V1",
# Work 7 + Work 3 분석 도구
"ALPHA_FEEDBACK_LOOP_V2", "ALPHA_LEAD_THRESHOLD_OPTIMIZER_V1",
# Registry sync: formulas implemented outside GAS coverage path
"VELOCITY_V1", "PROFIT_LOCK_STAGE_V1", "ANTI_LATE_ENTRY_GATE_V2",
"DYNAMIC_HEAT_GATE_V1", "POSITION_SIZE_REGIME_SCALE_V1",
"REGIME_CASH_UPLIFT_V1", "DRAWDOWN_GUARD_V1", "POSITION_COUNT_LIMIT_V1",
"CASH_FLOOR_V1", "SEMICONDUCTOR_CLUSTER_GATE_V1",
"SINGLE_POSITION_WEIGHT_CAP_V1", "REGIME_TRIM_GUIDANCE_V1",
"HEAT_CONCENTRATION_ALERT_V1", "SECTOR_CONCENTRATION_LIMIT_V1",
"PORTFOLIO_DRAWDOWN_GATE_V1", "K2_STAGED_REBOUND_SELL_V1",
"STOP_BREACH_ALERT_V1", "SECTOR_ROTATION_MOMENTUM_V1",
"ANTI_WHIPSAW_GATE_V1", "BREAKEVEN_RATCHET_V1",
"MARKET_WEIGHT_AWARE_CLUSTER_GATE_V1", "LEADER_POSITION_WEIGHT_CAP_V1",
"CAPITAL_STYLE_ALLOCATION_V1",
# ENGINE_AUDIT_V1 — Python-tool-only 감사 게이트 (GAS 런타임 비개입)
"IMPUTED_DATA_EXPOSURE_GATE_V1",
"SCORES_HARNESS_V1",
"STRATEGY_ROUTING_AUDIT_V1",
"SELL_ENGINE_AUDIT_V1",
"YAML_TO_CODE_COVERAGE_V1",
"REALIZED_PERFORMANCE_V1",
"BACKTEST_HARNESS_V1",
# NF1~NF5: GAS execution_order 제외 Python-harness 전용 보조 공식 (python_harness_supplements 등록)
"REGIME_CONDITIONAL_MACRO_FACTOR_V1", # NF1 — tools/build_predictive_alpha_dialectic_engine_v2.py
"REBOUND_CAPTURE_THESIS_FACTOR_V1", # NF2 — tools/build_predictive_alpha_dialectic_engine_v2.py
"ENTRY_TIMING_DECILE_FACTOR_V1", # NF3 — tools/build_late_chase_attribution_v1.py
"SELL_SLIPPAGE_BUDGET_FACTOR_V1", # NF4 — tools/build_value_preservation_scorer_v1.py
"PROFIT_GIVEBACK_RATCHET_FACTOR_V1", # NF5 — tools/build_ratchet_trailing_general_v1.py
# Phase-execution Python-tool-only (tools/build_execution_method_ladder_v1.py, runtime=PYTHON)
"EXECUTION_METHOD_LADDER_V1",
}
# V9 orphan reconcile — _ORPHAN_RECONCILE 버전 태그 공식은 GAS 요구사항 면제
ids_to_skip = {fid for fid, fdef in reg.items() if isinstance(fdef, dict) and str(fdef.get("version", "")).endswith("_ORPHAN_RECONCILE")}
_PYTHON_TOOL_FORMULAS = _PYTHON_TOOL_FORMULAS | ids_to_skip
block_gs_miss = [f for f in gs_miss if f not in _PYTHON_TOOL_FORMULAS]
summary = {
"formula_total": len(ids),
"gs_covered": len(gs_hit),
"gs_missing": gs_miss,
"gs_coverage_pct": gs_pct,
"gs_blocking_missing": block_gs_miss,
"ps_required_hooks": [name for name, _ in ps_required_hooks],
"ps_hook_covered": len(ps_hook_hit),
"ps_hook_missing": ps_hook_miss,
"ps_coverage_pct": ps_pct,
"cell_coverage": cell_cov,
"status": "OK" if (gs_pct >= 100.0 and ps_pct == 100.0 and cell_cov["cell_gate"] != "FAIL") else "FAIL",
}
out = Path(args.output_json)
if not out.is_absolute():
out = ROOT / out
out.parent.mkdir(parents=True, exist_ok=True)
out.write_text(json.dumps(summary, ensure_ascii=False, indent=2), encoding="utf-8")
print(
f"YAML_GS_PS_COVERAGE: gs={gs_pct:.2f}% "
f"ps={ps_pct:.2f}% total={len(ids)} "
f"cell_coverage={cell_cov['cell_coverage_pct']:.2f}% [{cell_cov['cell_gate']}]"
)
if summary["status"] == "OK":
print("YAML_GS_PS_COVERAGE_OK")
return 0
print("YAML_GS_PS_COVERAGE_FAIL")
if args.strict_100:
return 1
return 0
if __name__ == "__main__":
raise SystemExit(main())
+1
View File
@@ -0,0 +1 @@
"""Auto-generated quant_engine.models package."""
@@ -0,0 +1 @@
"""Auto-generated schema model package."""
@@ -0,0 +1,33 @@
"""Auto-generated schema model descriptor."""
from __future__ import annotations
from dataclasses import dataclass
import json
from pathlib import Path
from typing import Any
SCHEMA_TITLE = 'ABSOLUTE_RISK_STOP_V1'
SCHEMA_ID = 'schema://formula/ABSOLUTE_RISK_STOP_V1'
SCHEMA_PATH = 'schemas/generated/absolute_risk_stop_v1.schema.json'
SCHEMA_PROPERTIES = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
SCHEMA_REQUIRED = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
@dataclass(frozen=True)
class SchemaModel:
title: str
schema_id: str
path: str
properties: list[str]
required: list[str]
def load_schema() -> dict[str, Any]:
return json.loads(Path(__file__).with_suffix('.schema.json').read_text(encoding='utf-8'))
def describe() -> SchemaModel:
return SchemaModel(
title=SCHEMA_TITLE,
schema_id=SCHEMA_ID,
path=SCHEMA_PATH,
properties=list(SCHEMA_PROPERTIES),
required=list(SCHEMA_REQUIRED),
)
@@ -0,0 +1,41 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema://formula/ABSOLUTE_RISK_STOP_V1",
"title": "ABSOLUTE_RISK_STOP_V1",
"type": "object",
"properties": {
"formula_id": {
"const": "ABSOLUTE_RISK_STOP_V1"
},
"owner": {
"type": "string"
},
"status": {
"type": "string"
},
"inputs": {
"type": "array",
"items": {
"type": "string"
}
},
"outputs": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"formula_id",
"owner",
"status",
"inputs",
"outputs"
],
"x_formula_inputs": [
"holdings",
"df_map"
],
"x_formula_outputs": []
}
@@ -0,0 +1,33 @@
"""Auto-generated schema model descriptor."""
from __future__ import annotations
from dataclasses import dataclass
import json
from pathlib import Path
from typing import Any
SCHEMA_TITLE = 'ALGORITHM_GUIDANCE_PROOF_V1'
SCHEMA_ID = 'schema://formula/ALGORITHM_GUIDANCE_PROOF_V1'
SCHEMA_PATH = 'schemas/generated/algorithm_guidance_proof_v1.schema.json'
SCHEMA_PROPERTIES = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
SCHEMA_REQUIRED = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
@dataclass(frozen=True)
class SchemaModel:
title: str
schema_id: str
path: str
properties: list[str]
required: list[str]
def load_schema() -> dict[str, Any]:
return json.loads(Path(__file__).with_suffix('.schema.json').read_text(encoding='utf-8'))
def describe() -> SchemaModel:
return SchemaModel(
title=SCHEMA_TITLE,
schema_id=SCHEMA_ID,
path=SCHEMA_PATH,
properties=list(SCHEMA_PROPERTIES),
required=list(SCHEMA_REQUIRED),
)
@@ -0,0 +1,38 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema://formula/ALGORITHM_GUIDANCE_PROOF_V1",
"title": "ALGORITHM_GUIDANCE_PROOF_V1",
"type": "object",
"properties": {
"formula_id": {
"const": "ALGORITHM_GUIDANCE_PROOF_V1"
},
"owner": {
"type": "string"
},
"status": {
"type": "string"
},
"inputs": {
"type": "array",
"items": {
"type": "string"
}
},
"outputs": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"formula_id",
"owner",
"status",
"inputs",
"outputs"
],
"x_formula_inputs": [],
"x_formula_outputs": []
}
@@ -0,0 +1,33 @@
"""Auto-generated schema model descriptor."""
from __future__ import annotations
from dataclasses import dataclass
import json
from pathlib import Path
from typing import Any
SCHEMA_TITLE = 'ALPHA_EVALUATION_WINDOW_V1'
SCHEMA_ID = 'schema://formula/ALPHA_EVALUATION_WINDOW_V1'
SCHEMA_PATH = 'schemas/generated/alpha_evaluation_window_v1.schema.json'
SCHEMA_PROPERTIES = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
SCHEMA_REQUIRED = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
@dataclass(frozen=True)
class SchemaModel:
title: str
schema_id: str
path: str
properties: list[str]
required: list[str]
def load_schema() -> dict[str, Any]:
return json.loads(Path(__file__).with_suffix('.schema.json').read_text(encoding='utf-8'))
def describe() -> SchemaModel:
return SchemaModel(
title=SCHEMA_TITLE,
schema_id=SCHEMA_ID,
path=SCHEMA_PATH,
properties=list(SCHEMA_PROPERTIES),
required=list(SCHEMA_REQUIRED),
)
@@ -0,0 +1,44 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema://formula/ALPHA_EVALUATION_WINDOW_V1",
"title": "ALPHA_EVALUATION_WINDOW_V1",
"type": "object",
"properties": {
"formula_id": {
"const": "ALPHA_EVALUATION_WINDOW_V1"
},
"owner": {
"type": "string"
},
"status": {
"type": "string"
},
"inputs": {
"type": "array",
"items": {
"type": "string"
}
},
"outputs": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"formula_id",
"owner",
"status",
"inputs",
"outputs"
],
"x_formula_inputs": [
"entry_date",
"position_class",
"t20_return_pct",
"t60_return_pct",
"benchmark_core_return_pct"
],
"x_formula_outputs": []
}
@@ -0,0 +1,33 @@
"""Auto-generated schema model descriptor."""
from __future__ import annotations
from dataclasses import dataclass
import json
from pathlib import Path
from typing import Any
SCHEMA_TITLE = 'ALPHA_FEEDBACK_LOOP_V1'
SCHEMA_ID = 'schema://formula/ALPHA_FEEDBACK_LOOP_V1'
SCHEMA_PATH = 'schemas/generated/alpha_feedback_loop_v1.schema.json'
SCHEMA_PROPERTIES = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
SCHEMA_REQUIRED = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
@dataclass(frozen=True)
class SchemaModel:
title: str
schema_id: str
path: str
properties: list[str]
required: list[str]
def load_schema() -> dict[str, Any]:
return json.loads(Path(__file__).with_suffix('.schema.json').read_text(encoding='utf-8'))
def describe() -> SchemaModel:
return SchemaModel(
title=SCHEMA_TITLE,
schema_id=SCHEMA_ID,
path=SCHEMA_PATH,
properties=list(SCHEMA_PROPERTIES),
required=list(SCHEMA_REQUIRED),
)
@@ -0,0 +1,53 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema://formula/ALPHA_FEEDBACK_LOOP_V1",
"title": "ALPHA_FEEDBACK_LOOP_V1",
"type": "object",
"properties": {
"formula_id": {
"const": "ALPHA_FEEDBACK_LOOP_V1"
},
"owner": {
"type": "string"
},
"status": {
"type": "string"
},
"inputs": {
"type": "array",
"items": {
"type": "string"
}
},
"outputs": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"formula_id",
"owner",
"status",
"inputs",
"outputs"
],
"x_formula_inputs": [
"alpha_evaluation_window_json",
"saqg_v1",
"brt_verdict",
"market_regime"
],
"x_formula_outputs": [
{
"field": "alpha_feedback_json",
"subfields": [
"eligible_t20_fail_rate",
"eligible_t60_fail_rate",
"recommended_filter_adjustments",
"cases_analyzed"
]
}
]
}
@@ -0,0 +1,33 @@
"""Auto-generated schema model descriptor."""
from __future__ import annotations
from dataclasses import dataclass
import json
from pathlib import Path
from typing import Any
SCHEMA_TITLE = 'ANTI_CHASE_V1'
SCHEMA_ID = 'schema://formula/ANTI_CHASE_V1'
SCHEMA_PATH = 'schemas/generated/anti_chase_v1.schema.json'
SCHEMA_PROPERTIES = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
SCHEMA_REQUIRED = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
@dataclass(frozen=True)
class SchemaModel:
title: str
schema_id: str
path: str
properties: list[str]
required: list[str]
def load_schema() -> dict[str, Any]:
return json.loads(Path(__file__).with_suffix('.schema.json').read_text(encoding='utf-8'))
def describe() -> SchemaModel:
return SchemaModel(
title=SCHEMA_TITLE,
schema_id=SCHEMA_ID,
path=SCHEMA_PATH,
properties=list(SCHEMA_PROPERTIES),
required=list(SCHEMA_REQUIRED),
)
@@ -0,0 +1,38 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema://formula/ANTI_CHASE_V1",
"title": "ANTI_CHASE_V1",
"type": "object",
"properties": {
"formula_id": {
"const": "ANTI_CHASE_V1"
},
"owner": {
"type": "string"
},
"status": {
"type": "string"
},
"inputs": {
"type": "array",
"items": {
"type": "string"
}
},
"outputs": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"formula_id",
"owner",
"status",
"inputs",
"outputs"
],
"x_formula_inputs": [],
"x_formula_outputs": []
}
@@ -0,0 +1,33 @@
"""Auto-generated schema model descriptor."""
from __future__ import annotations
from dataclasses import dataclass
import json
from pathlib import Path
from typing import Any
SCHEMA_TITLE = 'ANTI_CHASING_VELOCITY_V1'
SCHEMA_ID = 'schema://formula/ANTI_CHASING_VELOCITY_V1'
SCHEMA_PATH = 'schemas/generated/anti_chasing_velocity_v1.schema.json'
SCHEMA_PROPERTIES = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
SCHEMA_REQUIRED = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
@dataclass(frozen=True)
class SchemaModel:
title: str
schema_id: str
path: str
properties: list[str]
required: list[str]
def load_schema() -> dict[str, Any]:
return json.loads(Path(__file__).with_suffix('.schema.json').read_text(encoding='utf-8'))
def describe() -> SchemaModel:
return SchemaModel(
title=SCHEMA_TITLE,
schema_id=SCHEMA_ID,
path=SCHEMA_PATH,
properties=list(SCHEMA_PROPERTIES),
required=list(SCHEMA_REQUIRED),
)
@@ -0,0 +1,43 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema://formula/ANTI_CHASING_VELOCITY_V1",
"title": "ANTI_CHASING_VELOCITY_V1",
"type": "object",
"properties": {
"formula_id": {
"const": "ANTI_CHASING_VELOCITY_V1"
},
"owner": {
"type": "string"
},
"status": {
"type": "string"
},
"inputs": {
"type": "array",
"items": {
"type": "string"
}
},
"outputs": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"formula_id",
"owner",
"status",
"inputs",
"outputs"
],
"x_formula_inputs": [
"close",
"close_1d_ago",
"close_5d_ago",
"market_regime"
],
"x_formula_outputs": []
}
@@ -0,0 +1,33 @@
"""Auto-generated schema model descriptor."""
from __future__ import annotations
from dataclasses import dataclass
import json
from pathlib import Path
from typing import Any
SCHEMA_TITLE = 'ANTI_LATE_ENTRY_GATE_V2'
SCHEMA_ID = 'schema://formula/ANTI_LATE_ENTRY_GATE_V2'
SCHEMA_PATH = 'schemas/generated/anti_late_entry_gate_v2.schema.json'
SCHEMA_PROPERTIES = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
SCHEMA_REQUIRED = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
@dataclass(frozen=True)
class SchemaModel:
title: str
schema_id: str
path: str
properties: list[str]
required: list[str]
def load_schema() -> dict[str, Any]:
return json.loads(Path(__file__).with_suffix('.schema.json').read_text(encoding='utf-8'))
def describe() -> SchemaModel:
return SchemaModel(
title=SCHEMA_TITLE,
schema_id=SCHEMA_ID,
path=SCHEMA_PATH,
properties=list(SCHEMA_PROPERTIES),
required=list(SCHEMA_REQUIRED),
)
@@ -0,0 +1,38 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema://formula/ANTI_LATE_ENTRY_GATE_V2",
"title": "ANTI_LATE_ENTRY_GATE_V2",
"type": "object",
"properties": {
"formula_id": {
"const": "ANTI_LATE_ENTRY_GATE_V2"
},
"owner": {
"type": "string"
},
"status": {
"type": "string"
},
"inputs": {
"type": "array",
"items": {
"type": "string"
}
},
"outputs": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"formula_id",
"owner",
"status",
"inputs",
"outputs"
],
"x_formula_inputs": [],
"x_formula_outputs": []
}
@@ -0,0 +1,33 @@
"""Auto-generated schema model descriptor."""
from __future__ import annotations
from dataclasses import dataclass
import json
from pathlib import Path
from typing import Any
SCHEMA_TITLE = 'ANTI_WHIPSAW_GATE_V1'
SCHEMA_ID = 'schema://formula/ANTI_WHIPSAW_GATE_V1'
SCHEMA_PATH = 'schemas/generated/anti_whipsaw_gate_v1.schema.json'
SCHEMA_PROPERTIES = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
SCHEMA_REQUIRED = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
@dataclass(frozen=True)
class SchemaModel:
title: str
schema_id: str
path: str
properties: list[str]
required: list[str]
def load_schema() -> dict[str, Any]:
return json.loads(Path(__file__).with_suffix('.schema.json').read_text(encoding='utf-8'))
def describe() -> SchemaModel:
return SchemaModel(
title=SCHEMA_TITLE,
schema_id=SCHEMA_ID,
path=SCHEMA_PATH,
properties=list(SCHEMA_PROPERTIES),
required=list(SCHEMA_REQUIRED),
)
@@ -0,0 +1,42 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema://formula/ANTI_WHIPSAW_GATE_V1",
"title": "ANTI_WHIPSAW_GATE_V1",
"type": "object",
"properties": {
"formula_id": {
"const": "ANTI_WHIPSAW_GATE_V1"
},
"owner": {
"type": "string"
},
"status": {
"type": "string"
},
"inputs": {
"type": "array",
"items": {
"type": "string"
}
},
"outputs": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"formula_id",
"owner",
"status",
"inputs",
"outputs"
],
"x_formula_inputs": [
"close_price",
"ma20",
"rsi14"
],
"x_formula_outputs": []
}
@@ -0,0 +1,33 @@
"""Auto-generated schema model descriptor."""
from __future__ import annotations
from dataclasses import dataclass
import json
from pathlib import Path
from typing import Any
SCHEMA_TITLE = 'ARTIFACT_FRESHNESS_GATE_V1'
SCHEMA_ID = 'schema://formula/ARTIFACT_FRESHNESS_GATE_V1'
SCHEMA_PATH = 'schemas/generated/artifact_freshness_gate_v1.schema.json'
SCHEMA_PROPERTIES = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
SCHEMA_REQUIRED = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
@dataclass(frozen=True)
class SchemaModel:
title: str
schema_id: str
path: str
properties: list[str]
required: list[str]
def load_schema() -> dict[str, Any]:
return json.loads(Path(__file__).with_suffix('.schema.json').read_text(encoding='utf-8'))
def describe() -> SchemaModel:
return SchemaModel(
title=SCHEMA_TITLE,
schema_id=SCHEMA_ID,
path=SCHEMA_PATH,
properties=list(SCHEMA_PROPERTIES),
required=list(SCHEMA_REQUIRED),
)
@@ -0,0 +1,38 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema://formula/ARTIFACT_FRESHNESS_GATE_V1",
"title": "ARTIFACT_FRESHNESS_GATE_V1",
"type": "object",
"properties": {
"formula_id": {
"const": "ARTIFACT_FRESHNESS_GATE_V1"
},
"owner": {
"type": "string"
},
"status": {
"type": "string"
},
"inputs": {
"type": "array",
"items": {
"type": "string"
}
},
"outputs": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"formula_id",
"owner",
"status",
"inputs",
"outputs"
],
"x_formula_inputs": [],
"x_formula_outputs": []
}
@@ -0,0 +1,33 @@
"""Auto-generated schema model descriptor."""
from __future__ import annotations
from dataclasses import dataclass
import json
from pathlib import Path
from typing import Any
SCHEMA_TITLE = 'AUDIT_REPLAY_SNAPSHOT_V1'
SCHEMA_ID = 'schema://formula/AUDIT_REPLAY_SNAPSHOT_V1'
SCHEMA_PATH = 'schemas/generated/audit_replay_snapshot_v1.schema.json'
SCHEMA_PROPERTIES = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
SCHEMA_REQUIRED = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
@dataclass(frozen=True)
class SchemaModel:
title: str
schema_id: str
path: str
properties: list[str]
required: list[str]
def load_schema() -> dict[str, Any]:
return json.loads(Path(__file__).with_suffix('.schema.json').read_text(encoding='utf-8'))
def describe() -> SchemaModel:
return SchemaModel(
title=SCHEMA_TITLE,
schema_id=SCHEMA_ID,
path=SCHEMA_PATH,
properties=list(SCHEMA_PROPERTIES),
required=list(SCHEMA_REQUIRED),
)
@@ -0,0 +1,38 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema://formula/AUDIT_REPLAY_SNAPSHOT_V1",
"title": "AUDIT_REPLAY_SNAPSHOT_V1",
"type": "object",
"properties": {
"formula_id": {
"const": "AUDIT_REPLAY_SNAPSHOT_V1"
},
"owner": {
"type": "string"
},
"status": {
"type": "string"
},
"inputs": {
"type": "array",
"items": {
"type": "string"
}
},
"outputs": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"formula_id",
"owner",
"status",
"inputs",
"outputs"
],
"x_formula_inputs": [],
"x_formula_outputs": []
}
@@ -0,0 +1,33 @@
"""Auto-generated schema model descriptor."""
from __future__ import annotations
from dataclasses import dataclass
import json
from pathlib import Path
from typing import Any
SCHEMA_TITLE = 'BENCHMARK_RELATIVE_TIMESERIES_V1'
SCHEMA_ID = 'schema://formula/BENCHMARK_RELATIVE_TIMESERIES_V1'
SCHEMA_PATH = 'schemas/generated/benchmark_relative_timeseries_v1.schema.json'
SCHEMA_PROPERTIES = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
SCHEMA_REQUIRED = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
@dataclass(frozen=True)
class SchemaModel:
title: str
schema_id: str
path: str
properties: list[str]
required: list[str]
def load_schema() -> dict[str, Any]:
return json.loads(Path(__file__).with_suffix('.schema.json').read_text(encoding='utf-8'))
def describe() -> SchemaModel:
return SchemaModel(
title=SCHEMA_TITLE,
schema_id=SCHEMA_ID,
path=SCHEMA_PATH,
properties=list(SCHEMA_PROPERTIES),
required=list(SCHEMA_REQUIRED),
)
@@ -0,0 +1,48 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema://formula/BENCHMARK_RELATIVE_TIMESERIES_V1",
"title": "BENCHMARK_RELATIVE_TIMESERIES_V1",
"type": "object",
"properties": {
"formula_id": {
"const": "BENCHMARK_RELATIVE_TIMESERIES_V1"
},
"owner": {
"type": "string"
},
"status": {
"type": "string"
},
"inputs": {
"type": "array",
"items": {
"type": "string"
}
},
"outputs": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"formula_id",
"owner",
"status",
"inputs",
"outputs"
],
"x_formula_inputs": [
"price.ret5D",
"price.ret20D",
"price.ret60D",
"price.close",
"high52w",
"globalKospiRet5D_",
"globalKospiRet20D_",
"globalKospiRet60D_",
"globalKospiDrawdown_"
],
"x_formula_outputs": []
}
@@ -0,0 +1,33 @@
"""Auto-generated schema model descriptor."""
from __future__ import annotations
from dataclasses import dataclass
import json
from pathlib import Path
from typing import Any
SCHEMA_TITLE = 'BLANK_CELL_AUDIT_V1'
SCHEMA_ID = 'schema://formula/BLANK_CELL_AUDIT_V1'
SCHEMA_PATH = 'schemas/generated/blank_cell_audit_v1.schema.json'
SCHEMA_PROPERTIES = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
SCHEMA_REQUIRED = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
@dataclass(frozen=True)
class SchemaModel:
title: str
schema_id: str
path: str
properties: list[str]
required: list[str]
def load_schema() -> dict[str, Any]:
return json.loads(Path(__file__).with_suffix('.schema.json').read_text(encoding='utf-8'))
def describe() -> SchemaModel:
return SchemaModel(
title=SCHEMA_TITLE,
schema_id=SCHEMA_ID,
path=SCHEMA_PATH,
properties=list(SCHEMA_PROPERTIES),
required=list(SCHEMA_REQUIRED),
)
@@ -0,0 +1,40 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema://formula/BLANK_CELL_AUDIT_V1",
"title": "BLANK_CELL_AUDIT_V1",
"type": "object",
"properties": {
"formula_id": {
"const": "BLANK_CELL_AUDIT_V1"
},
"owner": {
"type": "string"
},
"status": {
"type": "string"
},
"inputs": {
"type": "array",
"items": {
"type": "string"
}
},
"outputs": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"formula_id",
"owner",
"status",
"inputs",
"outputs"
],
"x_formula_inputs": [
"operational_report_json"
],
"x_formula_outputs": []
}
@@ -0,0 +1,33 @@
"""Auto-generated schema model descriptor."""
from __future__ import annotations
from dataclasses import dataclass
import json
from pathlib import Path
from typing import Any
SCHEMA_TITLE = 'BREAKEVEN_RATCHET_V1'
SCHEMA_ID = 'schema://formula/BREAKEVEN_RATCHET_V1'
SCHEMA_PATH = 'schemas/generated/breakeven_ratchet_v1.schema.json'
SCHEMA_PROPERTIES = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
SCHEMA_REQUIRED = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
@dataclass(frozen=True)
class SchemaModel:
title: str
schema_id: str
path: str
properties: list[str]
required: list[str]
def load_schema() -> dict[str, Any]:
return json.loads(Path(__file__).with_suffix('.schema.json').read_text(encoding='utf-8'))
def describe() -> SchemaModel:
return SchemaModel(
title=SCHEMA_TITLE,
schema_id=SCHEMA_ID,
path=SCHEMA_PATH,
properties=list(SCHEMA_PROPERTIES),
required=list(SCHEMA_REQUIRED),
)
@@ -0,0 +1,41 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema://formula/BREAKEVEN_RATCHET_V1",
"title": "BREAKEVEN_RATCHET_V1",
"type": "object",
"properties": {
"formula_id": {
"const": "BREAKEVEN_RATCHET_V1"
},
"owner": {
"type": "string"
},
"status": {
"type": "string"
},
"inputs": {
"type": "array",
"items": {
"type": "string"
}
},
"outputs": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"formula_id",
"owner",
"status",
"inputs",
"outputs"
],
"x_formula_inputs": [
"average_cost",
"highest_price_since_entry"
],
"x_formula_outputs": []
}
@@ -0,0 +1,33 @@
"""Auto-generated schema model descriptor."""
from __future__ import annotations
from dataclasses import dataclass
import json
from pathlib import Path
from typing import Any
SCHEMA_TITLE = 'BREAKOUT_QUALITY_GATE_V2'
SCHEMA_ID = 'schema://formula/BREAKOUT_QUALITY_GATE_V2'
SCHEMA_PATH = 'schemas/generated/breakout_quality_gate_v2.schema.json'
SCHEMA_PROPERTIES = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
SCHEMA_REQUIRED = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
@dataclass(frozen=True)
class SchemaModel:
title: str
schema_id: str
path: str
properties: list[str]
required: list[str]
def load_schema() -> dict[str, Any]:
return json.loads(Path(__file__).with_suffix('.schema.json').read_text(encoding='utf-8'))
def describe() -> SchemaModel:
return SchemaModel(
title=SCHEMA_TITLE,
schema_id=SCHEMA_ID,
path=SCHEMA_PATH,
properties=list(SCHEMA_PROPERTIES),
required=list(SCHEMA_REQUIRED),
)
@@ -0,0 +1,50 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema://formula/BREAKOUT_QUALITY_GATE_V2",
"title": "BREAKOUT_QUALITY_GATE_V2",
"type": "object",
"properties": {
"formula_id": {
"const": "BREAKOUT_QUALITY_GATE_V2"
},
"owner": {
"type": "string"
},
"status": {
"type": "string"
},
"inputs": {
"type": "array",
"items": {
"type": "string"
}
},
"outputs": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"formula_id",
"owner",
"status",
"inputs",
"outputs"
],
"x_formula_inputs": [
"close",
"ma20",
"ret_3d",
"ret_1d",
"disparity",
"rsi14",
"volume",
"avg_volume_5d",
"timing_score_exit",
"distribution_risk_score",
"late_chase_risk_score"
],
"x_formula_outputs": []
}
@@ -0,0 +1,33 @@
"""Auto-generated schema model descriptor."""
from __future__ import annotations
from dataclasses import dataclass
import json
from pathlib import Path
from typing import Any
SCHEMA_TITLE = 'CANONICAL_ARTIFACT_RESOLVER_V1'
SCHEMA_ID = 'schema://formula/CANONICAL_ARTIFACT_RESOLVER_V1'
SCHEMA_PATH = 'schemas/generated/canonical_artifact_resolver_v1.schema.json'
SCHEMA_PROPERTIES = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
SCHEMA_REQUIRED = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
@dataclass(frozen=True)
class SchemaModel:
title: str
schema_id: str
path: str
properties: list[str]
required: list[str]
def load_schema() -> dict[str, Any]:
return json.loads(Path(__file__).with_suffix('.schema.json').read_text(encoding='utf-8'))
def describe() -> SchemaModel:
return SchemaModel(
title=SCHEMA_TITLE,
schema_id=SCHEMA_ID,
path=SCHEMA_PATH,
properties=list(SCHEMA_PROPERTIES),
required=list(SCHEMA_REQUIRED),
)
@@ -0,0 +1,38 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema://formula/CANONICAL_ARTIFACT_RESOLVER_V1",
"title": "CANONICAL_ARTIFACT_RESOLVER_V1",
"type": "object",
"properties": {
"formula_id": {
"const": "CANONICAL_ARTIFACT_RESOLVER_V1"
},
"owner": {
"type": "string"
},
"status": {
"type": "string"
},
"inputs": {
"type": "array",
"items": {
"type": "string"
}
},
"outputs": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"formula_id",
"owner",
"status",
"inputs",
"outputs"
],
"x_formula_inputs": [],
"x_formula_outputs": []
}
@@ -0,0 +1,33 @@
"""Auto-generated schema model descriptor."""
from __future__ import annotations
from dataclasses import dataclass
import json
from pathlib import Path
from typing import Any
SCHEMA_TITLE = 'CANONICAL_METRICS_V1'
SCHEMA_ID = 'schema://formula/CANONICAL_METRICS_V1'
SCHEMA_PATH = 'schemas/generated/canonical_metrics_v1.schema.json'
SCHEMA_PROPERTIES = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
SCHEMA_REQUIRED = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
@dataclass(frozen=True)
class SchemaModel:
title: str
schema_id: str
path: str
properties: list[str]
required: list[str]
def load_schema() -> dict[str, Any]:
return json.loads(Path(__file__).with_suffix('.schema.json').read_text(encoding='utf-8'))
def describe() -> SchemaModel:
return SchemaModel(
title=SCHEMA_TITLE,
schema_id=SCHEMA_ID,
path=SCHEMA_PATH,
properties=list(SCHEMA_PROPERTIES),
required=list(SCHEMA_REQUIRED),
)
@@ -0,0 +1,38 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema://formula/CANONICAL_METRICS_V1",
"title": "CANONICAL_METRICS_V1",
"type": "object",
"properties": {
"formula_id": {
"const": "CANONICAL_METRICS_V1"
},
"owner": {
"type": "string"
},
"status": {
"type": "string"
},
"inputs": {
"type": "array",
"items": {
"type": "string"
}
},
"outputs": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"formula_id",
"owner",
"status",
"inputs",
"outputs"
],
"x_formula_inputs": [],
"x_formula_outputs": []
}
@@ -0,0 +1,33 @@
"""Auto-generated schema model descriptor."""
from __future__ import annotations
from dataclasses import dataclass
import json
from pathlib import Path
from typing import Any
SCHEMA_TITLE = 'CAPITAL_STYLE_ALLOCATION_V1'
SCHEMA_ID = 'schema://formula/CAPITAL_STYLE_ALLOCATION_V1'
SCHEMA_PATH = 'schemas/generated/capital_style_allocation_v1.schema.json'
SCHEMA_PROPERTIES = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
SCHEMA_REQUIRED = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
@dataclass(frozen=True)
class SchemaModel:
title: str
schema_id: str
path: str
properties: list[str]
required: list[str]
def load_schema() -> dict[str, Any]:
return json.loads(Path(__file__).with_suffix('.schema.json').read_text(encoding='utf-8'))
def describe() -> SchemaModel:
return SchemaModel(
title=SCHEMA_TITLE,
schema_id=SCHEMA_ID,
path=SCHEMA_PATH,
properties=list(SCHEMA_PROPERTIES),
required=list(SCHEMA_REQUIRED),
)
@@ -0,0 +1,43 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema://formula/CAPITAL_STYLE_ALLOCATION_V1",
"title": "CAPITAL_STYLE_ALLOCATION_V1",
"type": "object",
"properties": {
"formula_id": {
"const": "CAPITAL_STYLE_ALLOCATION_V1"
},
"owner": {
"type": "string"
},
"status": {
"type": "string"
},
"inputs": {
"type": "array",
"items": {
"type": "string"
}
},
"outputs": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"formula_id",
"owner",
"status",
"inputs",
"outputs"
],
"x_formula_inputs": [
"smart_money_flow_signal_v2_json",
"fundamental_multifactor_v3_json",
"macro_event_ticker_impact_v1_json",
"liquidity_flow_signal_v1_json"
],
"x_formula_outputs": []
}
@@ -0,0 +1,33 @@
"""Auto-generated schema model descriptor."""
from __future__ import annotations
from dataclasses import dataclass
import json
from pathlib import Path
from typing import Any
SCHEMA_TITLE = 'CASH_CREATION_PURPOSE_LOCK_V1'
SCHEMA_ID = 'schema://formula/CASH_CREATION_PURPOSE_LOCK_V1'
SCHEMA_PATH = 'schemas/generated/cash_creation_purpose_lock_v1.schema.json'
SCHEMA_PROPERTIES = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
SCHEMA_REQUIRED = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
@dataclass(frozen=True)
class SchemaModel:
title: str
schema_id: str
path: str
properties: list[str]
required: list[str]
def load_schema() -> dict[str, Any]:
return json.loads(Path(__file__).with_suffix('.schema.json').read_text(encoding='utf-8'))
def describe() -> SchemaModel:
return SchemaModel(
title=SCHEMA_TITLE,
schema_id=SCHEMA_ID,
path=SCHEMA_PATH,
properties=list(SCHEMA_PROPERTIES),
required=list(SCHEMA_REQUIRED),
)
@@ -0,0 +1,45 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema://formula/CASH_CREATION_PURPOSE_LOCK_V1",
"title": "CASH_CREATION_PURPOSE_LOCK_V1",
"type": "object",
"properties": {
"formula_id": {
"const": "CASH_CREATION_PURPOSE_LOCK_V1"
},
"owner": {
"type": "string"
},
"status": {
"type": "string"
},
"inputs": {
"type": "array",
"items": {
"type": "string"
}
},
"outputs": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"formula_id",
"owner",
"status",
"inputs",
"outputs"
],
"x_formula_inputs": [
"composite_verdict",
"rs_verdict",
"brt_verdict",
"excess_drawdown_pctp",
"recovery_ratio_20d",
"sfg_v1"
],
"x_formula_outputs": []
}
@@ -0,0 +1,33 @@
"""Auto-generated schema model descriptor."""
from __future__ import annotations
from dataclasses import dataclass
import json
from pathlib import Path
from typing import Any
SCHEMA_TITLE = 'CASH_FLOOR_V1'
SCHEMA_ID = 'schema://formula/CASH_FLOOR_V1'
SCHEMA_PATH = 'schemas/generated/cash_floor_v1.schema.json'
SCHEMA_PROPERTIES = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
SCHEMA_REQUIRED = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
@dataclass(frozen=True)
class SchemaModel:
title: str
schema_id: str
path: str
properties: list[str]
required: list[str]
def load_schema() -> dict[str, Any]:
return json.loads(Path(__file__).with_suffix('.schema.json').read_text(encoding='utf-8'))
def describe() -> SchemaModel:
return SchemaModel(
title=SCHEMA_TITLE,
schema_id=SCHEMA_ID,
path=SCHEMA_PATH,
properties=list(SCHEMA_PROPERTIES),
required=list(SCHEMA_REQUIRED),
)
@@ -0,0 +1,42 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema://formula/CASH_FLOOR_V1",
"title": "CASH_FLOOR_V1",
"type": "object",
"properties": {
"formula_id": {
"const": "CASH_FLOOR_V1"
},
"owner": {
"type": "string"
},
"status": {
"type": "string"
},
"inputs": {
"type": "array",
"items": {
"type": "string"
}
},
"outputs": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"formula_id",
"owner",
"status",
"inputs",
"outputs"
],
"x_formula_inputs": [
"total_asset",
"settlement_cash_d2_krw",
"market_risk_score"
],
"x_formula_outputs": []
}
@@ -0,0 +1,33 @@
"""Auto-generated schema model descriptor."""
from __future__ import annotations
from dataclasses import dataclass
import json
from pathlib import Path
from typing import Any
SCHEMA_TITLE = 'CASH_RAISE_PARETO_EXECUTOR_V2'
SCHEMA_ID = 'schema://formula/CASH_RAISE_PARETO_EXECUTOR_V2'
SCHEMA_PATH = 'schemas/generated/cash_raise_pareto_executor_v2.schema.json'
SCHEMA_PROPERTIES = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
SCHEMA_REQUIRED = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
@dataclass(frozen=True)
class SchemaModel:
title: str
schema_id: str
path: str
properties: list[str]
required: list[str]
def load_schema() -> dict[str, Any]:
return json.loads(Path(__file__).with_suffix('.schema.json').read_text(encoding='utf-8'))
def describe() -> SchemaModel:
return SchemaModel(
title=SCHEMA_TITLE,
schema_id=SCHEMA_ID,
path=SCHEMA_PATH,
properties=list(SCHEMA_PROPERTIES),
required=list(SCHEMA_REQUIRED),
)
@@ -0,0 +1,38 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema://formula/CASH_RAISE_PARETO_EXECUTOR_V2",
"title": "CASH_RAISE_PARETO_EXECUTOR_V2",
"type": "object",
"properties": {
"formula_id": {
"const": "CASH_RAISE_PARETO_EXECUTOR_V2"
},
"owner": {
"type": "string"
},
"status": {
"type": "string"
},
"inputs": {
"type": "array",
"items": {
"type": "string"
}
},
"outputs": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"formula_id",
"owner",
"status",
"inputs",
"outputs"
],
"x_formula_inputs": [],
"x_formula_outputs": []
}
@@ -0,0 +1,33 @@
"""Auto-generated schema model descriptor."""
from __future__ import annotations
from dataclasses import dataclass
import json
from pathlib import Path
from typing import Any
SCHEMA_TITLE = 'CASH_RAISE_VALUE_OPTIMIZER_V3'
SCHEMA_ID = 'schema://formula/CASH_RAISE_VALUE_OPTIMIZER_V3'
SCHEMA_PATH = 'schemas/generated/cash_raise_value_optimizer_v3.schema.json'
SCHEMA_PROPERTIES = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
SCHEMA_REQUIRED = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
@dataclass(frozen=True)
class SchemaModel:
title: str
schema_id: str
path: str
properties: list[str]
required: list[str]
def load_schema() -> dict[str, Any]:
return json.loads(Path(__file__).with_suffix('.schema.json').read_text(encoding='utf-8'))
def describe() -> SchemaModel:
return SchemaModel(
title=SCHEMA_TITLE,
schema_id=SCHEMA_ID,
path=SCHEMA_PATH,
properties=list(SCHEMA_PROPERTIES),
required=list(SCHEMA_REQUIRED),
)
@@ -0,0 +1,38 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema://formula/CASH_RAISE_VALUE_OPTIMIZER_V3",
"title": "CASH_RAISE_VALUE_OPTIMIZER_V3",
"type": "object",
"properties": {
"formula_id": {
"const": "CASH_RAISE_VALUE_OPTIMIZER_V3"
},
"owner": {
"type": "string"
},
"status": {
"type": "string"
},
"inputs": {
"type": "array",
"items": {
"type": "string"
}
},
"outputs": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"formula_id",
"owner",
"status",
"inputs",
"outputs"
],
"x_formula_inputs": [],
"x_formula_outputs": []
}
@@ -0,0 +1,33 @@
"""Auto-generated schema model descriptor."""
from __future__ import annotations
from dataclasses import dataclass
import json
from pathlib import Path
from typing import Any
SCHEMA_TITLE = 'CASH_RATIOS_V1'
SCHEMA_ID = 'schema://formula/CASH_RATIOS_V1'
SCHEMA_PATH = 'schemas/generated/cash_ratios_v1.schema.json'
SCHEMA_PROPERTIES = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
SCHEMA_REQUIRED = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
@dataclass(frozen=True)
class SchemaModel:
title: str
schema_id: str
path: str
properties: list[str]
required: list[str]
def load_schema() -> dict[str, Any]:
return json.loads(Path(__file__).with_suffix('.schema.json').read_text(encoding='utf-8'))
def describe() -> SchemaModel:
return SchemaModel(
title=SCHEMA_TITLE,
schema_id=SCHEMA_ID,
path=SCHEMA_PATH,
properties=list(SCHEMA_PROPERTIES),
required=list(SCHEMA_REQUIRED),
)
@@ -0,0 +1,50 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema://formula/CASH_RATIOS_V1",
"title": "CASH_RATIOS_V1",
"type": "object",
"properties": {
"formula_id": {
"const": "CASH_RATIOS_V1"
},
"owner": {
"type": "string"
},
"status": {
"type": "string"
},
"inputs": {
"type": "array",
"items": {
"type": "string"
}
},
"outputs": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"formula_id",
"owner",
"status",
"inputs",
"outputs"
],
"x_formula_inputs": [
"settlement_cash",
"reserved_order_amount",
"planned_buy_amount",
"sell_cash_proceeds_d2",
"total_asset"
],
"x_formula_outputs": {
"settlement_cash_ratio": "settlement_cash / total_asset * 100",
"total_cash_ratio": "settlement_cash / total_asset * 100",
"buy_power_cash": "settlement_cash - reserved_order_amount",
"buy_power_ratio": "(settlement_cash - reserved_order_amount) / total_asset * 100",
"post_trade_total_cash_ratio": "(settlement_cash - planned_buy_amount + sell_cash_proceeds_d2) / total_asset * 100"
}
}
@@ -0,0 +1,33 @@
"""Auto-generated schema model descriptor."""
from __future__ import annotations
from dataclasses import dataclass
import json
from pathlib import Path
from typing import Any
SCHEMA_TITLE = 'CASH_RECOVERY_OPTIMIZER_V1'
SCHEMA_ID = 'schema://formula/CASH_RECOVERY_OPTIMIZER_V1'
SCHEMA_PATH = 'schemas/generated/cash_recovery_optimizer_v1.schema.json'
SCHEMA_PROPERTIES = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
SCHEMA_REQUIRED = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
@dataclass(frozen=True)
class SchemaModel:
title: str
schema_id: str
path: str
properties: list[str]
required: list[str]
def load_schema() -> dict[str, Any]:
return json.loads(Path(__file__).with_suffix('.schema.json').read_text(encoding='utf-8'))
def describe() -> SchemaModel:
return SchemaModel(
title=SCHEMA_TITLE,
schema_id=SCHEMA_ID,
path=SCHEMA_PATH,
properties=list(SCHEMA_PROPERTIES),
required=list(SCHEMA_REQUIRED),
)
@@ -0,0 +1,45 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema://formula/CASH_RECOVERY_OPTIMIZER_V1",
"title": "CASH_RECOVERY_OPTIMIZER_V1",
"type": "object",
"properties": {
"formula_id": {
"const": "CASH_RECOVERY_OPTIMIZER_V1"
},
"owner": {
"type": "string"
},
"status": {
"type": "string"
},
"inputs": {
"type": "array",
"items": {
"type": "string"
}
},
"outputs": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"formula_id",
"owner",
"status",
"inputs",
"outputs"
],
"x_formula_inputs": [
"cash_shortfall_target_krw",
"cash_shortfall_min_krw",
"sell_candidates_json",
"immediate_sell_qty",
"sell_limit_price",
"holding_qty"
],
"x_formula_outputs": []
}
@@ -0,0 +1,33 @@
"""Auto-generated schema model descriptor."""
from __future__ import annotations
from dataclasses import dataclass
import json
from pathlib import Path
from typing import Any
SCHEMA_TITLE = 'CASH_RECOVERY_OPTIMIZER_V4'
SCHEMA_ID = 'schema://formula/CASH_RECOVERY_OPTIMIZER_V4'
SCHEMA_PATH = 'schemas/generated/cash_recovery_optimizer_v4.schema.json'
SCHEMA_PROPERTIES = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
SCHEMA_REQUIRED = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
@dataclass(frozen=True)
class SchemaModel:
title: str
schema_id: str
path: str
properties: list[str]
required: list[str]
def load_schema() -> dict[str, Any]:
return json.loads(Path(__file__).with_suffix('.schema.json').read_text(encoding='utf-8'))
def describe() -> SchemaModel:
return SchemaModel(
title=SCHEMA_TITLE,
schema_id=SCHEMA_ID,
path=SCHEMA_PATH,
properties=list(SCHEMA_PROPERTIES),
required=list(SCHEMA_REQUIRED),
)
@@ -0,0 +1,38 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema://formula/CASH_RECOVERY_OPTIMIZER_V4",
"title": "CASH_RECOVERY_OPTIMIZER_V4",
"type": "object",
"properties": {
"formula_id": {
"const": "CASH_RECOVERY_OPTIMIZER_V4"
},
"owner": {
"type": "string"
},
"status": {
"type": "string"
},
"inputs": {
"type": "array",
"items": {
"type": "string"
}
},
"outputs": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"formula_id",
"owner",
"status",
"inputs",
"outputs"
],
"x_formula_inputs": [],
"x_formula_outputs": []
}
@@ -0,0 +1,33 @@
"""Auto-generated schema model descriptor."""
from __future__ import annotations
from dataclasses import dataclass
import json
from pathlib import Path
from typing import Any
SCHEMA_TITLE = 'CASH_RECOVERY_V1'
SCHEMA_ID = 'schema://formula/CASH_RECOVERY_V1'
SCHEMA_PATH = 'schemas/generated/cash_recovery_v1.schema.json'
SCHEMA_PROPERTIES = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
SCHEMA_REQUIRED = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
@dataclass(frozen=True)
class SchemaModel:
title: str
schema_id: str
path: str
properties: list[str]
required: list[str]
def load_schema() -> dict[str, Any]:
return json.loads(Path(__file__).with_suffix('.schema.json').read_text(encoding='utf-8'))
def describe() -> SchemaModel:
return SchemaModel(
title=SCHEMA_TITLE,
schema_id=SCHEMA_ID,
path=SCHEMA_PATH,
properties=list(SCHEMA_PROPERTIES),
required=list(SCHEMA_REQUIRED),
)
@@ -0,0 +1,38 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema://formula/CASH_RECOVERY_V1",
"title": "CASH_RECOVERY_V1",
"type": "object",
"properties": {
"formula_id": {
"const": "CASH_RECOVERY_V1"
},
"owner": {
"type": "string"
},
"status": {
"type": "string"
},
"inputs": {
"type": "array",
"items": {
"type": "string"
}
},
"outputs": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"formula_id",
"owner",
"status",
"inputs",
"outputs"
],
"x_formula_inputs": [],
"x_formula_outputs": []
}
@@ -0,0 +1,33 @@
"""Auto-generated schema model descriptor."""
from __future__ import annotations
from dataclasses import dataclass
import json
from pathlib import Path
from typing import Any
SCHEMA_TITLE = 'CASHFLOW_QUALITY_SIGNAL_V1'
SCHEMA_ID = 'schema://formula/CASHFLOW_QUALITY_SIGNAL_V1'
SCHEMA_PATH = 'schemas/generated/cashflow_quality_signal_v1.schema.json'
SCHEMA_PROPERTIES = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
SCHEMA_REQUIRED = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
@dataclass(frozen=True)
class SchemaModel:
title: str
schema_id: str
path: str
properties: list[str]
required: list[str]
def load_schema() -> dict[str, Any]:
return json.loads(Path(__file__).with_suffix('.schema.json').read_text(encoding='utf-8'))
def describe() -> SchemaModel:
return SchemaModel(
title=SCHEMA_TITLE,
schema_id=SCHEMA_ID,
path=SCHEMA_PATH,
properties=list(SCHEMA_PROPERTIES),
required=list(SCHEMA_REQUIRED),
)
@@ -0,0 +1,38 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema://formula/CASHFLOW_QUALITY_SIGNAL_V1",
"title": "CASHFLOW_QUALITY_SIGNAL_V1",
"type": "object",
"properties": {
"formula_id": {
"const": "CASHFLOW_QUALITY_SIGNAL_V1"
},
"owner": {
"type": "string"
},
"status": {
"type": "string"
},
"inputs": {
"type": "array",
"items": {
"type": "string"
}
},
"outputs": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"formula_id",
"owner",
"status",
"inputs",
"outputs"
],
"x_formula_inputs": [],
"x_formula_outputs": []
}
@@ -0,0 +1,33 @@
"""Auto-generated schema model descriptor."""
from __future__ import annotations
from dataclasses import dataclass
import json
from pathlib import Path
from typing import Any
SCHEMA_TITLE = 'CASHFLOW_STABILITY_GATE_V1'
SCHEMA_ID = 'schema://formula/CASHFLOW_STABILITY_GATE_V1'
SCHEMA_PATH = 'schemas/generated/cashflow_stability_gate_v1.schema.json'
SCHEMA_PROPERTIES = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
SCHEMA_REQUIRED = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
@dataclass(frozen=True)
class SchemaModel:
title: str
schema_id: str
path: str
properties: list[str]
required: list[str]
def load_schema() -> dict[str, Any]:
return json.loads(Path(__file__).with_suffix('.schema.json').read_text(encoding='utf-8'))
def describe() -> SchemaModel:
return SchemaModel(
title=SCHEMA_TITLE,
schema_id=SCHEMA_ID,
path=SCHEMA_PATH,
properties=list(SCHEMA_PROPERTIES),
required=list(SCHEMA_REQUIRED),
)
@@ -0,0 +1,42 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema://formula/CASHFLOW_STABILITY_GATE_V1",
"title": "CASHFLOW_STABILITY_GATE_V1",
"type": "object",
"properties": {
"formula_id": {
"const": "CASHFLOW_STABILITY_GATE_V1"
},
"owner": {
"type": "string"
},
"status": {
"type": "string"
},
"inputs": {
"type": "array",
"items": {
"type": "string"
}
},
"outputs": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"formula_id",
"owner",
"status",
"inputs",
"outputs"
],
"x_formula_inputs": [
"operating_cf_krw",
"free_cf_krw",
"accrual_ratio_pct"
],
"x_formula_outputs": []
}
@@ -0,0 +1,33 @@
"""Auto-generated schema model descriptor."""
from __future__ import annotations
from dataclasses import dataclass
import json
from pathlib import Path
from typing import Any
SCHEMA_TITLE = 'CLA_REGIME_EXIT_CONDITION_V1'
SCHEMA_ID = 'schema://formula/CLA_REGIME_EXIT_CONDITION_V1'
SCHEMA_PATH = 'schemas/generated/cla_regime_exit_condition_v1.schema.json'
SCHEMA_PROPERTIES = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
SCHEMA_REQUIRED = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
@dataclass(frozen=True)
class SchemaModel:
title: str
schema_id: str
path: str
properties: list[str]
required: list[str]
def load_schema() -> dict[str, Any]:
return json.loads(Path(__file__).with_suffix('.schema.json').read_text(encoding='utf-8'))
def describe() -> SchemaModel:
return SchemaModel(
title=SCHEMA_TITLE,
schema_id=SCHEMA_ID,
path=SCHEMA_PATH,
properties=list(SCHEMA_PROPERTIES),
required=list(SCHEMA_REQUIRED),
)
@@ -0,0 +1,59 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema://formula/CLA_REGIME_EXIT_CONDITION_V1",
"title": "CLA_REGIME_EXIT_CONDITION_V1",
"type": "object",
"properties": {
"formula_id": {
"const": "CLA_REGIME_EXIT_CONDITION_V1"
},
"owner": {
"type": "string"
},
"status": {
"type": "string"
},
"inputs": {
"type": "array",
"items": {
"type": "string"
}
},
"outputs": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"formula_id",
"owner",
"status",
"inputs",
"outputs"
],
"x_formula_inputs": [
"ticker",
"rs_verdict",
"brt_verdict",
"frg_5d_sh",
"volume",
"avg_volume_5d",
"market_regime"
],
"x_formula_outputs": [
{
"field": "cla_exit_status",
"unit": "enum [CLA_ACTIVE,CLA_EXIT_WARNING,CLA_EXIT_CONFIRMED]"
},
{
"field": "cla_exit_signals_triggered",
"unit": "list"
},
{
"field": "cla_exit_total_weight",
"unit": "int"
}
]
}
@@ -0,0 +1,33 @@
"""Auto-generated schema model descriptor."""
from __future__ import annotations
from dataclasses import dataclass
import json
from pathlib import Path
from typing import Any
SCHEMA_TITLE = 'COMPLETION_GAP_V1'
SCHEMA_ID = 'schema://formula/COMPLETION_GAP_V1'
SCHEMA_PATH = 'schemas/generated/completion_gap_v1.schema.json'
SCHEMA_PROPERTIES = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
SCHEMA_REQUIRED = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
@dataclass(frozen=True)
class SchemaModel:
title: str
schema_id: str
path: str
properties: list[str]
required: list[str]
def load_schema() -> dict[str, Any]:
return json.loads(Path(__file__).with_suffix('.schema.json').read_text(encoding='utf-8'))
def describe() -> SchemaModel:
return SchemaModel(
title=SCHEMA_TITLE,
schema_id=SCHEMA_ID,
path=SCHEMA_PATH,
properties=list(SCHEMA_PROPERTIES),
required=list(SCHEMA_REQUIRED),
)
@@ -0,0 +1,38 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema://formula/COMPLETION_GAP_V1",
"title": "COMPLETION_GAP_V1",
"type": "object",
"properties": {
"formula_id": {
"const": "COMPLETION_GAP_V1"
},
"owner": {
"type": "string"
},
"status": {
"type": "string"
},
"inputs": {
"type": "array",
"items": {
"type": "string"
}
},
"outputs": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"formula_id",
"owner",
"status",
"inputs",
"outputs"
],
"x_formula_inputs": [],
"x_formula_outputs": []
}
@@ -0,0 +1,33 @@
"""Auto-generated schema model descriptor."""
from __future__ import annotations
from dataclasses import dataclass
import json
from pathlib import Path
from typing import Any
SCHEMA_TITLE = 'COMPOSITE_VERDICT_V1'
SCHEMA_ID = 'schema://formula/COMPOSITE_VERDICT_V1'
SCHEMA_PATH = 'schemas/generated/composite_verdict_v1.schema.json'
SCHEMA_PROPERTIES = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
SCHEMA_REQUIRED = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
@dataclass(frozen=True)
class SchemaModel:
title: str
schema_id: str
path: str
properties: list[str]
required: list[str]
def load_schema() -> dict[str, Any]:
return json.loads(Path(__file__).with_suffix('.schema.json').read_text(encoding='utf-8'))
def describe() -> SchemaModel:
return SchemaModel(
title=SCHEMA_TITLE,
schema_id=SCHEMA_ID,
path=SCHEMA_PATH,
properties=list(SCHEMA_PROPERTIES),
required=list(SCHEMA_REQUIRED),
)
@@ -0,0 +1,41 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema://formula/COMPOSITE_VERDICT_V1",
"title": "COMPOSITE_VERDICT_V1",
"type": "object",
"properties": {
"formula_id": {
"const": "COMPOSITE_VERDICT_V1"
},
"owner": {
"type": "string"
},
"status": {
"type": "string"
},
"inputs": {
"type": "array",
"items": {
"type": "string"
}
},
"outputs": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"formula_id",
"owner",
"status",
"inputs",
"outputs"
],
"x_formula_inputs": [
"ss001_grade",
"rs_verdict"
],
"x_formula_outputs": []
}
@@ -0,0 +1,33 @@
"""Auto-generated schema model descriptor."""
from __future__ import annotations
from dataclasses import dataclass
import json
from pathlib import Path
from typing import Any
SCHEMA_TITLE = 'COMPREHENSIVE_PROPOSAL_V1'
SCHEMA_ID = 'schema://formula/COMPREHENSIVE_PROPOSAL_V1'
SCHEMA_PATH = 'schemas/generated/comprehensive_proposal_v1.schema.json'
SCHEMA_PROPERTIES = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
SCHEMA_REQUIRED = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
@dataclass(frozen=True)
class SchemaModel:
title: str
schema_id: str
path: str
properties: list[str]
required: list[str]
def load_schema() -> dict[str, Any]:
return json.loads(Path(__file__).with_suffix('.schema.json').read_text(encoding='utf-8'))
def describe() -> SchemaModel:
return SchemaModel(
title=SCHEMA_TITLE,
schema_id=SCHEMA_ID,
path=SCHEMA_PATH,
properties=list(SCHEMA_PROPERTIES),
required=list(SCHEMA_REQUIRED),
)
@@ -0,0 +1,38 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema://formula/COMPREHENSIVE_PROPOSAL_V1",
"title": "COMPREHENSIVE_PROPOSAL_V1",
"type": "object",
"properties": {
"formula_id": {
"const": "COMPREHENSIVE_PROPOSAL_V1"
},
"owner": {
"type": "string"
},
"status": {
"type": "string"
},
"inputs": {
"type": "array",
"items": {
"type": "string"
}
},
"outputs": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"formula_id",
"owner",
"status",
"inputs",
"outputs"
],
"x_formula_inputs": [],
"x_formula_outputs": []
}
@@ -0,0 +1,33 @@
"""Auto-generated schema model descriptor."""
from __future__ import annotations
from dataclasses import dataclass
import json
from pathlib import Path
from typing import Any
SCHEMA_TITLE = 'CONTINUOUS_EVALUATION_DASHBOARD_V1'
SCHEMA_ID = 'schema://formula/CONTINUOUS_EVALUATION_DASHBOARD_V1'
SCHEMA_PATH = 'schemas/generated/continuous_evaluation_dashboard_v1.schema.json'
SCHEMA_PROPERTIES = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
SCHEMA_REQUIRED = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
@dataclass(frozen=True)
class SchemaModel:
title: str
schema_id: str
path: str
properties: list[str]
required: list[str]
def load_schema() -> dict[str, Any]:
return json.loads(Path(__file__).with_suffix('.schema.json').read_text(encoding='utf-8'))
def describe() -> SchemaModel:
return SchemaModel(
title=SCHEMA_TITLE,
schema_id=SCHEMA_ID,
path=SCHEMA_PATH,
properties=list(SCHEMA_PROPERTIES),
required=list(SCHEMA_REQUIRED),
)
@@ -0,0 +1,38 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema://formula/CONTINUOUS_EVALUATION_DASHBOARD_V1",
"title": "CONTINUOUS_EVALUATION_DASHBOARD_V1",
"type": "object",
"properties": {
"formula_id": {
"const": "CONTINUOUS_EVALUATION_DASHBOARD_V1"
},
"owner": {
"type": "string"
},
"status": {
"type": "string"
},
"inputs": {
"type": "array",
"items": {
"type": "string"
}
},
"outputs": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"formula_id",
"owner",
"status",
"inputs",
"outputs"
],
"x_formula_inputs": [],
"x_formula_outputs": []
}
@@ -0,0 +1,33 @@
"""Auto-generated schema model descriptor."""
from __future__ import annotations
from dataclasses import dataclass
import json
from pathlib import Path
from typing import Any
SCHEMA_TITLE = 'CROSS_SECTION_CONSISTENCY_V1'
SCHEMA_ID = 'schema://formula/CROSS_SECTION_CONSISTENCY_V1'
SCHEMA_PATH = 'schemas/generated/cross_section_consistency_v1.schema.json'
SCHEMA_PROPERTIES = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
SCHEMA_REQUIRED = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
@dataclass(frozen=True)
class SchemaModel:
title: str
schema_id: str
path: str
properties: list[str]
required: list[str]
def load_schema() -> dict[str, Any]:
return json.loads(Path(__file__).with_suffix('.schema.json').read_text(encoding='utf-8'))
def describe() -> SchemaModel:
return SchemaModel(
title=SCHEMA_TITLE,
schema_id=SCHEMA_ID,
path=SCHEMA_PATH,
properties=list(SCHEMA_PROPERTIES),
required=list(SCHEMA_REQUIRED),
)
@@ -0,0 +1,38 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema://formula/CROSS_SECTION_CONSISTENCY_V1",
"title": "CROSS_SECTION_CONSISTENCY_V1",
"type": "object",
"properties": {
"formula_id": {
"const": "CROSS_SECTION_CONSISTENCY_V1"
},
"owner": {
"type": "string"
},
"status": {
"type": "string"
},
"inputs": {
"type": "array",
"items": {
"type": "string"
}
},
"outputs": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"formula_id",
"owner",
"status",
"inputs",
"outputs"
],
"x_formula_inputs": [],
"x_formula_outputs": []
}
@@ -0,0 +1,33 @@
"""Auto-generated schema model descriptor."""
from __future__ import annotations
from dataclasses import dataclass
import json
from pathlib import Path
from typing import Any
SCHEMA_TITLE = 'DATA_INTEGRITY_100_LOCK_V1'
SCHEMA_ID = 'schema://formula/DATA_INTEGRITY_100_LOCK_V1'
SCHEMA_PATH = 'schemas/generated/data_integrity_100_lock_v1.schema.json'
SCHEMA_PROPERTIES = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
SCHEMA_REQUIRED = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
@dataclass(frozen=True)
class SchemaModel:
title: str
schema_id: str
path: str
properties: list[str]
required: list[str]
def load_schema() -> dict[str, Any]:
return json.loads(Path(__file__).with_suffix('.schema.json').read_text(encoding='utf-8'))
def describe() -> SchemaModel:
return SchemaModel(
title=SCHEMA_TITLE,
schema_id=SCHEMA_ID,
path=SCHEMA_PATH,
properties=list(SCHEMA_PROPERTIES),
required=list(SCHEMA_REQUIRED),
)
@@ -0,0 +1,38 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema://formula/DATA_INTEGRITY_100_LOCK_V1",
"title": "DATA_INTEGRITY_100_LOCK_V1",
"type": "object",
"properties": {
"formula_id": {
"const": "DATA_INTEGRITY_100_LOCK_V1"
},
"owner": {
"type": "string"
},
"status": {
"type": "string"
},
"inputs": {
"type": "array",
"items": {
"type": "string"
}
},
"outputs": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"formula_id",
"owner",
"status",
"inputs",
"outputs"
],
"x_formula_inputs": [],
"x_formula_outputs": []
}
@@ -0,0 +1,33 @@
"""Auto-generated schema model descriptor."""
from __future__ import annotations
from dataclasses import dataclass
import json
from pathlib import Path
from typing import Any
SCHEMA_TITLE = 'DATA_INTEGRITY_100_LOCK_V2'
SCHEMA_ID = 'schema://formula/DATA_INTEGRITY_100_LOCK_V2'
SCHEMA_PATH = 'schemas/generated/data_integrity_100_lock_v2.schema.json'
SCHEMA_PROPERTIES = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
SCHEMA_REQUIRED = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
@dataclass(frozen=True)
class SchemaModel:
title: str
schema_id: str
path: str
properties: list[str]
required: list[str]
def load_schema() -> dict[str, Any]:
return json.loads(Path(__file__).with_suffix('.schema.json').read_text(encoding='utf-8'))
def describe() -> SchemaModel:
return SchemaModel(
title=SCHEMA_TITLE,
schema_id=SCHEMA_ID,
path=SCHEMA_PATH,
properties=list(SCHEMA_PROPERTIES),
required=list(SCHEMA_REQUIRED),
)
@@ -0,0 +1,38 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema://formula/DATA_INTEGRITY_100_LOCK_V2",
"title": "DATA_INTEGRITY_100_LOCK_V2",
"type": "object",
"properties": {
"formula_id": {
"const": "DATA_INTEGRITY_100_LOCK_V2"
},
"owner": {
"type": "string"
},
"status": {
"type": "string"
},
"inputs": {
"type": "array",
"items": {
"type": "string"
}
},
"outputs": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"formula_id",
"owner",
"status",
"inputs",
"outputs"
],
"x_formula_inputs": [],
"x_formula_outputs": []
}
@@ -0,0 +1,33 @@
"""Auto-generated schema model descriptor."""
from __future__ import annotations
from dataclasses import dataclass
import json
from pathlib import Path
from typing import Any
SCHEMA_TITLE = 'DATA_INTEGRITY_SCORE_V1'
SCHEMA_ID = 'schema://formula/DATA_INTEGRITY_SCORE_V1'
SCHEMA_PATH = 'schemas/generated/data_integrity_score_v1.schema.json'
SCHEMA_PROPERTIES = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
SCHEMA_REQUIRED = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
@dataclass(frozen=True)
class SchemaModel:
title: str
schema_id: str
path: str
properties: list[str]
required: list[str]
def load_schema() -> dict[str, Any]:
return json.loads(Path(__file__).with_suffix('.schema.json').read_text(encoding='utf-8'))
def describe() -> SchemaModel:
return SchemaModel(
title=SCHEMA_TITLE,
schema_id=SCHEMA_ID,
path=SCHEMA_PATH,
properties=list(SCHEMA_PROPERTIES),
required=list(SCHEMA_REQUIRED),
)
@@ -0,0 +1,38 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema://formula/DATA_INTEGRITY_SCORE_V1",
"title": "DATA_INTEGRITY_SCORE_V1",
"type": "object",
"properties": {
"formula_id": {
"const": "DATA_INTEGRITY_SCORE_V1"
},
"owner": {
"type": "string"
},
"status": {
"type": "string"
},
"inputs": {
"type": "array",
"items": {
"type": "string"
}
},
"outputs": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"formula_id",
"owner",
"status",
"inputs",
"outputs"
],
"x_formula_inputs": [],
"x_formula_outputs": []
}
@@ -0,0 +1,33 @@
"""Auto-generated schema model descriptor."""
from __future__ import annotations
from dataclasses import dataclass
import json
from pathlib import Path
from typing import Any
SCHEMA_TITLE = 'DATA_MATURITY_TRUTH_GATE_V1'
SCHEMA_ID = 'schema://formula/DATA_MATURITY_TRUTH_GATE_V1'
SCHEMA_PATH = 'schemas/generated/data_maturity_truth_gate_v1.schema.json'
SCHEMA_PROPERTIES = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
SCHEMA_REQUIRED = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
@dataclass(frozen=True)
class SchemaModel:
title: str
schema_id: str
path: str
properties: list[str]
required: list[str]
def load_schema() -> dict[str, Any]:
return json.loads(Path(__file__).with_suffix('.schema.json').read_text(encoding='utf-8'))
def describe() -> SchemaModel:
return SchemaModel(
title=SCHEMA_TITLE,
schema_id=SCHEMA_ID,
path=SCHEMA_PATH,
properties=list(SCHEMA_PROPERTIES),
required=list(SCHEMA_REQUIRED),
)
@@ -0,0 +1,38 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema://formula/DATA_MATURITY_TRUTH_GATE_V1",
"title": "DATA_MATURITY_TRUTH_GATE_V1",
"type": "object",
"properties": {
"formula_id": {
"const": "DATA_MATURITY_TRUTH_GATE_V1"
},
"owner": {
"type": "string"
},
"status": {
"type": "string"
},
"inputs": {
"type": "array",
"items": {
"type": "string"
}
},
"outputs": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"formula_id",
"owner",
"status",
"inputs",
"outputs"
],
"x_formula_inputs": [],
"x_formula_outputs": []
}
@@ -0,0 +1,33 @@
"""Auto-generated schema model descriptor."""
from __future__ import annotations
from dataclasses import dataclass
import json
from pathlib import Path
from typing import Any
SCHEMA_TITLE = 'DATA_MATURITY_TRUTH_GATE_VALIDATOR_V1'
SCHEMA_ID = 'schema://formula/DATA_MATURITY_TRUTH_GATE_VALIDATOR_V1'
SCHEMA_PATH = 'schemas/generated/data_maturity_truth_gate_validator_v1.schema.json'
SCHEMA_PROPERTIES = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
SCHEMA_REQUIRED = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
@dataclass(frozen=True)
class SchemaModel:
title: str
schema_id: str
path: str
properties: list[str]
required: list[str]
def load_schema() -> dict[str, Any]:
return json.loads(Path(__file__).with_suffix('.schema.json').read_text(encoding='utf-8'))
def describe() -> SchemaModel:
return SchemaModel(
title=SCHEMA_TITLE,
schema_id=SCHEMA_ID,
path=SCHEMA_PATH,
properties=list(SCHEMA_PROPERTIES),
required=list(SCHEMA_REQUIRED),
)
@@ -0,0 +1,38 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema://formula/DATA_MATURITY_TRUTH_GATE_VALIDATOR_V1",
"title": "DATA_MATURITY_TRUTH_GATE_VALIDATOR_V1",
"type": "object",
"properties": {
"formula_id": {
"const": "DATA_MATURITY_TRUTH_GATE_VALIDATOR_V1"
},
"owner": {
"type": "string"
},
"status": {
"type": "string"
},
"inputs": {
"type": "array",
"items": {
"type": "string"
}
},
"outputs": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"formula_id",
"owner",
"status",
"inputs",
"outputs"
],
"x_formula_inputs": [],
"x_formula_outputs": []
}
@@ -0,0 +1,33 @@
"""Auto-generated schema model descriptor."""
from __future__ import annotations
from dataclasses import dataclass
import json
from pathlib import Path
from typing import Any
SCHEMA_TITLE = 'DATA_QUALITY_GATE_V2_PY'
SCHEMA_ID = 'schema://formula/DATA_QUALITY_GATE_V2_PY'
SCHEMA_PATH = 'schemas/generated/data_quality_gate_v2_py.schema.json'
SCHEMA_PROPERTIES = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
SCHEMA_REQUIRED = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
@dataclass(frozen=True)
class SchemaModel:
title: str
schema_id: str
path: str
properties: list[str]
required: list[str]
def load_schema() -> dict[str, Any]:
return json.loads(Path(__file__).with_suffix('.schema.json').read_text(encoding='utf-8'))
def describe() -> SchemaModel:
return SchemaModel(
title=SCHEMA_TITLE,
schema_id=SCHEMA_ID,
path=SCHEMA_PATH,
properties=list(SCHEMA_PROPERTIES),
required=list(SCHEMA_REQUIRED),
)
@@ -0,0 +1,38 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema://formula/DATA_QUALITY_GATE_V2_PY",
"title": "DATA_QUALITY_GATE_V2_PY",
"type": "object",
"properties": {
"formula_id": {
"const": "DATA_QUALITY_GATE_V2_PY"
},
"owner": {
"type": "string"
},
"status": {
"type": "string"
},
"inputs": {
"type": "array",
"items": {
"type": "string"
}
},
"outputs": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"formula_id",
"owner",
"status",
"inputs",
"outputs"
],
"x_formula_inputs": [],
"x_formula_outputs": []
}
@@ -0,0 +1,33 @@
"""Auto-generated schema model descriptor."""
from __future__ import annotations
from dataclasses import dataclass
import json
from pathlib import Path
from typing import Any
SCHEMA_TITLE = 'DATA_QUALITY_GATE_V3'
SCHEMA_ID = 'schema://formula/DATA_QUALITY_GATE_V3'
SCHEMA_PATH = 'schemas/generated/data_quality_gate_v3.schema.json'
SCHEMA_PROPERTIES = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
SCHEMA_REQUIRED = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
@dataclass(frozen=True)
class SchemaModel:
title: str
schema_id: str
path: str
properties: list[str]
required: list[str]
def load_schema() -> dict[str, Any]:
return json.loads(Path(__file__).with_suffix('.schema.json').read_text(encoding='utf-8'))
def describe() -> SchemaModel:
return SchemaModel(
title=SCHEMA_TITLE,
schema_id=SCHEMA_ID,
path=SCHEMA_PATH,
properties=list(SCHEMA_PROPERTIES),
required=list(SCHEMA_REQUIRED),
)
@@ -0,0 +1,38 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema://formula/DATA_QUALITY_GATE_V3",
"title": "DATA_QUALITY_GATE_V3",
"type": "object",
"properties": {
"formula_id": {
"const": "DATA_QUALITY_GATE_V3"
},
"owner": {
"type": "string"
},
"status": {
"type": "string"
},
"inputs": {
"type": "array",
"items": {
"type": "string"
}
},
"outputs": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"formula_id",
"owner",
"status",
"inputs",
"outputs"
],
"x_formula_inputs": [],
"x_formula_outputs": []
}
@@ -0,0 +1,33 @@
"""Auto-generated schema model descriptor."""
from __future__ import annotations
from dataclasses import dataclass
import json
from pathlib import Path
from typing import Any
SCHEMA_TITLE = 'DETERMINISTIC_ROUTING_ENGINE_V1'
SCHEMA_ID = 'schema://formula/DETERMINISTIC_ROUTING_ENGINE_V1'
SCHEMA_PATH = 'schemas/generated/deterministic_routing_engine_v1.schema.json'
SCHEMA_PROPERTIES = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
SCHEMA_REQUIRED = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
@dataclass(frozen=True)
class SchemaModel:
title: str
schema_id: str
path: str
properties: list[str]
required: list[str]
def load_schema() -> dict[str, Any]:
return json.loads(Path(__file__).with_suffix('.schema.json').read_text(encoding='utf-8'))
def describe() -> SchemaModel:
return SchemaModel(
title=SCHEMA_TITLE,
schema_id=SCHEMA_ID,
path=SCHEMA_PATH,
properties=list(SCHEMA_PROPERTIES),
required=list(SCHEMA_REQUIRED),
)
@@ -0,0 +1,40 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema://formula/DETERMINISTIC_ROUTING_ENGINE_V1",
"title": "DETERMINISTIC_ROUTING_ENGINE_V1",
"type": "object",
"properties": {
"formula_id": {
"const": "DETERMINISTIC_ROUTING_ENGINE_V1"
},
"owner": {
"type": "string"
},
"status": {
"type": "string"
},
"inputs": {
"type": "array",
"items": {
"type": "string"
}
},
"outputs": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"formula_id",
"owner",
"status",
"inputs",
"outputs"
],
"x_formula_inputs": [
"harness_context"
],
"x_formula_outputs": []
}
@@ -0,0 +1,33 @@
"""Auto-generated schema model descriptor."""
from __future__ import annotations
from dataclasses import dataclass
import json
from pathlib import Path
from typing import Any
SCHEMA_TITLE = 'DISTRIBUTION_SELL_DETECTOR_V1'
SCHEMA_ID = 'schema://formula/DISTRIBUTION_SELL_DETECTOR_V1'
SCHEMA_PATH = 'schemas/generated/distribution_sell_detector_v1.schema.json'
SCHEMA_PROPERTIES = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
SCHEMA_REQUIRED = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
@dataclass(frozen=True)
class SchemaModel:
title: str
schema_id: str
path: str
properties: list[str]
required: list[str]
def load_schema() -> dict[str, Any]:
return json.loads(Path(__file__).with_suffix('.schema.json').read_text(encoding='utf-8'))
def describe() -> SchemaModel:
return SchemaModel(
title=SCHEMA_TITLE,
schema_id=SCHEMA_ID,
path=SCHEMA_PATH,
properties=list(SCHEMA_PROPERTIES),
required=list(SCHEMA_REQUIRED),
)
@@ -0,0 +1,49 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema://formula/DISTRIBUTION_SELL_DETECTOR_V1",
"title": "DISTRIBUTION_SELL_DETECTOR_V1",
"type": "object",
"properties": {
"formula_id": {
"const": "DISTRIBUTION_SELL_DETECTOR_V1"
},
"owner": {
"type": "string"
},
"status": {
"type": "string"
},
"inputs": {
"type": "array",
"items": {
"type": "string"
}
},
"outputs": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"formula_id",
"owner",
"status",
"inputs",
"outputs"
],
"x_formula_inputs": [
"close",
"high52w",
"avg_volume_5d",
"volume",
"ret5d",
"flow_credit",
"frg_5d_sh",
"inst_5d_sh",
"rsi14",
"obv_slope_20d"
],
"x_formula_outputs": []
}
@@ -0,0 +1,33 @@
"""Auto-generated schema model descriptor."""
from __future__ import annotations
from dataclasses import dataclass
import json
from pathlib import Path
from typing import Any
SCHEMA_TITLE = 'DIVERGENCE_SCORE_V1'
SCHEMA_ID = 'schema://formula/DIVERGENCE_SCORE_V1'
SCHEMA_PATH = 'schemas/generated/divergence_score_v1.schema.json'
SCHEMA_PROPERTIES = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
SCHEMA_REQUIRED = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
@dataclass(frozen=True)
class SchemaModel:
title: str
schema_id: str
path: str
properties: list[str]
required: list[str]
def load_schema() -> dict[str, Any]:
return json.loads(Path(__file__).with_suffix('.schema.json').read_text(encoding='utf-8'))
def describe() -> SchemaModel:
return SchemaModel(
title=SCHEMA_TITLE,
schema_id=SCHEMA_ID,
path=SCHEMA_PATH,
properties=list(SCHEMA_PROPERTIES),
required=list(SCHEMA_REQUIRED),
)

Some files were not shown because too many files have changed in this diff Show More