3년 sim 기반 sizing_rules를 저장소에 포함하고, live 매수 시 수수료 lock과 5000원 잔여 현금을 확보하도록 운영 기본값을 갱신한다. Co-authored-by: Cursor <cursoragent@cursor.com>
98 lines
3.1 KiB
Python
98 lines
3.1 KiB
Python
#!/usr/bin/env python3
|
|
"""1단계: 연속 매수·매도 클러스터별 매수·매도 비율 튜닝 (타점 고정)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import logging
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
SRC = ROOT / "src"
|
|
if str(SRC) not in sys.path:
|
|
sys.path.insert(0, str(SRC))
|
|
|
|
from bithumb.config import load_settings
|
|
from bithumb.ground_truth.sizing_rules import save_sizing_rules
|
|
from bithumb.ground_truth.sizing_tune import tune_sizing_rules
|
|
from bithumb.operations.signal_pipeline import run_signal_pipeline
|
|
|
|
|
|
def _configure_logging(verbose: bool) -> None:
|
|
level = logging.DEBUG if verbose else logging.INFO
|
|
logging.basicConfig(
|
|
level=level,
|
|
format="%(asctime)s [%(levelname)s] %(message)s",
|
|
datefmt="%Y-%m-%d %H:%M:%S",
|
|
)
|
|
|
|
|
|
def main() -> int:
|
|
"""CLI 진입점."""
|
|
parser = argparse.ArgumentParser(
|
|
description="연속 매수·매도 클러스터 상태별 사이징 비율 학습",
|
|
)
|
|
parser.add_argument(
|
|
"-o",
|
|
"--output",
|
|
type=str,
|
|
default=None,
|
|
help="규칙 JSON 경로 (기본: OPS_SIZING_RULES_JSON)",
|
|
)
|
|
parser.add_argument(
|
|
"--min-bucket-samples",
|
|
type=int,
|
|
default=5,
|
|
help="클러스터 버킷별 최소 샘플 수",
|
|
)
|
|
parser.add_argument("-v", "--verbose", action="store_true")
|
|
args = parser.parse_args()
|
|
_configure_logging(args.verbose)
|
|
|
|
settings = load_settings()
|
|
output_path = Path(args.output) if args.output else settings.ops_sizing_rules_json
|
|
if not output_path.is_absolute():
|
|
output_path = ROOT / output_path
|
|
|
|
print("\n=== 매수·매도 비율 튜닝 (타점 고정) ===", flush=True)
|
|
print(
|
|
f"기법: {settings.ops_technique_id} · sim {settings.gt_sim_lookback_days}일 · "
|
|
f"일 체결 상한 {settings.ops_daily_max_trades} · "
|
|
f"튜닝 시드 {settings.ops_buy_cash_pct:.0%}/{settings.ops_sell_coin_pct:.0%}",
|
|
flush=True,
|
|
)
|
|
|
|
pipeline = run_signal_pipeline(settings, use_cache=True)
|
|
rules, final_sim = tune_sizing_rules(
|
|
settings,
|
|
pipeline["kept"],
|
|
data_end=pipeline["data_end"],
|
|
last_mark_price=pipeline["last_price"],
|
|
technique_id=pipeline["technique_id"],
|
|
min_bucket_samples=args.min_bucket_samples,
|
|
)
|
|
save_sizing_rules(rules, output_path)
|
|
|
|
tuning = rules.get("tuning") or {}
|
|
print(f"\n기준(고정 10%): {tuning.get('baseline_return_pct'):+.2f}%")
|
|
print(
|
|
f"학습 후: {final_sim.get('total_return_pct'):+.2f}% · "
|
|
f"매수/매도 {final_sim.get('buys_executed')}/{final_sim.get('sells_executed')}"
|
|
)
|
|
print(
|
|
f"전역 비율: 매수 {rules.get('default_buy_cash_pct', 0):.0%} · "
|
|
f"매도 {rules.get('default_sell_coin_pct', 0):.0%}"
|
|
)
|
|
by_cluster = rules.get("by_cluster") or {}
|
|
if by_cluster.get("buy") or by_cluster.get("sell"):
|
|
print("클러스터별:")
|
|
print(json.dumps(by_cluster, ensure_ascii=False, indent=2))
|
|
print(f"\n규칙 JSON: {output_path}")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|