refactor: 프로젝트명 bithumb으로 변경 및 futures 파이프라인 제거
deepcoin 패키지를 bithumb으로 rename하고, 3단계 live 운영·사이징 튜닝·텔레그램 알림을 통합한다. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
97
src/bithumb/ground_truth/sizing_rules.py
Normal file
97
src/bithumb/ground_truth/sizing_rules.py
Normal file
@@ -0,0 +1,97 @@
|
||||
"""연속 매수·매도 클러스터 상태별 매수·매도 비율 규칙."""
|
||||
|
||||
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 {},
|
||||
}
|
||||
Reference in New Issue
Block a user