diff --git a/README.md b/README.md index a935893..b14dc37 100644 --- a/README.md +++ b/README.md @@ -802,42 +802,6 @@ See: `TRADEMARKS.md` --- -### 💼 Trusted Exchange Partners (Affiliate Links) - -By using our partner links, you support QuantDinger's development while enjoying the same trading experience. - -
- - - - - - -
- - Binance - -

- World's Largest Crypto Exchange
- Spot • Futures • Margin Trading -
- - OKX - -

- Leading Derivatives Platform
- Spot • Perpetual • Options -
- - Bitget - -

- Innovative Copy Trading
- Spot • Futures • Social Trading -
-
- ---- ### 💝 Direct Support (Donations) diff --git a/README_CN.md b/README_CN.md index 15f9468..c148a5d 100644 --- a/README_CN.md +++ b/README_CN.md @@ -381,6 +381,7 @@ QuantDinger 支持多种市场类型的执行方式: | KuCoin | 现货, 合约 | | Gate.io | 现货, 合约 | | Bitfinex | 现货, 衍生品 | +| Deepcoin | 现货, 永续 | ### 传统券商 @@ -613,43 +614,6 @@ QuantDinger 的代码使用 **Apache License 2.0** 授权。但需要注意:** --- -### 💼 值得信赖的交易所合作伙伴(联盟链接) - -使用我们的合作伙伴链接,在享受相同交易体验的同时支持 QuantDinger 的发展。 - -
- - - - - - -
- - Binance - -

- 全球最大的加密货币交易所
- 现货 • 期货 • 杠杆交易 -
- - OKX - -

- 领先的衍生品平台
- 现货 • 永续合约 • 期权 -
- - Bitget - -

- 创新的跟单交易
- 现货 • 期货 • 社交交易 -
-
- ---- - ### 💝 直接支持(捐赠) 你的贡献帮助我们维护和改进 QuantDinger。 diff --git a/README_JA.md b/README_JA.md index eebb5d1..19b5562 100644 --- a/README_JA.md +++ b/README_JA.md @@ -351,6 +351,7 @@ QuantDingerは複数の市場タイプに対して複数の実行方法をサポ | KuCoin | 現物, 先物 | | Gate.io | 現物, 先物 | | Bitfinex | 現物, デリバティブ | +| Deepcoin | 現物, 無期限 | ### 伝統的なブローカー @@ -569,43 +570,6 @@ QuantDinger のコードは **Apache License 2.0** で提供されています --- -### 💼 取引所パートナー(アフィリエイトリンク) - -パートナーリンクを使用することで、同じ取引体験を楽しみながら QuantDinger の開発を支援できます。 - -
- - - - - - -
- - Binance - -

- 世界最大の暗号通貨取引所
- 現物 • 先物 • マージン取引 -
- - OKX - -

- 主要なデリバティブプラットフォーム
- 現物 • パーペチュアル • オプション -
- - Bitget - -

- 革新的なコピートレード
- 現物 • 先物 • ソーシャルトレード -
-
- ---- - ### 💝 直接サポート(寄付) あなたのご支援は QuantDinger の維持・改善に役立ちます。 diff --git a/README_KO.md b/README_KO.md index 1278294..6da86ee 100644 --- a/README_KO.md +++ b/README_KO.md @@ -351,6 +351,7 @@ QuantDinger는 다양한 시장 유형에 대해 여러 실행 방법을 지원 | KuCoin | 현물, 선물 | | Gate.io | 현물, 선물 | | Bitfinex | 현물, 파생상품 | +| Deepcoin | 현물, 무기한 | ### 전통적인 브로커 @@ -582,43 +583,6 @@ QuantDinger의 코드는 **Apache License 2.0**으로 제공됩니다. 다만 ** --- -### 💼 거래소 파트너(어필리에이트 링크) - -파트너 링크를 사용하면 동일한 거래 경험을 즐기면서 QuantDinger의 개발을 지원할 수 있습니다. - -
- - - - - - -
- - Binance - -

- 세계 최대 암호화폐 거래소
- 현물 • 선물 • 마진 거래 -
- - OKX - -

- 주요 파생상품 플랫폼
- 현물 • 영구 선물 • 옵션 -
- - Bitget - -

- 혁신적인 복사 거래
- 현물 • 선물 • 소셜 거래 -
-
- ---- - ### 💝 직접 지원(기부) 귀하의 기여는 QuantDinger의 유지 및 개선에 도움이 됩니다. diff --git a/README_TW.md b/README_TW.md index ec57a9d..ce2b2a4 100644 --- a/README_TW.md +++ b/README_TW.md @@ -351,6 +351,7 @@ QuantDinger 支持多種市場類型的執行方式: | KuCoin | 現貨, 合約 | | Gate.io | 現貨, 合約 | | Bitfinex | 現貨, 衍生品 | +| Deepcoin | 現貨, 永續 | ### 傳統券商 @@ -583,43 +584,6 @@ QuantDinger 的代碼使用 **Apache License 2.0** 授權。但請注意:**Apa --- -### 💼 值得信賴的交易所合作夥伴(聯盟鏈接) - -使用我們的合作夥伴鏈接,在享受相同交易體驗的同時支持 QuantDinger 的發展。 - -
- - - - - - -
- - Binance - -

- 全球最大的加密貨幣交易所
- 現貨 • 期貨 • 槓桿交易 -
- - OKX - -

- 領先的衍生品平台
- 現貨 • 永續合約 • 期權 -
- - Bitget - -

- 創新的跟單交易
- 現貨 • 期貨 • 社交交易 -
-
- ---- - ### 💝 直接支持(捐贈) 你的貢獻幫助我們維護和改進 QuantDinger。 diff --git a/backend_api_python/app/services/live_trading/deepcoin.py b/backend_api_python/app/services/live_trading/deepcoin.py new file mode 100644 index 0000000..f7265ab --- /dev/null +++ b/backend_api_python/app/services/live_trading/deepcoin.py @@ -0,0 +1,640 @@ +""" +Deepcoin (direct REST) client for spot / perpetual swap orders. + +Based on official Deepcoin Python SDK example. +API Base URL: https://api.deepcoin.com + +Signing: +- DC-ACCESS-SIGN = base64(hmac_sha256(secret, timestamp + method + uri + body)) +- For GET: message = timestamp + method + uri (with query params) +- For POST: message = timestamp + method + uri + json_body +""" + +from __future__ import annotations + +import base64 +import datetime +import hashlib +import hmac +import json +import time +from decimal import Decimal, ROUND_DOWN +from typing import Any, Dict, Optional, Tuple +from urllib.parse import urlencode + +import requests + +from app.services.live_trading.base import BaseRestClient, LiveOrderResult, LiveTradingError +from app.services.live_trading.symbols import to_deepcoin_symbol + + +class DeepcoinClient(BaseRestClient): + """ + Deepcoin REST client for spot and perpetual swap trading. + + Based on official Deepcoin Python SDK. + Supports both spot and swap (perpetual futures) markets. + """ + + def __init__( + self, + *, + api_key: str, + secret_key: str, + passphrase: str = "", + base_url: str = "https://api.deepcoin.com", + timeout_sec: float = 15.0, + market_type: str = "swap", # "swap" (perpetual) or "spot" + ): + super().__init__(base_url=base_url, timeout_sec=timeout_sec) + self.api_key = (api_key or "").strip() + self.secret_key = (secret_key or "").strip() + self.passphrase = (passphrase or "").strip() + self.market_type = (market_type or "swap").strip().lower() + if self.market_type not in ("swap", "spot"): + self.market_type = "swap" + + if not self.api_key or not self.secret_key: + raise LiveTradingError("Missing Deepcoin api_key/secret_key") + + # Best-effort cache for instrument metadata (qty step, min qty, etc.) + # Key: f"{market_type}:{symbol}" -> (fetched_at_ts, info_dict) + self._inst_cache: Dict[str, Tuple[float, Dict[str, Any]]] = {} + self._inst_cache_ttl_sec = 300.0 + + # Best-effort cache for leverage settings + self._lev_cache: Dict[str, Tuple[float, bool]] = {} + self._lev_cache_ttl_sec = 60.0 + + @staticmethod + def _to_dec(x: Any) -> Decimal: + try: + return Decimal(str(x)) + except Exception: + return Decimal("0") + + @staticmethod + def _dec_str(d: Decimal) -> str: + try: + return format(d, "f") + except Exception: + return str(d) + + @staticmethod + def _floor_to_step(value: Decimal, step: Decimal) -> Decimal: + if step is None: + return value + if value <= 0: + return Decimal("0") + try: + st = Decimal(step) + except Exception: + st = Decimal("0") + if st <= 0: + return value + try: + n = (value / st).to_integral_value(rounding=ROUND_DOWN) + return n * st + except Exception: + return Decimal("0") + + def _get_iso_time(self) -> str: + """ + Generate ISO 8601 timestamp for Deepcoin API. + Format: 2024-07-29T11:12:00.123Z + """ + ticks = time.time() + localdate = datetime.datetime.utcfromtimestamp(ticks) + iso_time = localdate.isoformat() + # Ensure milliseconds and Z suffix + iso_time_change = iso_time[:23] + "Z" + return iso_time_change + + def _build_uri_with_params(self, uri: str, params: Optional[Dict[str, Any]], method: str) -> str: + """ + Build URI with query parameters for GET requests. + For POST requests, return URI as-is. + """ + if method.upper() == "GET" and params and params != {}: + query_parts = [] + for key, value in params.items(): + query_parts.append(f"{key}={value}") + query_string = "&".join(query_parts) + return f"{uri}?{query_string}" + return uri + + def _sign(self, iso_time: str, method: str, uri: str, data: Optional[Dict[str, Any]] = None) -> str: + """ + Generate HMAC-SHA256 signature for request authentication. + + For POST: message = timestamp + method + uri + json_body + For GET: message = timestamp + method + uri (with query params) + """ + method_upper = method.upper() + if method_upper == "POST" and data: + # Convert dict to JSON string with double quotes + data_str = json.dumps(data, separators=(',', ':')) + message = f"{iso_time}{method_upper}{uri}{data_str}" + else: + message = f"{iso_time}{method_upper}{uri}" + + message_bytes = message.encode('utf-8') + key_bytes = self.secret_key.encode('utf-8') + sign = base64.b64encode( + hmac.new(key=key_bytes, msg=message_bytes, digestmod=hashlib.sha256).digest() + ).decode('utf-8') + return sign + + def _headers(self, iso_time: str, sign: str) -> Dict[str, str]: + """ + Build authenticated request headers. + """ + headers = { + "DC-ACCESS-KEY": self.api_key, + "DC-ACCESS-SIGN": sign, + "DC-ACCESS-TIMESTAMP": iso_time, + "DC-ACCESS-PASSPHRASE": self.passphrase, + "Content-Type": "application/json", + } + return headers + + def _public_request(self, method: str, uri: str, *, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: + """ + Make public (unauthenticated) API request. + """ + full_uri = self._build_uri_with_params(uri, params, method) + url = f"{self.base_url}{full_uri}" + + try: + if method.upper() == "GET": + resp = requests.get(url=url, timeout=self.timeout_sec) + else: + resp = requests.post(url=url, json=params, timeout=self.timeout_sec) + + if resp.status_code >= 400: + raise LiveTradingError(f"Deepcoin HTTP {resp.status_code}: {resp.text[:500]}") + + data = resp.json() + if isinstance(data, dict): + code = data.get("code") or data.get("retCode") + if code not in (0, "0", None, "", "00000"): + raise LiveTradingError(f"Deepcoin error: {data}") + return data if isinstance(data, dict) else {"raw": data} + except requests.RequestException as e: + raise LiveTradingError(f"Deepcoin request failed: {str(e)}") + + def _signed_request( + self, + method: str, + uri: str, + *, + params: Optional[Dict[str, Any]] = None, + ) -> Dict[str, Any]: + """ + Make authenticated API request following Deepcoin signing spec. + + For GET requests: params are appended to URI as query string + For POST requests: params are sent as JSON body + """ + iso_time = self._get_iso_time() + method_upper = method.upper() + + # Build full URI (with query params for GET) + full_uri = self._build_uri_with_params(uri, params, method) + + # Generate signature + if method_upper == "POST": + sign = self._sign(iso_time, method_upper, uri, params) + else: + sign = self._sign(iso_time, method_upper, full_uri, None) + + headers = self._headers(iso_time, sign) + url = f"{self.base_url}{full_uri}" + + try: + if method_upper == "POST": + body_str = json.dumps(params, separators=(',', ':')) if params else "" + resp = requests.post(url=url, headers=headers, data=body_str, timeout=self.timeout_sec) + else: + resp = requests.get(url=url, headers=headers, timeout=self.timeout_sec) + + if resp.status_code >= 400: + raise LiveTradingError(f"Deepcoin HTTP {resp.status_code}: {resp.text[:500]}") + + data = resp.json() + if isinstance(data, dict): + code = data.get("code") or data.get("retCode") + if code not in (0, "0", None, "", "00000"): + raise LiveTradingError(f"Deepcoin error: {data}") + return data if isinstance(data, dict) else {"raw": data} + except requests.RequestException as e: + raise LiveTradingError(f"Deepcoin request failed: {str(e)}") + + def ping(self) -> bool: + """ + Test API connectivity using public endpoint. + """ + try: + # Try public endpoint to check connectivity + url = f"{self.base_url}/deepcoin/market/time" + resp = requests.get(url=url, timeout=self.timeout_sec) + return resp.status_code == 200 + except Exception: + return False + + def get_balance(self) -> Dict[str, Any]: + """ + Get account balance. + + Endpoint: GET /deepcoin/account/balances + """ + params = {"instType": "SWAP" if self.market_type == "swap" else "SPOT"} + return self._signed_request("GET", "/deepcoin/account/balances", params=params) + + def get_positions(self, *, symbol: str = "") -> Dict[str, Any]: + """ + Get open positions. + + Endpoint: GET /deepcoin/account/positions + """ + params: Dict[str, Any] = {"instType": "SWAP" if self.market_type == "swap" else "SPOT"} + if symbol: + params["instId"] = to_deepcoin_symbol(symbol) + return self._signed_request("GET", "/deepcoin/account/positions", params=params) + + def set_leverage(self, *, symbol: str, leverage: float, mgn_mode: str = "cross") -> bool: + """ + Set leverage for a trading pair. + + Endpoint: POST /deepcoin/account/set-leverage + """ + sym = to_deepcoin_symbol(symbol) + if not sym: + return False + + try: + lv = int(float(leverage or 1.0)) + except Exception: + lv = 1 + if lv < 1: + lv = 1 + + mm = str(mgn_mode or "cross").strip().lower() + if mm not in ("cross", "isolated"): + mm = "cross" + + # Check cache + cache_key = f"{sym}:{mm}:{lv}" + now = time.time() + cached = self._lev_cache.get(cache_key) + if cached: + ts, ok = cached + if ok and (now - float(ts or 0.0)) <= float(self._lev_cache_ttl_sec or 60.0): + return True + + params = { + "instId": sym, + "lever": str(lv), + "mgnMode": mm, + "mrgPosition": "merge", + } + + try: + self._signed_request("POST", "/deepcoin/account/set-leverage", params=params) + self._lev_cache[cache_key] = (now, True) + return True + except Exception: + return False + + def get_instrument_info(self, *, symbol: str) -> Dict[str, Any]: + """ + Get instrument metadata (min qty, qty step, etc.). + + Endpoint: GET /deepcoin/market/instruments + """ + sym = to_deepcoin_symbol(symbol) + if not sym: + return {} + + key = f"{self.market_type}:{sym}" + now = time.time() + cached = self._inst_cache.get(key) + if cached: + ts, obj = cached + if obj and (now - float(ts or 0.0)) <= float(self._inst_cache_ttl_sec or 300.0): + return obj + + inst_type = "SWAP" if self.market_type == "swap" else "SPOT" + params = {"instType": inst_type, "instId": sym} + + try: + raw = self._public_request("GET", "/deepcoin/market/instruments", params=params) + data = (raw.get("data") or []) if isinstance(raw, dict) else [] + first: Dict[str, Any] = data[0] if isinstance(data, list) and data else {} + if isinstance(first, dict) and first: + self._inst_cache[key] = (now, first) + return first if isinstance(first, dict) else {} + except Exception: + return {} + + def _normalize_qty(self, *, symbol: str, qty: float) -> Decimal: + """ + Normalize order quantity to exchange requirements. + """ + q = self._to_dec(qty) + if q <= 0: + return Decimal("0") + + sym = to_deepcoin_symbol(symbol) + try: + info = self.get_instrument_info(symbol=sym) or {} + except Exception: + info = {} + + # Extract lot size filter + step = self._to_dec(info.get("lotSz") or info.get("qtyStep") or "0") + mn = self._to_dec(info.get("minSz") or info.get("minOrderQty") or "0") + + if step > 0: + q = self._floor_to_step(q, step) + if mn > 0 and q < mn: + return Decimal("0") + return q + + def place_market_order( + self, + *, + symbol: str, + side: str, + qty: float, + reduce_only: bool = False, + pos_side: str = "", + client_order_id: Optional[str] = None, + ) -> LiveOrderResult: + """ + Place a market order. + + Endpoint: POST /deepcoin/trade/order + + Args: + symbol: Trading pair (e.g., "BTC/USDT:USDT" or "BTCUSDT") + side: "buy" or "sell" + qty: Order quantity in base currency + reduce_only: If True, only reduce position (for futures) + pos_side: Position side for hedge mode ("long" or "short") + client_order_id: Optional client order ID + """ + sym = to_deepcoin_symbol(symbol) + sd = (side or "").strip().lower() + if sd not in ("buy", "sell"): + raise LiveTradingError(f"Invalid side: {side}") + + q_req = float(qty or 0.0) + q_dec = self._normalize_qty(symbol=symbol, qty=q_req) + if float(q_dec or 0) <= 0: + raise LiveTradingError(f"Invalid qty (below step/min): requested={q_req}") + + params: Dict[str, Any] = { + "instId": sym, + "tdMode": "cash" if self.market_type == "spot" else "cross", + "side": sd, + "ordType": "market", + "sz": self._dec_str(q_dec), + } + + if self.market_type != "spot": + ps = (pos_side or "").strip().lower() + if ps in ("long", "short", "net"): + params["posSide"] = ps + if reduce_only: + params["reduceOnly"] = True + + if client_order_id: + params["clOrdId"] = str(client_order_id) + + raw = self._signed_request("POST", "/deepcoin/trade/order", params=params) + data = (raw.get("data") or []) if isinstance(raw, dict) else [] + first: Dict[str, Any] = data[0] if isinstance(data, list) and data else {} + oid = str(first.get("ordId") or first.get("orderId") or first.get("clOrdId") or "") + + return LiveOrderResult( + exchange_id="deepcoin", + exchange_order_id=oid, + filled=0.0, + avg_price=0.0, + raw=raw, + ) + + def place_limit_order( + self, + *, + symbol: str, + side: str, + qty: float, + price: float, + reduce_only: bool = False, + pos_side: str = "", + client_order_id: Optional[str] = None, + ) -> LiveOrderResult: + """ + Place a limit order. + + Endpoint: POST /deepcoin/trade/order + """ + sym = to_deepcoin_symbol(symbol) + sd = (side or "").strip().lower() + if sd not in ("buy", "sell"): + raise LiveTradingError(f"Invalid side: {side}") + + q_req = float(qty or 0.0) + px = float(price or 0.0) + if q_req <= 0 or px <= 0: + raise LiveTradingError("Invalid qty/price") + + q_dec = self._normalize_qty(symbol=symbol, qty=q_req) + if float(q_dec or 0) <= 0: + raise LiveTradingError(f"Invalid qty (below step/min): requested={q_req}") + + params: Dict[str, Any] = { + "instId": sym, + "tdMode": "cash" if self.market_type == "spot" else "cross", + "side": sd, + "ordType": "limit", + "sz": self._dec_str(q_dec), + "px": str(px), + } + + if self.market_type != "spot": + ps = (pos_side or "").strip().lower() + if ps in ("long", "short", "net"): + params["posSide"] = ps + if reduce_only: + params["reduceOnly"] = True + + if client_order_id: + params["clOrdId"] = str(client_order_id) + + raw = self._signed_request("POST", "/deepcoin/trade/order", params=params) + data = (raw.get("data") or []) if isinstance(raw, dict) else [] + first: Dict[str, Any] = data[0] if isinstance(data, list) and data else {} + oid = str(first.get("ordId") or first.get("orderId") or first.get("clOrdId") or "") + + return LiveOrderResult( + exchange_id="deepcoin", + exchange_order_id=oid, + filled=0.0, + avg_price=0.0, + raw=raw, + ) + + def cancel_order(self, *, symbol: str, order_id: str = "", client_order_id: str = "") -> Dict[str, Any]: + """ + Cancel an order. + + Endpoint: POST /deepcoin/trade/cancel-order + """ + sym = to_deepcoin_symbol(symbol) + params: Dict[str, Any] = {"instId": sym} + + if order_id: + params["ordId"] = str(order_id) + elif client_order_id: + params["clOrdId"] = str(client_order_id) + else: + raise LiveTradingError("Deepcoin cancel_order requires order_id or client_order_id") + + return self._signed_request("POST", "/deepcoin/trade/cancel-order", params=params) + + def get_order(self, *, symbol: str, order_id: str = "", client_order_id: str = "") -> Dict[str, Any]: + """ + Get order details. + + Endpoint: GET /deepcoin/trade/order + """ + sym = to_deepcoin_symbol(symbol) + params: Dict[str, Any] = {"instId": sym} + + if order_id: + params["ordId"] = str(order_id) + elif client_order_id: + params["clOrdId"] = str(client_order_id) + else: + raise LiveTradingError("Deepcoin get_order requires order_id or client_order_id") + + resp = self._signed_request("GET", "/deepcoin/trade/order", params=params) + data = (resp.get("data") or []) if isinstance(resp, dict) else [] + first: Dict[str, Any] = data[0] if isinstance(data, list) and data else {} + return first + + def get_open_orders(self, *, symbol: str = "") -> Dict[str, Any]: + """ + Get open orders. + + Endpoint: GET /deepcoin/trade/orders-pending + """ + params: Dict[str, Any] = {"instType": "SWAP" if self.market_type == "swap" else "SPOT"} + if symbol: + params["instId"] = to_deepcoin_symbol(symbol) + return self._signed_request("GET", "/deepcoin/trade/orders-pending", params=params) + + def get_order_history(self, *, symbol: str = "", limit: int = 100) -> Dict[str, Any]: + """ + Get order history. + + Endpoint: GET /deepcoin/trade/orders-history + """ + params: Dict[str, Any] = { + "instType": "SWAP" if self.market_type == "swap" else "SPOT", + "limit": str(limit), + } + if symbol: + params["instId"] = to_deepcoin_symbol(symbol) + return self._signed_request("GET", "/deepcoin/trade/orders-history", params=params) + + def wait_for_fill( + self, + *, + symbol: str, + order_id: str = "", + client_order_id: str = "", + max_wait_sec: float = 3.0, + poll_interval_sec: float = 0.5, + ) -> Dict[str, Any]: + """ + Poll order status until filled or timeout. + + Returns: + { + "filled": float, + "avg_price": float, + "fee": float, + "fee_ccy": str, + "status": str, + "order": {...} + } + """ + end_ts = time.time() + float(max_wait_sec or 0.0) + last: Dict[str, Any] = {} + + while True: + try: + last = self.get_order( + symbol=symbol, + order_id=str(order_id or ""), + client_order_id=str(client_order_id or ""), + ) + except Exception: + last = last or {} + + status = str(last.get("state") or last.get("status") or last.get("orderStatus") or "") + + try: + filled = float(last.get("accFillSz") or last.get("fillSz") or last.get("cumExecQty") or 0.0) + except Exception: + filled = 0.0 + + try: + avg_price = float(last.get("avgPx") or last.get("fillPx") or last.get("avgPrice") or 0.0) + except Exception: + avg_price = 0.0 + + # Extract fee + fee = 0.0 + fee_ccy = "" + try: + fee = abs(float(last.get("fee") or last.get("cumExecFee") or 0.0)) + fee_ccy = str(last.get("feeCcy") or "") + except Exception: + pass + + if filled > 0 and avg_price > 0: + return { + "filled": filled, + "avg_price": avg_price, + "fee": fee, + "fee_ccy": fee_ccy, + "status": status, + "order": last, + } + + if status.lower() in ("filled", "cancelled", "canceled", "rejected"): + return { + "filled": filled, + "avg_price": avg_price, + "fee": fee, + "fee_ccy": fee_ccy, + "status": status, + "order": last, + } + + if time.time() >= end_ts: + return { + "filled": filled, + "avg_price": avg_price, + "fee": fee, + "fee_ccy": fee_ccy, + "status": status, + "order": last, + } + + time.sleep(float(poll_interval_sec or 0.5)) diff --git a/backend_api_python/app/services/live_trading/factory.py b/backend_api_python/app/services/live_trading/factory.py index 366bf93..fb956a4 100644 --- a/backend_api_python/app/services/live_trading/factory.py +++ b/backend_api_python/app/services/live_trading/factory.py @@ -24,6 +24,7 @@ from app.services.live_trading.kraken_futures import KrakenFuturesClient from app.services.live_trading.kucoin import KucoinSpotClient, KucoinFuturesClient from app.services.live_trading.gate import GateSpotClient, GateUsdtFuturesClient from app.services.live_trading.bitfinex import BitfinexClient, BitfinexDerivativesClient +from app.services.live_trading.deepcoin import DeepcoinClient # Lazy import IBKR to avoid ImportError if ib_insync not installed IBKRClient = None @@ -114,6 +115,16 @@ def create_client(exchange_config: Dict[str, Any], *, market_type: str = "swap") return BitfinexClient(api_key=api_key, secret_key=secret_key, base_url=base_url) return BitfinexDerivativesClient(api_key=api_key, secret_key=secret_key, base_url=base_url) + if exchange_id == "deepcoin": + base_url = _get(exchange_config, "base_url", "baseUrl") or "https://api.deepcoin.com" + return DeepcoinClient( + api_key=api_key, + secret_key=secret_key, + passphrase=passphrase, + base_url=base_url, + market_type=mt, + ) + # Traditional brokers (IBKR for US/HK stocks only) if exchange_id == "ibkr": # Note: Market category validation should be done at the caller level diff --git a/backend_api_python/app/services/live_trading/symbols.py b/backend_api_python/app/services/live_trading/symbols.py index 96b4eb9..56b9497 100644 --- a/backend_api_python/app/services/live_trading/symbols.py +++ b/backend_api_python/app/services/live_trading/symbols.py @@ -185,3 +185,38 @@ def to_bitfinex_perp_symbol(symbol: str) -> str: return f"t{base}F0:{q}F0" +def to_deepcoin_symbol(symbol: str) -> str: + """ + Deepcoin symbol format: typically BASE-QUOTE for spot, BASE-QUOTE-SWAP for perpetual. + Examples: + - Spot: BTC-USDT + - Perpetual: BTC-USDT-SWAP + + If symbol already contains '-', return as-is (already in Deepcoin format). + """ + s = (symbol or "").strip() + if not s: + return s + + # Already in Deepcoin format + if "-" in s: + return s.upper() + + base, quote = _split_base_quote(symbol) + if not base or not quote: + # Best effort: remove slashes and colons + return s.replace("/", "-").replace(":", "-").upper() + + # Return BASE-QUOTE format (caller adds -SWAP if needed for futures) + return f"{base}-{quote}" + + +def to_deepcoin_swap_symbol(symbol: str) -> str: + """ + Deepcoin perpetual swap symbol format: BASE-QUOTE-SWAP, e.g. BTC-USDT-SWAP. + """ + base_symbol = to_deepcoin_symbol(symbol) + if base_symbol.endswith("-SWAP"): + return base_symbol + return f"{base_symbol}-SWAP" + diff --git a/backend_api_python/app/services/strategy.py b/backend_api_python/app/services/strategy.py index cb60f98..6c59d50 100644 --- a/backend_api_python/app/services/strategy.py +++ b/backend_api_python/app/services/strategy.py @@ -282,6 +282,7 @@ class StrategyService: from app.services.live_trading.kucoin import KucoinFuturesClient from app.services.live_trading.gate import GateSpotClient, GateUsdtFuturesClient from app.services.live_trading.bitfinex import BitfinexClient, BitfinexDerivativesClient + from app.services.live_trading.deepcoin import DeepcoinClient resolved = resolve_exchange_config(exchange_config or {}) safe_cfg = safe_exchange_config_for_log(resolved) @@ -350,6 +351,8 @@ class StrategyService: priv_data = client.get_wallets() elif isinstance(client, BitfinexDerivativesClient): priv_data = client.get_wallets() + elif isinstance(client, DeepcoinClient): + priv_data = client.get_balance() except Exception as e: msg = str(e) # Add actionable hints for the most common Binance auth error. diff --git a/quantdinger_vue/Dockerfile b/quantdinger_vue/Dockerfile index cc9976e..25a385a 100644 --- a/quantdinger_vue/Dockerfile +++ b/quantdinger_vue/Dockerfile @@ -6,8 +6,6 @@ WORKDIR /app # Copy package files COPY package*.json ./ -COPY pnpm-lock.yaml* ./ -COPY yarn.lock* ./ # Install dependencies (prefer npm) RUN npm install --legacy-peer-deps diff --git a/quantdinger_vue/src/locales/lang/ar-SA.js b/quantdinger_vue/src/locales/lang/ar-SA.js index 0bad384..fef6a97 100644 --- a/quantdinger_vue/src/locales/lang/ar-SA.js +++ b/quantdinger_vue/src/locales/lang/ar-SA.js @@ -1233,7 +1233,8 @@ const locale = { 'ftxus': 'إف تي إكس الولايات المتحدة', 'binanceus': 'بينانس الولايات المتحدة', 'binancecoinm': 'عملة بينانس-M', - 'binanceusdm': 'Binance USDⓈ-M' + 'binanceusdm': 'Binance USDⓈ-M', + 'deepcoin': 'ديبكوين' }, 'ai-trading-assistant.title': 'مساعد التداول بالذكاء الاصطناعي', 'ai-trading-assistant.strategyList': 'قائمة الإستراتيجية', diff --git a/quantdinger_vue/src/locales/lang/de-DE.js b/quantdinger_vue/src/locales/lang/de-DE.js index c37ba2b..59bceee 100644 --- a/quantdinger_vue/src/locales/lang/de-DE.js +++ b/quantdinger_vue/src/locales/lang/de-DE.js @@ -1283,7 +1283,8 @@ const locale = { 'ftxus': 'FTX USA', 'binanceus': 'Binance USA', 'binancecoinm': 'Binance COIN-M', - 'binanceusdm': 'Binance USDⓈ-M' + 'binanceusdm': 'Binance USDⓈ-M', + 'deepcoin': 'Deepcoin' }, 'ai-trading-assistant.title': 'KI-Handelsassistent', 'ai-trading-assistant.strategyList': 'Strategieliste', diff --git a/quantdinger_vue/src/locales/lang/en-US.js b/quantdinger_vue/src/locales/lang/en-US.js index 4534e79..8b88fb3 100644 --- a/quantdinger_vue/src/locales/lang/en-US.js +++ b/quantdinger_vue/src/locales/lang/en-US.js @@ -1664,7 +1664,8 @@ const locale = { 'binanceus': 'Binance US', 'binancecoinm': 'Binance COIN-M', 'binanceusdm': 'Binance USDⓈ-M', - 'ibkr': 'Interactive Brokers (IBKR)' + 'ibkr': 'Interactive Brokers (IBKR)', + 'deepcoin': 'Deepcoin' }, 'ai-trading-assistant.title': 'AI Trading Assistant', 'ai-trading-assistant.strategyList': 'Strategy List', diff --git a/quantdinger_vue/src/locales/lang/fr-FR.js b/quantdinger_vue/src/locales/lang/fr-FR.js index 6b253e6..e84ca57 100644 --- a/quantdinger_vue/src/locales/lang/fr-FR.js +++ b/quantdinger_vue/src/locales/lang/fr-FR.js @@ -1234,7 +1234,8 @@ const locale = { 'ftxus': 'FTX États-Unis', 'binanceus': 'Binance États-Unis', 'binancecoinm': 'Binance COIN-M', - 'binanceusdm': 'Binance USDⓈ-M' + 'binanceusdm': 'Binance USDⓈ-M', + 'deepcoin': 'Deepcoin' }, 'ai-trading-assistant.title': 'Assistant de trading IA', 'ai-trading-assistant.strategyList': 'Liste de stratégies', diff --git a/quantdinger_vue/src/locales/lang/ja-JP.js b/quantdinger_vue/src/locales/lang/ja-JP.js index b1affc6..7309e8b 100644 --- a/quantdinger_vue/src/locales/lang/ja-JP.js +++ b/quantdinger_vue/src/locales/lang/ja-JP.js @@ -1238,7 +1238,8 @@ const locale = { 'ftxus': 'FTX米国', 'binanceus': 'バイナンスUS', 'binancecoinm': 'バイナンス COIN-M', - 'binanceusdm': 'バイナンスUSDⓈ-M' + 'binanceusdm': 'バイナンスUSDⓈ-M', + 'deepcoin': 'ディープコイン' }, 'ai-trading-assistant.title': 'AI取引アシスタント', 'ai-trading-assistant.strategyList': '戦略一覧', diff --git a/quantdinger_vue/src/locales/lang/ko-KR.js b/quantdinger_vue/src/locales/lang/ko-KR.js index db6d847..1abd96f 100644 --- a/quantdinger_vue/src/locales/lang/ko-KR.js +++ b/quantdinger_vue/src/locales/lang/ko-KR.js @@ -1238,7 +1238,8 @@ const locale = { 'ftxus': 'FTX 미국', 'binanceus': '바이낸스 미국', 'binancecoinm': '바이낸스 코인-M', - 'binanceusdm': '바이낸스 USDⓈ-M' + 'binanceusdm': '바이낸스 USDⓈ-M', + 'deepcoin': '딥코인' }, 'ai-trading-assistant.title': 'AI 트레이딩 도우미', 'ai-trading-assistant.strategyList': '전략 목록', diff --git a/quantdinger_vue/src/locales/lang/th-TH.js b/quantdinger_vue/src/locales/lang/th-TH.js index f46ad35..a6ac03d 100644 --- a/quantdinger_vue/src/locales/lang/th-TH.js +++ b/quantdinger_vue/src/locales/lang/th-TH.js @@ -1235,7 +1235,8 @@ const locale = { 'ftxus': 'FTX สหรัฐอเมริกา', 'binanceus': 'Binance สหรัฐอเมริกา', 'binancecoinm': 'Binance COIN-M', - 'binanceusdm': 'Binance USDⓈ-M' + 'binanceusdm': 'Binance USDⓈ-M', + 'deepcoin': 'ดีพคอยน์' }, 'ai-trading-assistant.title': 'ผู้ช่วยการซื้อขาย AI', 'ai-trading-assistant.strategyList': 'รายการกลยุทธ์', diff --git a/quantdinger_vue/src/locales/lang/vi-VN.js b/quantdinger_vue/src/locales/lang/vi-VN.js index 28aaee8..ed53382 100644 --- a/quantdinger_vue/src/locales/lang/vi-VN.js +++ b/quantdinger_vue/src/locales/lang/vi-VN.js @@ -2376,7 +2376,9 @@ const locale = { 'binancecoinm': 'Binance COIN-M', -'binanceusdm': 'Binance USDⓈ-M' +'binanceusdm': 'Binance USDⓈ-M', + +'deepcoin': 'Deepcoin' }, diff --git a/quantdinger_vue/src/locales/lang/zh-CN.js b/quantdinger_vue/src/locales/lang/zh-CN.js index 623ff25..2ef2202 100644 --- a/quantdinger_vue/src/locales/lang/zh-CN.js +++ b/quantdinger_vue/src/locales/lang/zh-CN.js @@ -1559,7 +1559,8 @@ const locale = { 'binanceus': 'Binance US', 'binancecoinm': 'Binance COIN-M', 'binanceusdm': 'Binance USDⓈ-M', -'ibkr': '盈透证券 (IBKR)' +'ibkr': '盈透证券 (IBKR)', +'deepcoin': 'Deepcoin' }, 'ai-trading-assistant.title': 'AI交易助手', 'ai-trading-assistant.strategyList': '策略列表', diff --git a/quantdinger_vue/src/locales/lang/zh-TW.js b/quantdinger_vue/src/locales/lang/zh-TW.js index 284c506..e955007 100644 --- a/quantdinger_vue/src/locales/lang/zh-TW.js +++ b/quantdinger_vue/src/locales/lang/zh-TW.js @@ -1421,7 +1421,8 @@ const locale = { 'ftxus': 'FTX US', 'binanceus': 'Binance US', 'binancecoinm': 'Binance COIN-M', - 'binanceusdm': 'Binance USDⓈ-M' + 'binanceusdm': 'Binance USDⓈ-M', + 'deepcoin': 'Deepcoin' }, 'ai-trading-assistant.title': 'AI交易助手', 'ai-trading-assistant.strategyList': '策略列表', diff --git a/quantdinger_vue/src/views/trading-assistant/index.vue b/quantdinger_vue/src/views/trading-assistant/index.vue index 624b3e0..a4936dc 100644 --- a/quantdinger_vue/src/views/trading-assistant/index.vue +++ b/quantdinger_vue/src/views/trading-assistant/index.vue @@ -1255,7 +1255,8 @@ const EXCHANGE_OPTIONS = [ { value: 'kraken', labelKey: 'kraken' }, { value: 'kucoin', labelKey: 'kucoin' }, { value: 'gate', labelKey: 'gate' }, - { value: 'bitfinex', labelKey: 'bitfinex' } + { value: 'bitfinex', labelKey: 'bitfinex' }, + { value: 'deepcoin', labelKey: 'deepcoin' } ] // Traditional broker options (US/HK stocks) - extensible for future brokers @@ -1289,7 +1290,7 @@ export default { }, needsPassphrase () { // Exchanges that require passphrase - return ['okx', 'okex', 'coinbaseexchange', 'kucoin', 'bitget'].includes(this.currentExchangeId) + return ['okx', 'okex', 'coinbaseexchange', 'kucoin', 'bitget', 'deepcoin'].includes(this.currentExchangeId) }, // Check if current market uses IBKR (US Stock / HK Stock) isIBKRMarket () {