feat(spot): 3단계 운영 파이프라인 — composite_v3 + MTF paper/live

MTF 필터 백테스트, paper/live 체결, 빗썸 Private API 연동 및 운영 스크립트·설계 문서를 추가해 2단계 전략을 실거래 단계에 연결한다.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
xavis
2026-06-12 18:27:34 +09:00
parent 2d515dd669
commit 58802bdc5f
19 changed files with 1485 additions and 10 deletions

View File

@@ -0,0 +1,127 @@
"""매수·매도 체결 로직 (paper / live 공통 사이징)."""
from __future__ import annotations
from dataclasses import dataclass
from typing import Any
from deepcoin.ground_truth.order_sizing import max_buy_from_cash
@dataclass
class TradeResult:
"""단일 체결 결과."""
executed: bool
side: str
order_krw: float
order_coin: float
fee_krw: float
price: float
skip_reason: str = ""
api_response: dict[str, Any] | None = None
def to_dict(self) -> dict[str, Any]:
"""JSON 직렬화 dict."""
return {
"executed": self.executed,
"side": self.side,
"order_krw": round(self.order_krw, 0),
"order_coin": round(self.order_coin, 8),
"fee_krw": round(self.fee_krw, 2),
"price": self.price,
"skip_reason": self.skip_reason,
"api_response": self.api_response,
}
def compute_buy_order(
*,
cash_krw: float,
coin_qty: float,
price: float,
fee_rate: float,
min_order_krw: float,
cluster_size: int = 1,
) -> TradeResult:
"""매수 주문 금액·수량을 계산한다."""
cash = max(float(cash_krw), 0.0)
equity = cash + float(coin_qty) * price
cash_cap = max_buy_from_cash(equity, cash)
per_buy = cash / cluster_size if cluster_size > 0 else cash
order_krw = min(per_buy, cash, cash_cap)
if order_krw < min_order_krw:
return TradeResult(
executed=False,
side="buy",
order_krw=0.0,
order_coin=0.0,
fee_krw=0.0,
price=price,
skip_reason="원화 부족 또는 최소 주문 미만",
)
fee = order_krw * fee_rate
bought = (order_krw - fee) / price
return TradeResult(
executed=True,
side="buy",
order_krw=order_krw,
order_coin=bought,
fee_krw=fee,
price=price,
)
def compute_sell_order(
*,
coin_qty: float,
price: float,
fee_rate: float,
min_order_krw: float,
cluster_size: int = 1,
) -> TradeResult:
"""매도 주문 수량을 계산한다."""
qty = max(float(coin_qty), 0.0)
per_sell = qty / cluster_size if cluster_size > 0 else qty
order_coin = per_sell
order_krw = order_coin * price
if order_coin <= 0 or order_krw < min_order_krw:
return TradeResult(
executed=False,
side="sell",
order_krw=0.0,
order_coin=0.0,
fee_krw=0.0,
price=price,
skip_reason="코인 부족 또는 최소 주문 미만",
)
fee = order_krw * fee_rate
return TradeResult(
executed=True,
side="sell",
order_krw=order_krw,
order_coin=order_coin,
fee_krw=fee,
price=price,
)
def apply_trade_to_portfolio(
portfolio: dict[str, Any],
trade: TradeResult,
) -> None:
"""체결 결과를 포트폴리오에 반영한다."""
cash = float(portfolio.get("cash_krw", 0))
coin = float(portfolio.get("coin_qty", 0))
if not trade.executed:
return
if trade.side == "buy":
portfolio["cash_krw"] = cash - trade.order_krw
portfolio["coin_qty"] = coin + trade.order_coin
else:
portfolio["cash_krw"] = cash + trade.order_krw - trade.fee_krw
portfolio["coin_qty"] = coin - trade.order_coin