c4e6aab7b2
- backend/ — FastAPI + JWT + 모든 REST 엔드포인트 - frontend/ — Next.js 14 + Tailwind + 7페이지 (대시보드/트레이드/거래소/자동매매/설정/내정보/로그인) - core_logic.py — 신호계산/알림 로직 분리 (기존 app_streamlit.py 에서 추출) - users_db.py + bcrypt 인증, exchange_keys.py + Fernet 암호화 - trades_db.py — 진입/청산 lifecycle 추적, signal_events raw 로그 - settings_db.py — 모든 운영 파라미터 DB 영속 저장 (RSI/거래량/펀딩비 임계값 포함) - docker-compose: frontend / backend / postgres + Traefik 라우팅 - assets/logo.svg — JUNGGOMOA 그라디언트 로고 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
94 lines
3.6 KiB
Python
94 lines
3.6 KiB
Python
"""
|
|
거래소 어댑터 인터페이스 placeholder.
|
|
|
|
자동매매 본격 구현 시 각 거래소 SDK 를 wrap 한 ExchangeAdapter 구현체를
|
|
이 파일에 추가. 지금은 인터페이스 + 더미 구현 (DRY-RUN 으로 로그만 출력) 만 잡아둔다.
|
|
|
|
사용 흐름:
|
|
cred = exchange_keys.get_credential(active_id)
|
|
adapter = make_adapter(cred)
|
|
adapter.place_market_order(symbol="BTCUSDT", side="long", qty=0.01)
|
|
"""
|
|
from dataclasses import dataclass, asdict
|
|
from typing import Optional, Dict, Any, List
|
|
|
|
|
|
@dataclass
|
|
class OrderResult:
|
|
ok: bool
|
|
order_id: Optional[str] = None
|
|
filled_qty: float = 0.0
|
|
avg_price: float = 0.0
|
|
raw: Optional[Dict[str, Any]] = None
|
|
error: Optional[str] = None
|
|
|
|
|
|
class ExchangeAdapter:
|
|
"""모든 거래소 어댑터의 베이스. dry_run=True 면 실제 거래소에 보내지 않는다."""
|
|
def __init__(self, exchange: str, api_key: str, api_secret: str,
|
|
passphrase: Optional[str] = None, testnet: bool = False, dry_run: bool = True):
|
|
self.exchange = exchange
|
|
self.api_key = api_key
|
|
self.api_secret = api_secret
|
|
self.passphrase = passphrase
|
|
self.testnet = testnet
|
|
self.dry_run = dry_run
|
|
|
|
# 하위 클래스에서 override
|
|
def get_balance(self, asset: str = "USDT") -> float:
|
|
raise NotImplementedError
|
|
|
|
def get_position(self, symbol: str) -> Optional[Dict[str, Any]]:
|
|
raise NotImplementedError
|
|
|
|
def place_market_order(self, symbol: str, side: str, qty: float,
|
|
reduce_only: bool = False) -> OrderResult:
|
|
raise NotImplementedError
|
|
|
|
def close_position(self, symbol: str) -> OrderResult:
|
|
raise NotImplementedError
|
|
|
|
def set_leverage(self, symbol: str, leverage: int) -> bool:
|
|
raise NotImplementedError
|
|
|
|
|
|
class DryRunAdapter(ExchangeAdapter):
|
|
"""모든 메서드를 stdout 으로만 시뮬레이션. 실제 SDK 가 연결되기 전 사용."""
|
|
def get_balance(self, asset: str = "USDT") -> float:
|
|
print(f"[DRY {self.exchange}] get_balance({asset}) -> 1000.0 (stub)")
|
|
return 1000.0
|
|
|
|
def get_position(self, symbol: str):
|
|
print(f"[DRY {self.exchange}] get_position({symbol}) -> None")
|
|
return None
|
|
|
|
def place_market_order(self, symbol: str, side: str, qty: float, reduce_only: bool = False) -> OrderResult:
|
|
msg = f"[DRY {self.exchange}] MARKET {side.upper()} {symbol} qty={qty} reduce_only={reduce_only}"
|
|
print(msg)
|
|
return OrderResult(ok=True, order_id="dry-" + symbol, filled_qty=qty, avg_price=0.0,
|
|
raw={"note": msg})
|
|
|
|
def close_position(self, symbol: str) -> OrderResult:
|
|
msg = f"[DRY {self.exchange}] CLOSE {symbol}"
|
|
print(msg)
|
|
return OrderResult(ok=True, order_id="dry-close-" + symbol, raw={"note": msg})
|
|
|
|
def set_leverage(self, symbol: str, leverage: int) -> bool:
|
|
print(f"[DRY {self.exchange}] set_leverage({symbol}, {leverage}) -> True")
|
|
return True
|
|
|
|
|
|
def make_adapter(credential: Dict[str, Any], dry_run: bool = True) -> ExchangeAdapter:
|
|
"""exchange_keys.get_credential() 결과로부터 어댑터 생성.
|
|
실제 거래소별 구현이 추가되기 전엔 모두 DryRunAdapter 반환."""
|
|
if not credential:
|
|
return DryRunAdapter("none", "", "", dry_run=True)
|
|
return DryRunAdapter(
|
|
exchange=credential.get("exchange", ""),
|
|
api_key=credential.get("api_key") or "",
|
|
api_secret=credential.get("api_secret") or "",
|
|
passphrase=credential.get("passphrase"),
|
|
testnet=bool(credential.get("testnet")),
|
|
dry_run=dry_run,
|
|
)
|