diff --git a/tools/validate_execution_simulator_v1.py b/tools/validate_execution_simulator_v1.py index 52a31dd..1d99543 100644 --- a/tools/validate_execution_simulator_v1.py +++ b/tools/validate_execution_simulator_v1.py @@ -36,6 +36,32 @@ TICK_TABLE = [ ] TICK_ABOVE_2M = 1_000 +# Load names from GatherTradingData.json to detect ETF or US Stocks +TICKER_NAMES: dict[str, str] = {} +gtd_path = ROOT / "GatherTradingData.json" +if gtd_path.exists(): + try: + gtd = json.loads(gtd_path.read_text(encoding="utf-8")) + df_rows = gtd.get("data", {}).get("data_feed") or [] + for r in df_rows: + if isinstance(r, dict) and r.get("Ticker"): + TICKER_NAMES[str(r["Ticker"])] = str(r.get("Name") or "") + univ_rows = gtd.get("data", {}).get("universe") or [] + for r in univ_rows: + if isinstance(r, dict) and r.get("Ticker"): + TICKER_NAMES[str(r["Ticker"])] = str(r.get("Name") or "") + except Exception: + pass + + +def _is_etf(ticker: str) -> bool: + name = TICKER_NAMES.get(ticker, "").upper() + return any(x in name for x in ["KODEX", "TIGER", "KBSTAR", "ARIRANG", "HANARO", "TIMEFOLIO", "SOL", "ACE", "PLUS"]) + + +def _is_us_stock(ticker: str) -> bool: + return str(ticker).isalpha() + def _load_json(path: Path) -> dict: if not path.exists(): @@ -46,15 +72,19 @@ def _load_json(path: Path) -> dict: return {"_error": str(e), "_path": str(path)} -def _tick_size(price: float) -> int: +def _tick_size(price: float, ticker: str) -> float: + if _is_us_stock(ticker): + return 0.01 + if _is_etf(ticker): + return 5.0 for threshold, tick in TICK_TABLE: if price < threshold: return tick return TICK_ABOVE_2M -def _normalize_tick(price: float) -> float: - tick = _tick_size(price) +def _normalize_tick(price: float, ticker: str) -> float: + tick = _tick_size(price, ticker) return math.floor(price / tick) * tick @@ -95,10 +125,11 @@ def _simulate_order(order: dict) -> dict: qty = int(order.get("quantity") or order.get("qty") or 0) action = str(order.get("action") or order.get("order_type") or "").upper() side = "BUY" if "BUY" in action else "SELL" + ticker = str(order.get("ticker") or "") # Tick normalization check if price > 0: - normalized = _normalize_tick(price) + normalized = _normalize_tick(price, ticker) if abs(normalized - price) > 0.01: errors.append(f"TICK_INVALID: price={price} → normalized={normalized}") else: @@ -113,11 +144,11 @@ def _simulate_order(order: dict) -> dict: order_amount = slippage_price * qty return { - "ticker": order.get("ticker"), + "ticker": ticker, "action": action, "original_price": price, - "normalized_price": _normalize_tick(price) if price > 0 else 0, - "slippage_adjusted_price": round(slippage_price, 0), + "normalized_price": _normalize_tick(price, ticker) if price > 0 else 0, + "slippage_adjusted_price": round(slippage_price, 2) if _is_us_stock(ticker) else round(slippage_price, 0), "quantity": qty, "order_amount_krw": round(order_amount), "errors": errors,