"""연속 매수·매도 클러스터 상태별 매수·매도 비율 규칙.""" from __future__ import annotations import json from datetime import datetime from pathlib import Path from typing import Any CLUSTER_SIZE_KEYS: tuple[str, ...] = ("1", "2", "3+") def cluster_size_key(cluster_size: int) -> str: """클러스터 크기를 규칙 lookup 키로 변환한다.""" size = max(int(cluster_size), 1) if size >= 3: return "3+" return str(size) def resolve_buy_cash_pct( rules: dict[str, Any] | None, cluster_size: int, default: float | None, ) -> float | None: """클러스터 상태에 맞는 1회 매수 현금 비율을 반환한다.""" if not rules: return default by_side = (rules.get("by_cluster") or {}).get("buy") or {} key = cluster_size_key(cluster_size) if key in by_side: return float(by_side[key]) fallback = rules.get("default_buy_cash_pct") if fallback is not None: return float(fallback) return default def resolve_sell_coin_pct( rules: dict[str, Any] | None, cluster_size: int, default: float | None, ) -> float | None: """클러스터 상태에 맞는 1회 매도 코인 비율을 반환한다.""" if not rules: return default by_side = (rules.get("by_cluster") or {}).get("sell") or {} key = cluster_size_key(cluster_size) if key in by_side: return float(by_side[key]) fallback = rules.get("default_sell_coin_pct") if fallback is not None: return float(fallback) return default def load_sizing_rules(path: Path) -> dict[str, Any] | None: """JSON 사이징 규칙을 로드한다. 파일이 없으면 None.""" if not path.exists(): return None with path.open(encoding="utf-8") as fp: data = json.load(fp) return data if isinstance(data, dict) else None def save_sizing_rules(rules: dict[str, Any], path: Path) -> Path: """사이징 규칙 JSON 저장.""" path.parent.mkdir(parents=True, exist_ok=True) with path.open("w", encoding="utf-8") as fp: json.dump(rules, fp, ensure_ascii=False, indent=2) return path def empty_by_cluster() -> dict[str, dict[str, float]]: """빈 by_cluster 구조.""" return {"buy": {}, "sell": {}} def merge_rules( *, default_buy: float, default_sell: float, by_cluster: dict[str, dict[str, float]] | None = None, technique_id: str = "", symbol: str = "", tuning: dict[str, Any] | None = None, ) -> dict[str, Any]: """튜닝 결과 dict를 표준 규칙 형식으로 만든다.""" return { "generated_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "technique_id": technique_id, "symbol": symbol, "default_buy_cash_pct": round(float(default_buy), 4), "default_sell_coin_pct": round(float(default_sell), 4), "by_cluster": by_cluster or empty_by_cluster(), "tuning": tuning or {}, }