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:
@@ -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())
|
||||
@@ -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
@@ -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())
|
||||
@@ -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
@@ -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())
|
||||
@@ -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),
|
||||
)
|
||||
+38
@@ -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),
|
||||
)
|
||||
+38
@@ -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
Reference in New Issue
Block a user