Initial public release
This commit is contained in:
61
hydra/exchange/crypto.py
Normal file
61
hydra/exchange/crypto.py
Normal file
@@ -0,0 +1,61 @@
|
||||
import asyncio
|
||||
import json
|
||||
import subprocess
|
||||
|
||||
from pybreaker import CircuitBreaker
|
||||
|
||||
from hydra.exchange.base import BaseExchange
|
||||
from hydra.resilience.circuit_breaker import create_breaker
|
||||
from hydra.logging.setup import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
VALID_LEVERAGE = range(1, 126) # 1x ~ 125x
|
||||
|
||||
|
||||
class CryptoExchange(BaseExchange):
|
||||
"""ccxt CLI를 subprocess로 호출. 서킷 브레이커 적용."""
|
||||
|
||||
def __init__(self, exchange_id: str, breaker: CircuitBreaker | None = None, is_futures: bool = False):
|
||||
self.exchange_id = exchange_id
|
||||
self.is_futures = is_futures
|
||||
self._breaker = breaker or create_breaker(f"crypto:{exchange_id}")
|
||||
|
||||
async def _run(self, args: list[str]) -> dict:
|
||||
cmd = ["ccxt", self.exchange_id] + args + ["--json"]
|
||||
loop = asyncio.get_event_loop()
|
||||
raw = await loop.run_in_executor(
|
||||
None,
|
||||
lambda: self._breaker.call(subprocess.check_output, cmd, text=True, timeout=15),
|
||||
)
|
||||
return json.loads(raw)
|
||||
|
||||
async def get_balance(self) -> dict:
|
||||
return await self._run(["fetchBalance"])
|
||||
|
||||
async def create_order(self, symbol: str, side: str, order_type: str, qty: float, price: float | None = None) -> dict:
|
||||
args = ["createOrder", symbol, order_type, side, str(qty)]
|
||||
if price:
|
||||
args.append(str(price))
|
||||
return await self._run(args)
|
||||
|
||||
async def cancel_order(self, order_id: str) -> dict:
|
||||
return await self._run(["cancelOrder", order_id])
|
||||
|
||||
async def cancel_all(self) -> list:
|
||||
result = await self._run(["cancelAllOrders"])
|
||||
return result if isinstance(result, list) else []
|
||||
|
||||
async def get_positions(self) -> list:
|
||||
result = await self._run(["fetchPositions"])
|
||||
return result if isinstance(result, list) else []
|
||||
|
||||
async def set_leverage(self, symbol: str, leverage: int) -> None:
|
||||
"""선물 레버리지 설정. 현물 모드에서는 no-op."""
|
||||
if not self.is_futures:
|
||||
logger.debug("set_leverage_skipped_spot", exchange=self.exchange_id, symbol=symbol)
|
||||
return
|
||||
if leverage not in VALID_LEVERAGE:
|
||||
raise ValueError(f"레버리지는 1~125 사이여야 합니다. 입력값: {leverage}")
|
||||
await self._run(["setLeverage", str(leverage), symbol])
|
||||
logger.info("leverage_set", exchange=self.exchange_id, symbol=symbol, leverage=leverage)
|
||||
Reference in New Issue
Block a user