feat(ops): sim 튜닝 사이징·일 체결 10000·매수 안전버퍼 5000원

3년 sim 기반 sizing_rules를 저장소에 포함하고, live 매수 시 수수료 lock과 5000원 잔여 현금을 확보하도록 운영 기본값을 갱신한다.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dsyoon
2026-06-13 18:07:34 +09:00
parent c3334e4f77
commit be9ea53875
6 changed files with 109 additions and 11 deletions

View File

@@ -75,16 +75,16 @@ OPS_MODE=paper
OPS_TECHNIQUE_ID=fractal_swing OPS_TECHNIQUE_ID=fractal_swing
OPS_MTF_ENABLED=false OPS_MTF_ENABLED=false
OPS_TREND_GATE_ENABLED=false OPS_TREND_GATE_ENABLED=false
OPS_DAILY_MAX_TRADES=100 OPS_DAILY_MAX_TRADES=10000
OPS_MIN_ORDER_KRW=5000 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_BUY_CASH_PCT=0.10
OPS_SELL_COIN_PCT=0.10 OPS_SELL_COIN_PCT=0.10
OPS_SLIPPAGE_RATE=0.0005 OPS_SLIPPAGE_RATE=0.0005
# 빗썸 시장가 매수 주문금액+예약수수료 lock (전액 주문 400 방지) # 빗썸 시장가 매수: 주문금액+예약수수료 lock (insufficient_funds 방지)
OPS_EXCHANGE_FEE_LOCK_RATE=0.0025 OPS_EXCHANGE_FEE_LOCK_RATE=0.0025
# live 시장가 매수 안전 여유: floor(가용/(1+lock)) - N원 # live 매수 상한: floor(가용/(1+lock)) - N원 — N=최소 잔여 현금(수수료 lock 별도)
OPS_BUY_SAFETY_BUFFER_KRW=1000 OPS_BUY_SAFETY_BUFFER_KRW=5000
OPS_ORDER_INTERVAL_SEC=0.35 OPS_ORDER_INTERVAL_SEC=0.35
OPS_SYNC_CANDLES=true OPS_SYNC_CANDLES=true
# 비우면 DOWNLOAD_INTERVALS 전체 증분 sync # 비우면 DOWNLOAD_INTERVALS 전체 증분 sync

3
.gitignore vendored
View File

@@ -10,6 +10,9 @@
!/data/spot/mtf/ !/data/spot/mtf/
/data/spot/mtf/** /data/spot/mtf/**
!data/spot/mtf/mtf_rules_v3.json !data/spot/mtf/mtf_rules_v3.json
!/data/spot/operations/
/data/spot/operations/**
!data/spot/operations/sizing_rules.json
/docs/** /docs/**
!/docs/spot/ !/docs/spot/
/docs/spot/** /docs/spot/**

View File

@@ -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
}
}

View File

@@ -59,7 +59,8 @@ def main() -> int:
print("\n=== 매수·매도 비율 튜닝 (타점 고정) ===", flush=True) print("\n=== 매수·매도 비율 튜닝 (타점 고정) ===", flush=True)
print( print(
f"기법: {settings.ops_technique_id} · sim {settings.gt_sim_lookback_days}일 · " 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, flush=True,
) )

View File

@@ -276,7 +276,7 @@ def load_settings(env_path: Path | None = None) -> Settings:
os.getenv("OPS_EXCHANGE_FEE_LOCK_RATE", "0.0025") os.getenv("OPS_EXCHANGE_FEE_LOCK_RATE", "0.0025")
), ),
ops_buy_safety_buffer_krw=float( 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_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")), ops_sell_coin_pct=float(os.getenv("OPS_SELL_COIN_PCT", "0.10")),

View File

@@ -44,11 +44,11 @@ def spendable_cash_for_exchange_buy(
fee_lock_rate: float = 0.0025, fee_lock_rate: float = 0.0025,
safety_buffer_krw: float = 1000.0, safety_buffer_krw: float = 1000.0,
) -> float: ) -> float:
"""거래소 시장가 매수 시 주문 가능 원화 (수수료 lock + floor + 안전 여유). """거래소 시장가 매수 시 주문 가능 원화 (예약수수료 lock + 최소 잔여 현금).
빗썸은 주문금액 + 예약수수료를 KRW에서 동시에 lock하므로 빗썸은 주문금액 + 예약수수료(fee_lock_rate)를 KRW에서 동시에 lock한다.
가용 원화 전액을 price로 넣으면 insufficient_funds(400)가 발생한다. ``floor(가용/(1+lock)) - safety_buffer_krw`` 로 주문 상한을 계산하며,
``floor(가용/(1+lock)) - safety_buffer_krw`` 로 주문 상한을 계산한다. safety_buffer_krw(기본 5000)는 매수 후에도 남길 최소 현금(원)이다.
""" """
cash = max(float(cash_krw), 0.0) cash = max(float(cash_krw), 0.0)
lock = max(float(fee_lock_rate), 0.0) lock = max(float(fee_lock_rate), 0.0)