From be9ea53875e4be283c27e4c01490bdd118ef5e43 Mon Sep 17 00:00:00 2001 From: dsyoon Date: Sat, 13 Jun 2026 18:07:34 +0900 Subject: [PATCH] =?UTF-8?q?feat(ops):=20sim=20=ED=8A=9C=EB=8B=9D=20?= =?UTF-8?q?=EC=82=AC=EC=9D=B4=EC=A7=95=C2=B7=EC=9D=BC=20=EC=B2=B4=EA=B2=B0?= =?UTF-8?q?=2010000=C2=B7=EB=A7=A4=EC=88=98=20=EC=95=88=EC=A0=84=EB=B2=84?= =?UTF-8?q?=ED=8D=BC=205000=EC=9B=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 3년 sim 기반 sizing_rules를 저장소에 포함하고, live 매수 시 수수료 lock과 5000원 잔여 현금을 확보하도록 운영 기본값을 갱신한다. Co-authored-by: Cursor --- .env.example | 10 +-- .gitignore | 3 + data/spot/operations/sizing_rules.json | 94 ++++++++++++++++++++++++++ scripts/1_tune_order_sizing.py | 3 +- src/bithumb/config.py | 2 +- src/bithumb/operations/trade_engine.py | 8 +-- 6 files changed, 109 insertions(+), 11 deletions(-) create mode 100644 data/spot/operations/sizing_rules.json diff --git a/.env.example b/.env.example index 6aef138..dbe1be0 100644 --- a/.env.example +++ b/.env.example @@ -75,16 +75,16 @@ OPS_MODE=paper OPS_TECHNIQUE_ID=fractal_swing OPS_MTF_ENABLED=false OPS_TREND_GATE_ENABLED=false -OPS_DAILY_MAX_TRADES=100 +OPS_DAILY_MAX_TRADES=10000 OPS_MIN_ORDER_KRW=5000 -# 1회 매수·매도 분할 (0.10=총평가/보유의 10%, 20만원→약 2만원/회) +# 튜닝 기준값 — 실제 비율은 1_tune_order_sizing.py → sizing_rules.json OPS_BUY_CASH_PCT=0.10 OPS_SELL_COIN_PCT=0.10 OPS_SLIPPAGE_RATE=0.0005 -# 빗썸 시장가 매수 시 주문금액+예약수수료 lock (전액 주문 400 방지) +# 빗썸 시장가 매수: 주문금액+예약수수료 lock (insufficient_funds 방지) OPS_EXCHANGE_FEE_LOCK_RATE=0.0025 -# live 시장가 매수 안전 여유: floor(가용/(1+lock)) - N원 -OPS_BUY_SAFETY_BUFFER_KRW=1000 +# live 매수 상한: floor(가용/(1+lock)) - N원 — N=최소 잔여 현금(수수료 lock 별도) +OPS_BUY_SAFETY_BUFFER_KRW=5000 OPS_ORDER_INTERVAL_SEC=0.35 OPS_SYNC_CANDLES=true # 비우면 DOWNLOAD_INTERVALS 전체 증분 sync diff --git a/.gitignore b/.gitignore index 7b8fc8b..91ec39c 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,9 @@ !/data/spot/mtf/ /data/spot/mtf/** !data/spot/mtf/mtf_rules_v3.json +!/data/spot/operations/ +/data/spot/operations/** +!data/spot/operations/sizing_rules.json /docs/** !/docs/spot/ /docs/spot/** diff --git a/data/spot/operations/sizing_rules.json b/data/spot/operations/sizing_rules.json new file mode 100644 index 0000000..547fc96 --- /dev/null +++ b/data/spot/operations/sizing_rules.json @@ -0,0 +1,94 @@ +{ + "generated_at": "2026-06-13 18:04:03", + "technique_id": "fractal_swing", + "symbol": "BTC", + "default_buy_cash_pct": 1.0, + "default_sell_coin_pct": 1.0, + "by_cluster": { + "buy": { + "1": 1.0, + "2": 0.7 + }, + "sell": { + "1": 1.0, + "2": 1.0 + } + }, + "tuning": { + "objective": "total_return_pct", + "pct_candidates": [ + 0.1, + 0.15, + 0.2, + 0.25, + 0.3, + 0.4, + 0.5, + 0.6, + 0.7, + 0.8, + 1.0 + ], + "min_bucket_samples": 5, + "cluster_counts": { + "buy": { + "1": 56440, + "2": 185 + }, + "sell": { + "1": 56441, + "2": 184 + } + }, + "history": [ + { + "step": "global_buy", + "buy_pct": 1.0, + "return_pct": 83276.93 + }, + { + "step": "global_sell", + "sell_pct": 1.0, + "return_pct": 2307591.86 + }, + { + "step": "refine_buy", + "buy_pct": 1.0, + "return_pct": 2307591.86 + }, + { + "step": "refine_sell", + "sell_pct": 1.0, + "return_pct": 2307591.86 + }, + { + "step": "bucket_buy_1", + "pct": 1.0, + "samples": 56440, + "return_pct": 2307591.86 + }, + { + "step": "bucket_buy_2", + "pct": 0.7, + "samples": 185, + "return_pct": 2312689.34 + }, + { + "step": "bucket_sell_1", + "pct": 1.0, + "samples": 56441, + "return_pct": 2312689.34 + }, + { + "step": "bucket_sell_2", + "pct": 1.0, + "samples": 184, + "return_pct": 2312689.34 + } + ], + "baseline_return_pct": 3907.55, + "final_return_pct": 2312689.34, + "final_buys_executed": 56810, + "final_sells_executed": 56808 + } +} \ No newline at end of file diff --git a/scripts/1_tune_order_sizing.py b/scripts/1_tune_order_sizing.py index 7b26a0c..0524ca0 100644 --- a/scripts/1_tune_order_sizing.py +++ b/scripts/1_tune_order_sizing.py @@ -59,7 +59,8 @@ def main() -> int: print("\n=== 매수·매도 비율 튜닝 (타점 고정) ===", flush=True) print( f"기법: {settings.ops_technique_id} · sim {settings.gt_sim_lookback_days}일 · " - f"기본 {settings.ops_buy_cash_pct:.0%}/{settings.ops_sell_coin_pct:.0%}", + f"일 체결 상한 {settings.ops_daily_max_trades} · " + f"튜닝 시드 {settings.ops_buy_cash_pct:.0%}/{settings.ops_sell_coin_pct:.0%}", flush=True, ) diff --git a/src/bithumb/config.py b/src/bithumb/config.py index 75d0cfc..da73a78 100644 --- a/src/bithumb/config.py +++ b/src/bithumb/config.py @@ -276,7 +276,7 @@ def load_settings(env_path: Path | None = None) -> Settings: os.getenv("OPS_EXCHANGE_FEE_LOCK_RATE", "0.0025") ), ops_buy_safety_buffer_krw=float( - os.getenv("OPS_BUY_SAFETY_BUFFER_KRW", "1000") + os.getenv("OPS_BUY_SAFETY_BUFFER_KRW", "5000") ), ops_buy_cash_pct=float(os.getenv("OPS_BUY_CASH_PCT", "0.10")), ops_sell_coin_pct=float(os.getenv("OPS_SELL_COIN_PCT", "0.10")), diff --git a/src/bithumb/operations/trade_engine.py b/src/bithumb/operations/trade_engine.py index f536c1e..88e0640 100644 --- a/src/bithumb/operations/trade_engine.py +++ b/src/bithumb/operations/trade_engine.py @@ -44,11 +44,11 @@ def spendable_cash_for_exchange_buy( fee_lock_rate: float = 0.0025, safety_buffer_krw: float = 1000.0, ) -> float: - """거래소 시장가 매수 시 주문 가능 원화 (수수료 lock + floor + 안전 여유). + """거래소 시장가 매수 시 주문 가능 원화 (예약수수료 lock + 최소 잔여 현금). - 빗썸은 주문금액 + 예약수수료를 KRW에서 동시에 lock하므로 - 가용 원화 전액을 price로 넣으면 insufficient_funds(400)가 발생한다. - ``floor(가용/(1+lock)) - safety_buffer_krw`` 로 주문 상한을 계산한다. + 빗썸은 주문금액 + 예약수수료(fee_lock_rate)를 KRW에서 동시에 lock한다. + ``floor(가용/(1+lock)) - safety_buffer_krw`` 로 주문 상한을 계산하며, + safety_buffer_krw(기본 5000)는 매수 후에도 남길 최소 현금(원)이다. """ cash = max(float(cash_krw), 0.0) lock = max(float(fee_lock_rate), 0.0)