Merge branch 'brokermr810:main' into main
This commit is contained in:
@@ -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.
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td align="center" width="33.33%">
|
|
||||||
<a href="https://www.bmwweb.ac/referral/earn-together/refer2earn-usdc/claim?hl=zh-CN&ref=GRO_28502_9OSOJ" target="_blank">
|
|
||||||
<img src="https://img.shields.io/badge/Binance-Exchange-F0B90B?style=for-the-badge&logo=binance&logoColor=white" alt="Binance" />
|
|
||||||
</a>
|
|
||||||
<br/><br/>
|
|
||||||
<strong>World's Largest Crypto Exchange</strong><br/>
|
|
||||||
<small>Spot • Futures • Margin Trading</small>
|
|
||||||
</td>
|
|
||||||
<td align="center" width="33.33%">
|
|
||||||
<a href="https://www.bjwebptyiou.com/join/14449926" target="_blank">
|
|
||||||
<img src="https://img.shields.io/badge/OKX-Exchange-000000?style=for-the-badge&logo=okx&logoColor=white" alt="OKX" />
|
|
||||||
</a>
|
|
||||||
<br/><br/>
|
|
||||||
<strong>Leading Derivatives Platform</strong><br/>
|
|
||||||
<small>Spot • Perpetual • Options</small>
|
|
||||||
</td>
|
|
||||||
<td align="center" width="33.33%">
|
|
||||||
<a href="https://share.glassgs.com/u/H8XZGS71" target="_blank">
|
|
||||||
<img src="https://img.shields.io/badge/Bitget-Exchange-1F2937?style=for-the-badge&logo=bitget&logoColor=white" alt="Bitget" />
|
|
||||||
</a>
|
|
||||||
<br/><br/>
|
|
||||||
<strong>Innovative Copy Trading</strong><br/>
|
|
||||||
<small>Spot • Futures • Social Trading</small>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 💝 Direct Support (Donations)
|
### 💝 Direct Support (Donations)
|
||||||
|
|
||||||
|
|||||||
+1
-37
@@ -381,6 +381,7 @@ QuantDinger 支持多种市场类型的执行方式:
|
|||||||
| KuCoin | 现货, 合约 |
|
| KuCoin | 现货, 合约 |
|
||||||
| Gate.io | 现货, 合约 |
|
| Gate.io | 现货, 合约 |
|
||||||
| Bitfinex | 现货, 衍生品 |
|
| Bitfinex | 现货, 衍生品 |
|
||||||
|
| Deepcoin | 现货, 永续 |
|
||||||
|
|
||||||
### 传统券商
|
### 传统券商
|
||||||
|
|
||||||
@@ -613,43 +614,6 @@ QuantDinger 的代码使用 **Apache License 2.0** 授权。但需要注意:**
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 💼 值得信赖的交易所合作伙伴(联盟链接)
|
|
||||||
|
|
||||||
使用我们的合作伙伴链接,在享受相同交易体验的同时支持 QuantDinger 的发展。
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td align="center" width="33.33%">
|
|
||||||
<a href="https://www.bmwweb.ac/referral/earn-together/refer2earn-usdc/claim?hl=zh-CN&ref=GRO_28502_9OSOJ" target="_blank">
|
|
||||||
<img src="https://img.shields.io/badge/Binance-Exchange-F0B90B?style=for-the-badge&logo=binance&logoColor=white" alt="Binance" />
|
|
||||||
</a>
|
|
||||||
<br/><br/>
|
|
||||||
<strong>全球最大的加密货币交易所</strong><br/>
|
|
||||||
<small>现货 • 期货 • 杠杆交易</small>
|
|
||||||
</td>
|
|
||||||
<td align="center" width="33.33%">
|
|
||||||
<a href="https://www.bjwebptyiou.com/join/14449926" target="_blank">
|
|
||||||
<img src="https://img.shields.io/badge/OKX-Exchange-000000?style=for-the-badge&logo=okx&logoColor=white" alt="OKX" />
|
|
||||||
</a>
|
|
||||||
<br/><br/>
|
|
||||||
<strong>领先的衍生品平台</strong><br/>
|
|
||||||
<small>现货 • 永续合约 • 期权</small>
|
|
||||||
</td>
|
|
||||||
<td align="center" width="33.33%">
|
|
||||||
<a href="https://share.glassgs.com/u/H8XZGS71" target="_blank">
|
|
||||||
<img src="https://img.shields.io/badge/Bitget-Exchange-1F2937?style=for-the-badge&logo=bitget&logoColor=white" alt="Bitget" />
|
|
||||||
</a>
|
|
||||||
<br/><br/>
|
|
||||||
<strong>创新的跟单交易</strong><br/>
|
|
||||||
<small>现货 • 期货 • 社交交易</small>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 💝 直接支持(捐赠)
|
### 💝 直接支持(捐赠)
|
||||||
|
|
||||||
你的贡献帮助我们维护和改进 QuantDinger。
|
你的贡献帮助我们维护和改进 QuantDinger。
|
||||||
|
|||||||
+1
-37
@@ -351,6 +351,7 @@ QuantDingerは複数の市場タイプに対して複数の実行方法をサポ
|
|||||||
| KuCoin | 現物, 先物 |
|
| KuCoin | 現物, 先物 |
|
||||||
| Gate.io | 現物, 先物 |
|
| Gate.io | 現物, 先物 |
|
||||||
| Bitfinex | 現物, デリバティブ |
|
| Bitfinex | 現物, デリバティブ |
|
||||||
|
| Deepcoin | 現物, 無期限 |
|
||||||
|
|
||||||
### 伝統的なブローカー
|
### 伝統的なブローカー
|
||||||
|
|
||||||
@@ -569,43 +570,6 @@ QuantDinger のコードは **Apache License 2.0** で提供されています
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 💼 取引所パートナー(アフィリエイトリンク)
|
|
||||||
|
|
||||||
パートナーリンクを使用することで、同じ取引体験を楽しみながら QuantDinger の開発を支援できます。
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td align="center" width="33.33%">
|
|
||||||
<a href="https://www.bmwweb.ac/referral/earn-together/refer2earn-usdc/claim?hl=zh-CN&ref=GRO_28502_9OSOJ" target="_blank">
|
|
||||||
<img src="https://img.shields.io/badge/Binance-Exchange-F0B90B?style=for-the-badge&logo=binance&logoColor=white" alt="Binance" />
|
|
||||||
</a>
|
|
||||||
<br/><br/>
|
|
||||||
<strong>世界最大の暗号通貨取引所</strong><br/>
|
|
||||||
<small>現物 • 先物 • マージン取引</small>
|
|
||||||
</td>
|
|
||||||
<td align="center" width="33.33%">
|
|
||||||
<a href="https://www.bjwebptyiou.com/join/14449926" target="_blank">
|
|
||||||
<img src="https://img.shields.io/badge/OKX-Exchange-000000?style=for-the-badge&logo=okx&logoColor=white" alt="OKX" />
|
|
||||||
</a>
|
|
||||||
<br/><br/>
|
|
||||||
<strong>主要なデリバティブプラットフォーム</strong><br/>
|
|
||||||
<small>現物 • パーペチュアル • オプション</small>
|
|
||||||
</td>
|
|
||||||
<td align="center" width="33.33%">
|
|
||||||
<a href="https://share.glassgs.com/u/H8XZGS71" target="_blank">
|
|
||||||
<img src="https://img.shields.io/badge/Bitget-Exchange-1F2937?style=for-the-badge&logo=bitget&logoColor=white" alt="Bitget" />
|
|
||||||
</a>
|
|
||||||
<br/><br/>
|
|
||||||
<strong>革新的なコピートレード</strong><br/>
|
|
||||||
<small>現物 • 先物 • ソーシャルトレード</small>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 💝 直接サポート(寄付)
|
### 💝 直接サポート(寄付)
|
||||||
|
|
||||||
あなたのご支援は QuantDinger の維持・改善に役立ちます。
|
あなたのご支援は QuantDinger の維持・改善に役立ちます。
|
||||||
|
|||||||
+1
-37
@@ -351,6 +351,7 @@ QuantDinger는 다양한 시장 유형에 대해 여러 실행 방법을 지원
|
|||||||
| KuCoin | 현물, 선물 |
|
| KuCoin | 현물, 선물 |
|
||||||
| Gate.io | 현물, 선물 |
|
| Gate.io | 현물, 선물 |
|
||||||
| Bitfinex | 현물, 파생상품 |
|
| Bitfinex | 현물, 파생상품 |
|
||||||
|
| Deepcoin | 현물, 무기한 |
|
||||||
|
|
||||||
### 전통적인 브로커
|
### 전통적인 브로커
|
||||||
|
|
||||||
@@ -582,43 +583,6 @@ QuantDinger의 코드는 **Apache License 2.0**으로 제공됩니다. 다만 **
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 💼 거래소 파트너(어필리에이트 링크)
|
|
||||||
|
|
||||||
파트너 링크를 사용하면 동일한 거래 경험을 즐기면서 QuantDinger의 개발을 지원할 수 있습니다.
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td align="center" width="33.33%">
|
|
||||||
<a href="https://www.bmwweb.ac/referral/earn-together/refer2earn-usdc/claim?hl=zh-CN&ref=GRO_28502_9OSOJ" target="_blank">
|
|
||||||
<img src="https://img.shields.io/badge/Binance-Exchange-F0B90B?style=for-the-badge&logo=binance&logoColor=white" alt="Binance" />
|
|
||||||
</a>
|
|
||||||
<br/><br/>
|
|
||||||
<strong>세계 최대 암호화폐 거래소</strong><br/>
|
|
||||||
<small>현물 • 선물 • 마진 거래</small>
|
|
||||||
</td>
|
|
||||||
<td align="center" width="33.33%">
|
|
||||||
<a href="https://www.bjwebptyiou.com/join/14449926" target="_blank">
|
|
||||||
<img src="https://img.shields.io/badge/OKX-Exchange-000000?style=for-the-badge&logo=okx&logoColor=white" alt="OKX" />
|
|
||||||
</a>
|
|
||||||
<br/><br/>
|
|
||||||
<strong>주요 파생상품 플랫폼</strong><br/>
|
|
||||||
<small>현물 • 영구 선물 • 옵션</small>
|
|
||||||
</td>
|
|
||||||
<td align="center" width="33.33%">
|
|
||||||
<a href="https://share.glassgs.com/u/H8XZGS71" target="_blank">
|
|
||||||
<img src="https://img.shields.io/badge/Bitget-Exchange-1F2937?style=for-the-badge&logo=bitget&logoColor=white" alt="Bitget" />
|
|
||||||
</a>
|
|
||||||
<br/><br/>
|
|
||||||
<strong>혁신적인 복사 거래</strong><br/>
|
|
||||||
<small>현물 • 선물 • 소셜 거래</small>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 💝 직접 지원(기부)
|
### 💝 직접 지원(기부)
|
||||||
|
|
||||||
귀하의 기여는 QuantDinger의 유지 및 개선에 도움이 됩니다.
|
귀하의 기여는 QuantDinger의 유지 및 개선에 도움이 됩니다.
|
||||||
|
|||||||
+1
-37
@@ -351,6 +351,7 @@ QuantDinger 支持多種市場類型的執行方式:
|
|||||||
| KuCoin | 現貨, 合約 |
|
| KuCoin | 現貨, 合約 |
|
||||||
| Gate.io | 現貨, 合約 |
|
| Gate.io | 現貨, 合約 |
|
||||||
| Bitfinex | 現貨, 衍生品 |
|
| Bitfinex | 現貨, 衍生品 |
|
||||||
|
| Deepcoin | 現貨, 永續 |
|
||||||
|
|
||||||
### 傳統券商
|
### 傳統券商
|
||||||
|
|
||||||
@@ -583,43 +584,6 @@ QuantDinger 的代碼使用 **Apache License 2.0** 授權。但請注意:**Apa
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 💼 值得信賴的交易所合作夥伴(聯盟鏈接)
|
|
||||||
|
|
||||||
使用我們的合作夥伴鏈接,在享受相同交易體驗的同時支持 QuantDinger 的發展。
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td align="center" width="33.33%">
|
|
||||||
<a href="https://www.bmwweb.ac/referral/earn-together/refer2earn-usdc/claim?hl=zh-CN&ref=GRO_28502_9OSOJ" target="_blank">
|
|
||||||
<img src="https://img.shields.io/badge/Binance-Exchange-F0B90B?style=for-the-badge&logo=binance&logoColor=white" alt="Binance" />
|
|
||||||
</a>
|
|
||||||
<br/><br/>
|
|
||||||
<strong>全球最大的加密貨幣交易所</strong><br/>
|
|
||||||
<small>現貨 • 期貨 • 槓桿交易</small>
|
|
||||||
</td>
|
|
||||||
<td align="center" width="33.33%">
|
|
||||||
<a href="https://www.bjwebptyiou.com/join/14449926" target="_blank">
|
|
||||||
<img src="https://img.shields.io/badge/OKX-Exchange-000000?style=for-the-badge&logo=okx&logoColor=white" alt="OKX" />
|
|
||||||
</a>
|
|
||||||
<br/><br/>
|
|
||||||
<strong>領先的衍生品平台</strong><br/>
|
|
||||||
<small>現貨 • 永續合約 • 期權</small>
|
|
||||||
</td>
|
|
||||||
<td align="center" width="33.33%">
|
|
||||||
<a href="https://share.glassgs.com/u/H8XZGS71" target="_blank">
|
|
||||||
<img src="https://img.shields.io/badge/Bitget-Exchange-1F2937?style=for-the-badge&logo=bitget&logoColor=white" alt="Bitget" />
|
|
||||||
</a>
|
|
||||||
<br/><br/>
|
|
||||||
<strong>創新的跟單交易</strong><br/>
|
|
||||||
<small>現貨 • 期貨 • 社交交易</small>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 💝 直接支持(捐贈)
|
### 💝 直接支持(捐贈)
|
||||||
|
|
||||||
你的貢獻幫助我們維護和改進 QuantDinger。
|
你的貢獻幫助我們維護和改進 QuantDinger。
|
||||||
|
|||||||
@@ -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))
|
||||||
@@ -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.kucoin import KucoinSpotClient, KucoinFuturesClient
|
||||||
from app.services.live_trading.gate import GateSpotClient, GateUsdtFuturesClient
|
from app.services.live_trading.gate import GateSpotClient, GateUsdtFuturesClient
|
||||||
from app.services.live_trading.bitfinex import BitfinexClient, BitfinexDerivativesClient
|
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
|
# Lazy import IBKR to avoid ImportError if ib_insync not installed
|
||||||
IBKRClient = None
|
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 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)
|
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)
|
# Traditional brokers (IBKR for US/HK stocks only)
|
||||||
if exchange_id == "ibkr":
|
if exchange_id == "ibkr":
|
||||||
# Note: Market category validation should be done at the caller level
|
# Note: Market category validation should be done at the caller level
|
||||||
|
|||||||
@@ -185,3 +185,38 @@ def to_bitfinex_perp_symbol(symbol: str) -> str:
|
|||||||
return f"t{base}F0:{q}F0"
|
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"
|
||||||
|
|
||||||
|
|||||||
@@ -282,6 +282,7 @@ class StrategyService:
|
|||||||
from app.services.live_trading.kucoin import KucoinFuturesClient
|
from app.services.live_trading.kucoin import KucoinFuturesClient
|
||||||
from app.services.live_trading.gate import GateSpotClient, GateUsdtFuturesClient
|
from app.services.live_trading.gate import GateSpotClient, GateUsdtFuturesClient
|
||||||
from app.services.live_trading.bitfinex import BitfinexClient, BitfinexDerivativesClient
|
from app.services.live_trading.bitfinex import BitfinexClient, BitfinexDerivativesClient
|
||||||
|
from app.services.live_trading.deepcoin import DeepcoinClient
|
||||||
|
|
||||||
resolved = resolve_exchange_config(exchange_config or {})
|
resolved = resolve_exchange_config(exchange_config or {})
|
||||||
safe_cfg = safe_exchange_config_for_log(resolved)
|
safe_cfg = safe_exchange_config_for_log(resolved)
|
||||||
@@ -350,6 +351,8 @@ class StrategyService:
|
|||||||
priv_data = client.get_wallets()
|
priv_data = client.get_wallets()
|
||||||
elif isinstance(client, BitfinexDerivativesClient):
|
elif isinstance(client, BitfinexDerivativesClient):
|
||||||
priv_data = client.get_wallets()
|
priv_data = client.get_wallets()
|
||||||
|
elif isinstance(client, DeepcoinClient):
|
||||||
|
priv_data = client.get_balance()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
msg = str(e)
|
msg = str(e)
|
||||||
# Add actionable hints for the most common Binance auth error.
|
# Add actionable hints for the most common Binance auth error.
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ WORKDIR /app
|
|||||||
|
|
||||||
# Copy package files
|
# Copy package files
|
||||||
COPY package*.json ./
|
COPY package*.json ./
|
||||||
COPY pnpm-lock.yaml* ./
|
|
||||||
COPY yarn.lock* ./
|
|
||||||
|
|
||||||
# Install dependencies (prefer npm)
|
# Install dependencies (prefer npm)
|
||||||
RUN npm install --legacy-peer-deps
|
RUN npm install --legacy-peer-deps
|
||||||
|
|||||||
@@ -1233,7 +1233,8 @@ const locale = {
|
|||||||
'ftxus': 'إف تي إكس الولايات المتحدة',
|
'ftxus': 'إف تي إكس الولايات المتحدة',
|
||||||
'binanceus': 'بينانس الولايات المتحدة',
|
'binanceus': 'بينانس الولايات المتحدة',
|
||||||
'binancecoinm': 'عملة بينانس-M',
|
'binancecoinm': 'عملة بينانس-M',
|
||||||
'binanceusdm': 'Binance USDⓈ-M'
|
'binanceusdm': 'Binance USDⓈ-M',
|
||||||
|
'deepcoin': 'ديبكوين'
|
||||||
},
|
},
|
||||||
'ai-trading-assistant.title': 'مساعد التداول بالذكاء الاصطناعي',
|
'ai-trading-assistant.title': 'مساعد التداول بالذكاء الاصطناعي',
|
||||||
'ai-trading-assistant.strategyList': 'قائمة الإستراتيجية',
|
'ai-trading-assistant.strategyList': 'قائمة الإستراتيجية',
|
||||||
|
|||||||
@@ -1283,7 +1283,8 @@ const locale = {
|
|||||||
'ftxus': 'FTX USA',
|
'ftxus': 'FTX USA',
|
||||||
'binanceus': 'Binance USA',
|
'binanceus': 'Binance USA',
|
||||||
'binancecoinm': 'Binance COIN-M',
|
'binancecoinm': 'Binance COIN-M',
|
||||||
'binanceusdm': 'Binance USDⓈ-M'
|
'binanceusdm': 'Binance USDⓈ-M',
|
||||||
|
'deepcoin': 'Deepcoin'
|
||||||
},
|
},
|
||||||
'ai-trading-assistant.title': 'KI-Handelsassistent',
|
'ai-trading-assistant.title': 'KI-Handelsassistent',
|
||||||
'ai-trading-assistant.strategyList': 'Strategieliste',
|
'ai-trading-assistant.strategyList': 'Strategieliste',
|
||||||
|
|||||||
@@ -1664,7 +1664,8 @@ const locale = {
|
|||||||
'binanceus': 'Binance US',
|
'binanceus': 'Binance US',
|
||||||
'binancecoinm': 'Binance COIN-M',
|
'binancecoinm': 'Binance COIN-M',
|
||||||
'binanceusdm': 'Binance USDⓈ-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.title': 'AI Trading Assistant',
|
||||||
'ai-trading-assistant.strategyList': 'Strategy List',
|
'ai-trading-assistant.strategyList': 'Strategy List',
|
||||||
|
|||||||
@@ -1234,7 +1234,8 @@ const locale = {
|
|||||||
'ftxus': 'FTX États-Unis',
|
'ftxus': 'FTX États-Unis',
|
||||||
'binanceus': 'Binance États-Unis',
|
'binanceus': 'Binance États-Unis',
|
||||||
'binancecoinm': 'Binance COIN-M',
|
'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.title': 'Assistant de trading IA',
|
||||||
'ai-trading-assistant.strategyList': 'Liste de stratégies',
|
'ai-trading-assistant.strategyList': 'Liste de stratégies',
|
||||||
|
|||||||
@@ -1238,7 +1238,8 @@ const locale = {
|
|||||||
'ftxus': 'FTX米国',
|
'ftxus': 'FTX米国',
|
||||||
'binanceus': 'バイナンスUS',
|
'binanceus': 'バイナンスUS',
|
||||||
'binancecoinm': 'バイナンス COIN-M',
|
'binancecoinm': 'バイナンス COIN-M',
|
||||||
'binanceusdm': 'バイナンスUSDⓈ-M'
|
'binanceusdm': 'バイナンスUSDⓈ-M',
|
||||||
|
'deepcoin': 'ディープコイン'
|
||||||
},
|
},
|
||||||
'ai-trading-assistant.title': 'AI取引アシスタント',
|
'ai-trading-assistant.title': 'AI取引アシスタント',
|
||||||
'ai-trading-assistant.strategyList': '戦略一覧',
|
'ai-trading-assistant.strategyList': '戦略一覧',
|
||||||
|
|||||||
@@ -1238,7 +1238,8 @@ const locale = {
|
|||||||
'ftxus': 'FTX 미국',
|
'ftxus': 'FTX 미국',
|
||||||
'binanceus': '바이낸스 미국',
|
'binanceus': '바이낸스 미국',
|
||||||
'binancecoinm': '바이낸스 코인-M',
|
'binancecoinm': '바이낸스 코인-M',
|
||||||
'binanceusdm': '바이낸스 USDⓈ-M'
|
'binanceusdm': '바이낸스 USDⓈ-M',
|
||||||
|
'deepcoin': '딥코인'
|
||||||
},
|
},
|
||||||
'ai-trading-assistant.title': 'AI 트레이딩 도우미',
|
'ai-trading-assistant.title': 'AI 트레이딩 도우미',
|
||||||
'ai-trading-assistant.strategyList': '전략 목록',
|
'ai-trading-assistant.strategyList': '전략 목록',
|
||||||
|
|||||||
@@ -1235,7 +1235,8 @@ const locale = {
|
|||||||
'ftxus': 'FTX สหรัฐอเมริกา',
|
'ftxus': 'FTX สหรัฐอเมริกา',
|
||||||
'binanceus': 'Binance สหรัฐอเมริกา',
|
'binanceus': 'Binance สหรัฐอเมริกา',
|
||||||
'binancecoinm': 'Binance COIN-M',
|
'binancecoinm': 'Binance COIN-M',
|
||||||
'binanceusdm': 'Binance USDⓈ-M'
|
'binanceusdm': 'Binance USDⓈ-M',
|
||||||
|
'deepcoin': 'ดีพคอยน์'
|
||||||
},
|
},
|
||||||
'ai-trading-assistant.title': 'ผู้ช่วยการซื้อขาย AI',
|
'ai-trading-assistant.title': 'ผู้ช่วยการซื้อขาย AI',
|
||||||
'ai-trading-assistant.strategyList': 'รายการกลยุทธ์',
|
'ai-trading-assistant.strategyList': 'รายการกลยุทธ์',
|
||||||
|
|||||||
@@ -2376,7 +2376,9 @@ const locale = {
|
|||||||
|
|
||||||
'binancecoinm': 'Binance COIN-M',
|
'binancecoinm': 'Binance COIN-M',
|
||||||
|
|
||||||
'binanceusdm': 'Binance USDⓈ-M'
|
'binanceusdm': 'Binance USDⓈ-M',
|
||||||
|
|
||||||
|
'deepcoin': 'Deepcoin'
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -1559,7 +1559,8 @@ const locale = {
|
|||||||
'binanceus': 'Binance US',
|
'binanceus': 'Binance US',
|
||||||
'binancecoinm': 'Binance COIN-M',
|
'binancecoinm': 'Binance COIN-M',
|
||||||
'binanceusdm': 'Binance USDⓈ-M',
|
'binanceusdm': 'Binance USDⓈ-M',
|
||||||
'ibkr': '盈透证券 (IBKR)'
|
'ibkr': '盈透证券 (IBKR)',
|
||||||
|
'deepcoin': 'Deepcoin'
|
||||||
},
|
},
|
||||||
'ai-trading-assistant.title': 'AI交易助手',
|
'ai-trading-assistant.title': 'AI交易助手',
|
||||||
'ai-trading-assistant.strategyList': '策略列表',
|
'ai-trading-assistant.strategyList': '策略列表',
|
||||||
|
|||||||
@@ -1421,7 +1421,8 @@ const locale = {
|
|||||||
'ftxus': 'FTX US',
|
'ftxus': 'FTX US',
|
||||||
'binanceus': 'Binance US',
|
'binanceus': 'Binance US',
|
||||||
'binancecoinm': 'Binance COIN-M',
|
'binancecoinm': 'Binance COIN-M',
|
||||||
'binanceusdm': 'Binance USDⓈ-M'
|
'binanceusdm': 'Binance USDⓈ-M',
|
||||||
|
'deepcoin': 'Deepcoin'
|
||||||
},
|
},
|
||||||
'ai-trading-assistant.title': 'AI交易助手',
|
'ai-trading-assistant.title': 'AI交易助手',
|
||||||
'ai-trading-assistant.strategyList': '策略列表',
|
'ai-trading-assistant.strategyList': '策略列表',
|
||||||
|
|||||||
@@ -1255,7 +1255,8 @@ const EXCHANGE_OPTIONS = [
|
|||||||
{ value: 'kraken', labelKey: 'kraken' },
|
{ value: 'kraken', labelKey: 'kraken' },
|
||||||
{ value: 'kucoin', labelKey: 'kucoin' },
|
{ value: 'kucoin', labelKey: 'kucoin' },
|
||||||
{ value: 'gate', labelKey: 'gate' },
|
{ 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
|
// Traditional broker options (US/HK stocks) - extensible for future brokers
|
||||||
@@ -1289,7 +1290,7 @@ export default {
|
|||||||
},
|
},
|
||||||
needsPassphrase () {
|
needsPassphrase () {
|
||||||
// Exchanges that require passphrase
|
// 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)
|
// Check if current market uses IBKR (US Stock / HK Stock)
|
||||||
isIBKRMarket () {
|
isIBKRMarket () {
|
||||||
|
|||||||
Reference in New Issue
Block a user