dcex 0.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dcex/__init__.py +59 -0
- dcex/async_support/__init__.py +104 -0
- dcex/async_support/binance/__init__.py +3 -0
- dcex/async_support/binance/_account_http.py +74 -0
- dcex/async_support/binance/_http_manager.py +141 -0
- dcex/async_support/binance/_market_http.py +276 -0
- dcex/async_support/binance/_trade_http.py +544 -0
- dcex/async_support/binance/client.py +50 -0
- dcex/async_support/binance/endpoints/account.py +22 -0
- dcex/async_support/binance/endpoints/market.py +29 -0
- dcex/async_support/binance/endpoints/trade.py +28 -0
- dcex/async_support/binance/enums.py +13 -0
- dcex/async_support/bingx/__init__.py +3 -0
- dcex/async_support/bingx/_account_http.py +131 -0
- dcex/async_support/bingx/_http_manager.py +185 -0
- dcex/async_support/bingx/_market_http.py +157 -0
- dcex/async_support/bingx/_trade_http.py +936 -0
- dcex/async_support/bingx/client.py +50 -0
- dcex/async_support/bingx/endpoints/account.py +15 -0
- dcex/async_support/bingx/endpoints/market.py +16 -0
- dcex/async_support/bingx/endpoints/trade.py +30 -0
- dcex/async_support/bitmart/__init__.py +3 -0
- dcex/async_support/bitmart/_account_http.py +240 -0
- dcex/async_support/bitmart/_http_manager.py +215 -0
- dcex/async_support/bitmart/_market_http.py +274 -0
- dcex/async_support/bitmart/_trade_http.py +1151 -0
- dcex/async_support/bitmart/client.py +50 -0
- dcex/async_support/bitmart/endpoints/account.py +30 -0
- dcex/async_support/bitmart/endpoints/market.py +32 -0
- dcex/async_support/bitmart/endpoints/trade.py +44 -0
- dcex/async_support/bitmex/__init__.py +3 -0
- dcex/async_support/bitmex/_account_http.py +63 -0
- dcex/async_support/bitmex/_http_manager.py +258 -0
- dcex/async_support/bitmex/_market_http.py +324 -0
- dcex/async_support/bitmex/_position_http.py +235 -0
- dcex/async_support/bitmex/_trade_http.py +602 -0
- dcex/async_support/bitmex/_trading_http.py +171 -0
- dcex/async_support/bitmex/client.py +54 -0
- dcex/async_support/bitmex/endpoints/funds.py +22 -0
- dcex/async_support/bitmex/endpoints/market.py +28 -0
- dcex/async_support/bitmex/endpoints/order.py +26 -0
- dcex/async_support/bitmex/endpoints/positions.py +27 -0
- dcex/async_support/bitmex/endpoints/trading.py +25 -0
- dcex/async_support/bybit/__init__.py +3 -0
- dcex/async_support/bybit/_account_http.py +311 -0
- dcex/async_support/bybit/_asset_http.py +604 -0
- dcex/async_support/bybit/_http_manager.py +238 -0
- dcex/async_support/bybit/_market_http.py +273 -0
- dcex/async_support/bybit/_position_http.py +162 -0
- dcex/async_support/bybit/_trade_http.py +950 -0
- dcex/async_support/bybit/client.py +54 -0
- dcex/async_support/bybit/endpoints/account.py +33 -0
- dcex/async_support/bybit/endpoints/asset.py +41 -0
- dcex/async_support/bybit/endpoints/market.py +29 -0
- dcex/async_support/bybit/endpoints/position.py +26 -0
- dcex/async_support/bybit/endpoints/trade.py +53 -0
- dcex/async_support/gateio/__init__.py +3 -0
- dcex/async_support/gateio/_account_http.py +244 -0
- dcex/async_support/gateio/_http_manager.py +234 -0
- dcex/async_support/gateio/_market_http.py +428 -0
- dcex/async_support/gateio/_trade_http.py +1266 -0
- dcex/async_support/gateio/client.py +50 -0
- dcex/async_support/gateio/endpoints/account.py +53 -0
- dcex/async_support/gateio/endpoints/market.py +61 -0
- dcex/async_support/gateio/endpoints/trade.py +79 -0
- dcex/async_support/okx/__init__.py +3 -0
- dcex/async_support/okx/_account_http.py +827 -0
- dcex/async_support/okx/_asset_http.py +531 -0
- dcex/async_support/okx/_http_manager.py +235 -0
- dcex/async_support/okx/_market_http.py +143 -0
- dcex/async_support/okx/_public_http.py +151 -0
- dcex/async_support/okx/_trade_http.py +1014 -0
- dcex/async_support/okx/client.py +54 -0
- dcex/async_support/okx/endpoints/account.py +34 -0
- dcex/async_support/okx/endpoints/asset.py +27 -0
- dcex/async_support/okx/endpoints/market.py +15 -0
- dcex/async_support/okx/endpoints/public.py +15 -0
- dcex/async_support/okx/endpoints/trade.py +25 -0
- dcex/async_support/product_table/fetch.py +913 -0
- dcex/async_support/product_table/manager.py +554 -0
- dcex/binance/__init__.py +3 -0
- dcex/binance/_account_http.py +74 -0
- dcex/binance/_http_manager.py +180 -0
- dcex/binance/_market_http.py +269 -0
- dcex/binance/_trade_http.py +543 -0
- dcex/binance/client.py +33 -0
- dcex/binance/endpoints/account.py +22 -0
- dcex/binance/endpoints/market.py +28 -0
- dcex/binance/endpoints/trade.py +28 -0
- dcex/binance/enums.py +13 -0
- dcex/bitmart/__init__.py +3 -0
- dcex/bitmart/_account_http.py +254 -0
- dcex/bitmart/_http_manager.py +224 -0
- dcex/bitmart/_market_http.py +314 -0
- dcex/bitmart/_trade_http.py +1520 -0
- dcex/bitmart/client.py +45 -0
- dcex/bitmart/endpoints/account.py +40 -0
- dcex/bitmart/endpoints/market.py +42 -0
- dcex/bitmart/endpoints/trade.py +54 -0
- dcex/bitmex/__init__.py +3 -0
- dcex/bitmex/_account_http.py +63 -0
- dcex/bitmex/_http_manager.py +259 -0
- dcex/bitmex/_market_http.py +324 -0
- dcex/bitmex/_position_http.py +234 -0
- dcex/bitmex/_trade_http.py +648 -0
- dcex/bitmex/_trading_http.py +184 -0
- dcex/bitmex/client.py +69 -0
- dcex/bitmex/endpoints/funds.py +22 -0
- dcex/bitmex/endpoints/market.py +29 -0
- dcex/bitmex/endpoints/order.py +28 -0
- dcex/bitmex/endpoints/positions.py +28 -0
- dcex/bitmex/endpoints/trading.py +27 -0
- dcex/bybit/__init__.py +3 -0
- dcex/bybit/_account_http.py +313 -0
- dcex/bybit/_asset_http.py +604 -0
- dcex/bybit/_http_manager.py +231 -0
- dcex/bybit/_market_http.py +273 -0
- dcex/bybit/_position_http.py +159 -0
- dcex/bybit/_trade_http.py +945 -0
- dcex/bybit/client.py +50 -0
- dcex/bybit/endpoints/account.py +37 -0
- dcex/bybit/endpoints/asset.py +45 -0
- dcex/bybit/endpoints/market.py +30 -0
- dcex/bybit/endpoints/position.py +29 -0
- dcex/bybit/endpoints/trade.py +59 -0
- dcex/gateio/__init__.py +3 -0
- dcex/gateio/_account_http.py +231 -0
- dcex/gateio/_http_manager.py +224 -0
- dcex/gateio/_market_http.py +415 -0
- dcex/gateio/_trade_http.py +1602 -0
- dcex/gateio/client.py +66 -0
- dcex/gateio/endpoints/account.py +60 -0
- dcex/gateio/endpoints/market.py +72 -0
- dcex/gateio/endpoints/trade.py +89 -0
- dcex/okx/__init__.py +3 -0
- dcex/okx/_account_http.py +825 -0
- dcex/okx/_asset_http.py +526 -0
- dcex/okx/_http_manager.py +251 -0
- dcex/okx/_market_http.py +139 -0
- dcex/okx/_public_http.py +106 -0
- dcex/okx/_trade_http.py +995 -0
- dcex/okx/client.py +66 -0
- dcex/okx/endpoints/account.py +49 -0
- dcex/okx/endpoints/asset.py +42 -0
- dcex/okx/endpoints/market.py +28 -0
- dcex/okx/endpoints/public.py +27 -0
- dcex/okx/endpoints/trade.py +40 -0
- dcex/product_table/fetch.py +581 -0
- dcex/product_table/manager.py +482 -0
- dcex/utils/address_utils.py +14 -0
- dcex/utils/common.py +21 -0
- dcex/utils/common_dataframe.py +35 -0
- dcex/utils/decimal_utils.py +15 -0
- dcex/utils/errors.py +74 -0
- dcex/utils/helpers.py +88 -0
- dcex/utils/jupyter_helper.py +105 -0
- dcex/utils/timeframe_utils.py +126 -0
- dcex-0.7.0.dist-info/METADATA +171 -0
- dcex-0.7.0.dist-info/RECORD +162 -0
- dcex-0.7.0.dist-info/WHEEL +5 -0
- dcex-0.7.0.dist-info/licenses/LICENSE +21 -0
- dcex-0.7.0.dist-info/top_level.txt +1 -0
dcex/__init__.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""
|
|
2
|
+
dcex - dex & cex trading library.
|
|
3
|
+
|
|
4
|
+
A comprehensive library for cryptocurrency exchange interactions with both sync and async support.
|
|
5
|
+
Automatically handles Jupyter Notebook compatibility with nest_asyncio.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from .binance.client import Client as BinanceClient
|
|
11
|
+
from .bitmart.client import Client as BitmartClient
|
|
12
|
+
from .bitmex.client import Client as BitmexClient
|
|
13
|
+
from .bybit.client import Client as BybitClient
|
|
14
|
+
from .gateio.client import Client as GateioClient
|
|
15
|
+
from .okx.client import Client as OKXClient
|
|
16
|
+
from .utils.jupyter_helper import auto_apply_nest_asyncio
|
|
17
|
+
|
|
18
|
+
auto_apply_nest_asyncio(verbose=False)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# Create callable functions for each exchange (synchronous clients)
|
|
22
|
+
def binance(**kwargs: Any) -> BinanceClient: # noqa: ANN401
|
|
23
|
+
"""Create a Binance client instance."""
|
|
24
|
+
return BinanceClient(**kwargs)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def bitmart(**kwargs: Any) -> BitmartClient: # noqa: ANN401
|
|
28
|
+
"""Create a BitMart client instance."""
|
|
29
|
+
return BitmartClient(**kwargs)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def bitmex(**kwargs: Any) -> BitmexClient: # noqa: ANN401
|
|
33
|
+
"""Create a BitMEX client instance."""
|
|
34
|
+
return BitmexClient(**kwargs)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def bybit(**kwargs: Any) -> BybitClient: # noqa: ANN401
|
|
38
|
+
"""Create a Bybit client instance."""
|
|
39
|
+
return BybitClient(**kwargs)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def gateio(**kwargs: Any) -> GateioClient: # noqa: ANN401
|
|
43
|
+
"""Create a Gate.io client instance."""
|
|
44
|
+
return GateioClient(**kwargs)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def okx(**kwargs: Any) -> OKXClient: # noqa: ANN401
|
|
48
|
+
"""Create an OKX client instance."""
|
|
49
|
+
return OKXClient(**kwargs)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
__all__ = [
|
|
53
|
+
"binance",
|
|
54
|
+
"bitmart",
|
|
55
|
+
"bitmex",
|
|
56
|
+
"bybit",
|
|
57
|
+
"gateio",
|
|
58
|
+
"okx",
|
|
59
|
+
]
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Async exchange entry points.
|
|
3
|
+
|
|
4
|
+
This module exposes coroutine factory functions for each supported exchange,
|
|
5
|
+
which return an initialized async client (after awaiting `async_init`).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
# Import exchange client classes and create callable functions
|
|
9
|
+
from typing import Any, cast
|
|
10
|
+
|
|
11
|
+
from .binance.client import Client as BinanceClient
|
|
12
|
+
from .bingx.client import Client as BingXClient
|
|
13
|
+
from .bitmart.client import Client as BitmartClient
|
|
14
|
+
from .bitmex.client import Client as BitmexClient
|
|
15
|
+
from .bybit.client import Client as BybitClient
|
|
16
|
+
from .gateio.client import Client as GateioClient
|
|
17
|
+
from .hyperliquid.client import Client as HyperliquidClient
|
|
18
|
+
from .kucoin.client import Client as KuCoinClient
|
|
19
|
+
from .okx.client import Client as OKXClient
|
|
20
|
+
from .zoomex.client import Client as ZoomexClient
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
async def binance(
|
|
24
|
+
**kwargs: Any, # noqa: ANN401
|
|
25
|
+
) -> BinanceClient:
|
|
26
|
+
"""Create and initialize a Binance client instance."""
|
|
27
|
+
return cast(BinanceClient, await BinanceClient(**kwargs).async_init())
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
async def bingx(
|
|
31
|
+
**kwargs: Any, # noqa: ANN401
|
|
32
|
+
) -> BingXClient:
|
|
33
|
+
"""Create and initialize a BingX client instance."""
|
|
34
|
+
return cast(BingXClient, await BingXClient(**kwargs).async_init())
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
async def bitmart(
|
|
38
|
+
**kwargs: Any, # noqa: ANN401
|
|
39
|
+
) -> BitmartClient:
|
|
40
|
+
"""Create and initialize a BitMart client instance."""
|
|
41
|
+
return cast(BitmartClient, await BitmartClient(**kwargs).async_init())
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
async def bitmex(
|
|
45
|
+
**kwargs: Any, # noqa: ANN401
|
|
46
|
+
) -> BitmexClient:
|
|
47
|
+
"""Create and initialize a BitMEX client instance."""
|
|
48
|
+
return cast(BitmexClient, await BitmexClient(**kwargs).async_init())
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
async def bybit(
|
|
52
|
+
**kwargs: Any, # noqa: ANN401
|
|
53
|
+
) -> BybitClient:
|
|
54
|
+
"""Create and initialize a Bybit client instance."""
|
|
55
|
+
return cast(BybitClient, await BybitClient(**kwargs).async_init())
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
async def gateio(
|
|
59
|
+
**kwargs: Any, # noqa: ANN401
|
|
60
|
+
) -> GateioClient:
|
|
61
|
+
"""Create and initialize a Gate.io client instance."""
|
|
62
|
+
return cast(GateioClient, await GateioClient(**kwargs).async_init())
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
async def hyperliquid(
|
|
66
|
+
**kwargs: Any, # noqa: ANN401
|
|
67
|
+
) -> HyperliquidClient:
|
|
68
|
+
"""Create and initialize a Hyperliquid client instance."""
|
|
69
|
+
return cast(HyperliquidClient, await HyperliquidClient(**kwargs).async_init())
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
async def kucoin(
|
|
73
|
+
**kwargs: Any, # noqa: ANN401
|
|
74
|
+
) -> KuCoinClient:
|
|
75
|
+
"""Create and initialize a KuCoin client instance."""
|
|
76
|
+
return cast(KuCoinClient, await KuCoinClient(**kwargs).async_init())
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
async def okx(
|
|
80
|
+
**kwargs: Any, # noqa: ANN401
|
|
81
|
+
) -> OKXClient:
|
|
82
|
+
"""Create and initialize an OKX client instance."""
|
|
83
|
+
return cast(OKXClient, await OKXClient(**kwargs).async_init())
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
async def zoomex(
|
|
87
|
+
**kwargs: Any, # noqa: ANN401
|
|
88
|
+
) -> ZoomexClient:
|
|
89
|
+
"""Create and initialize a Zoomex client instance."""
|
|
90
|
+
return cast(ZoomexClient, await ZoomexClient(**kwargs).async_init())
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
__all__ = [
|
|
94
|
+
"binance",
|
|
95
|
+
"bingx",
|
|
96
|
+
"bitmart",
|
|
97
|
+
"bitmex",
|
|
98
|
+
"bybit",
|
|
99
|
+
"gateio",
|
|
100
|
+
"hyperliquid",
|
|
101
|
+
"kucoin",
|
|
102
|
+
"okx",
|
|
103
|
+
"zoomex",
|
|
104
|
+
]
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
from ...utils.common import Common
|
|
2
|
+
from ._http_manager import HTTPManager
|
|
3
|
+
from .endpoints.account import FuturesAccount, SpotAccount
|
|
4
|
+
from .enums import BinanceProductType
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class AccountHTTP(HTTPManager):
|
|
8
|
+
"""HTTP client for Binance account-related API endpoints."""
|
|
9
|
+
|
|
10
|
+
async def get_account_balance(
|
|
11
|
+
self,
|
|
12
|
+
market_type: str,
|
|
13
|
+
) -> dict:
|
|
14
|
+
"""
|
|
15
|
+
Get account balance.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
market_type: Market type ("spot" or "swap")
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
dict: Account balance information
|
|
22
|
+
"""
|
|
23
|
+
res = await self._request(
|
|
24
|
+
method="GET",
|
|
25
|
+
path=SpotAccount.ACCOUNT_BALANCE
|
|
26
|
+
if market_type == BinanceProductType.SPOT
|
|
27
|
+
else FuturesAccount.ACCOUNT_BALANCE,
|
|
28
|
+
query={},
|
|
29
|
+
)
|
|
30
|
+
return res
|
|
31
|
+
|
|
32
|
+
async def get_income_history(
|
|
33
|
+
self,
|
|
34
|
+
product_symbol: str | None = None,
|
|
35
|
+
incomeType: str | None = None,
|
|
36
|
+
startTime: int | None = None,
|
|
37
|
+
endTime: int | None = None,
|
|
38
|
+
page: int | None = None,
|
|
39
|
+
limit: int | None = None,
|
|
40
|
+
) -> dict:
|
|
41
|
+
"""
|
|
42
|
+
Get futures income history.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
product_symbol: Trading pair symbol (e.g., 'BTCUSDT')
|
|
46
|
+
incomeType: Income type (TRANSFER, WELCOME_BONUS, REALIZED_PNL, FUNDING_FEE, etc.)
|
|
47
|
+
startTime: Start time in milliseconds
|
|
48
|
+
endTime: End time in milliseconds
|
|
49
|
+
page: Page number for pagination
|
|
50
|
+
limit: Number of records per page
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
dict: Income history data
|
|
54
|
+
"""
|
|
55
|
+
payload = {}
|
|
56
|
+
if product_symbol is not None:
|
|
57
|
+
payload["symbol"] = self.ptm.get_exchange_symbol(Common.BINANCE, product_symbol)
|
|
58
|
+
if incomeType is not None:
|
|
59
|
+
payload["incomeType"] = incomeType
|
|
60
|
+
if startTime is not None:
|
|
61
|
+
payload["startTime"] = startTime
|
|
62
|
+
if endTime is not None:
|
|
63
|
+
payload["endTime"] = endTime
|
|
64
|
+
if page is not None:
|
|
65
|
+
payload["page"] = page
|
|
66
|
+
if limit is not None:
|
|
67
|
+
payload["limit"] = limit
|
|
68
|
+
|
|
69
|
+
res = await self._request(
|
|
70
|
+
method="GET",
|
|
71
|
+
path=FuturesAccount.INCOME_HISTORY,
|
|
72
|
+
query=payload,
|
|
73
|
+
)
|
|
74
|
+
return res
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
import hmac
|
|
3
|
+
import logging
|
|
4
|
+
import time
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import Self
|
|
7
|
+
from urllib.parse import urlencode
|
|
8
|
+
|
|
9
|
+
import httpx
|
|
10
|
+
|
|
11
|
+
from ...utils.common import Common
|
|
12
|
+
from ...utils.errors import FailedRequestError
|
|
13
|
+
from ...utils.helpers import generate_timestamp
|
|
14
|
+
from ..product_table.manager import ProductTableManager
|
|
15
|
+
from .endpoints.account import FuturesAccount, SpotAccount
|
|
16
|
+
from .endpoints.market import FuturesMarket, SpotMarket
|
|
17
|
+
from .endpoints.trade import FuturesTrade, SpotTrade
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class HTTPManager:
|
|
22
|
+
api_key: str | None = field(default=None)
|
|
23
|
+
api_secret: str | None = field(default=None)
|
|
24
|
+
timeout: int = field(default=10)
|
|
25
|
+
logger: logging.Logger | None = field(default=None)
|
|
26
|
+
session: httpx.AsyncClient | None = field(default=None, init=False)
|
|
27
|
+
ptm: ProductTableManager = field(init=False)
|
|
28
|
+
preload_product_table: bool = field(default=True)
|
|
29
|
+
|
|
30
|
+
api_map = {
|
|
31
|
+
"https://fapi.binance.com": {
|
|
32
|
+
FuturesTrade,
|
|
33
|
+
FuturesMarket,
|
|
34
|
+
FuturesAccount,
|
|
35
|
+
},
|
|
36
|
+
"https://api.binance.com": {
|
|
37
|
+
SpotMarket,
|
|
38
|
+
SpotTrade,
|
|
39
|
+
SpotAccount,
|
|
40
|
+
},
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async def async_init(self) -> Self:
|
|
44
|
+
self.session = httpx.AsyncClient(timeout=self.timeout)
|
|
45
|
+
self._logger = self.logger or logging.getLogger(__name__)
|
|
46
|
+
if self.preload_product_table:
|
|
47
|
+
self.ptm = await ProductTableManager.get_instance(Common.BINANCE)
|
|
48
|
+
return self
|
|
49
|
+
|
|
50
|
+
def _get_base_url(
|
|
51
|
+
self,
|
|
52
|
+
path: SpotAccount | FuturesAccount | SpotMarket | FuturesMarket | SpotTrade | FuturesTrade,
|
|
53
|
+
) -> str:
|
|
54
|
+
for base_url, enums in self.api_map.items():
|
|
55
|
+
if type(path) in enums:
|
|
56
|
+
return base_url
|
|
57
|
+
raise ValueError(f"Unknown API path: {path} (type={type(path)})")
|
|
58
|
+
|
|
59
|
+
def _sign(self, params: dict) -> str:
|
|
60
|
+
if self.api_secret is None:
|
|
61
|
+
raise ValueError("API secret is required for signing")
|
|
62
|
+
query_string = urlencode(params)
|
|
63
|
+
return hmac.new(self.api_secret.encode(), query_string.encode(), hashlib.sha256).hexdigest()
|
|
64
|
+
|
|
65
|
+
def _headers(self) -> dict[str, str]:
|
|
66
|
+
return {"X-MBX-APIKEY": self.api_key} if self.api_key else {}
|
|
67
|
+
|
|
68
|
+
async def _request(
|
|
69
|
+
self,
|
|
70
|
+
method: str,
|
|
71
|
+
path: SpotAccount | FuturesAccount | SpotMarket | FuturesMarket | SpotTrade | FuturesTrade,
|
|
72
|
+
query: dict | None = None,
|
|
73
|
+
signed: bool = True,
|
|
74
|
+
) -> dict:
|
|
75
|
+
if self.session is None or self.session.is_closed:
|
|
76
|
+
await self.async_init()
|
|
77
|
+
|
|
78
|
+
if query is None:
|
|
79
|
+
query = {}
|
|
80
|
+
|
|
81
|
+
if signed:
|
|
82
|
+
if not (self.api_key and self.api_secret):
|
|
83
|
+
raise ValueError("Signed request requires API Key and Secret.")
|
|
84
|
+
query["timestamp"] = int(time.time() * 1000)
|
|
85
|
+
query["recvWindow"] = 5000
|
|
86
|
+
query["signature"] = self._sign(query)
|
|
87
|
+
|
|
88
|
+
response = None
|
|
89
|
+
try:
|
|
90
|
+
if self.session is None:
|
|
91
|
+
raise ValueError("Session is not initialized")
|
|
92
|
+
base_url = self._get_base_url(path)
|
|
93
|
+
url = f"{base_url}{path}"
|
|
94
|
+
if method.upper() == "GET":
|
|
95
|
+
url += f"?{urlencode(query)}" if query else ""
|
|
96
|
+
response = await self.session.get(url, headers=self._headers())
|
|
97
|
+
elif method.upper() == "POST":
|
|
98
|
+
response = await self.session.post(url, headers=self._headers(), data=query)
|
|
99
|
+
elif method.upper() == "DELETE":
|
|
100
|
+
url += f"?{urlencode(query)}" if query else ""
|
|
101
|
+
response = await self.session.delete(url, headers=self._headers())
|
|
102
|
+
else:
|
|
103
|
+
raise ValueError(f"Unsupported method: {method}")
|
|
104
|
+
|
|
105
|
+
except httpx.RequestError as e:
|
|
106
|
+
raise FailedRequestError(
|
|
107
|
+
request=f"{method.upper()} {url} | Params: {query}",
|
|
108
|
+
message=f"Request failed: {str(e)}",
|
|
109
|
+
status_code=response.status_code if response else "Unknown",
|
|
110
|
+
time=str(query.get("timestamp", "Unknown")),
|
|
111
|
+
resp_headers=dict(response.headers) if response else None,
|
|
112
|
+
) from e
|
|
113
|
+
else:
|
|
114
|
+
try:
|
|
115
|
+
data = response.json()
|
|
116
|
+
except Exception:
|
|
117
|
+
data = {}
|
|
118
|
+
|
|
119
|
+
timestamp = generate_timestamp(iso_format=True)
|
|
120
|
+
|
|
121
|
+
if isinstance(data, dict) and "code" in data and str(data["code"]) != "200":
|
|
122
|
+
code = data.get("code", "Unknown")
|
|
123
|
+
error_message = data.get("msg", "Unknown error")
|
|
124
|
+
raise FailedRequestError(
|
|
125
|
+
request=f"{method} {url} | Body: {query}",
|
|
126
|
+
message=f"BINANCE API Error: [{code}] {error_message}",
|
|
127
|
+
status_code=response.status_code,
|
|
128
|
+
time=str(timestamp),
|
|
129
|
+
resp_headers=dict(response.headers),
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
if not response.status_code // 100 == 2:
|
|
133
|
+
raise FailedRequestError(
|
|
134
|
+
request=f"{method.upper()} {url} | Body: {query}",
|
|
135
|
+
message=f"HTTP Error {response.status_code}: {response.text}",
|
|
136
|
+
status_code=response.status_code,
|
|
137
|
+
time=str(timestamp),
|
|
138
|
+
resp_headers=dict(response.headers),
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
return data
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
from ...utils.common import Common
|
|
2
|
+
from ._http_manager import HTTPManager
|
|
3
|
+
from .endpoints.market import FuturesMarket, SpotMarket
|
|
4
|
+
from .enums import BinanceProductType
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class MarketHTTP(HTTPManager):
|
|
8
|
+
"""HTTP client for Binance market data API endpoints."""
|
|
9
|
+
|
|
10
|
+
async def get_spot_exchange_info(
|
|
11
|
+
self,
|
|
12
|
+
product_symbol: str | None = None,
|
|
13
|
+
product_symbols: list | None = None,
|
|
14
|
+
symbolStatus: str | None = None,
|
|
15
|
+
) -> dict:
|
|
16
|
+
"""
|
|
17
|
+
Get spot exchange information.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
product_symbol: Single trading pair symbol (e.g., 'BTCUSDT')
|
|
21
|
+
product_symbols: List of trading pair symbols
|
|
22
|
+
symbolStatus: Symbol status filter (TRADING, HALT, BREAK)
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
dict: Exchange information including symbols, filters, and trading rules
|
|
26
|
+
"""
|
|
27
|
+
payload = {}
|
|
28
|
+
if product_symbol is not None:
|
|
29
|
+
payload["symbol"] = self.ptm.get_exchange_symbol(Common.BINANCE, product_symbol)
|
|
30
|
+
if product_symbols is not None:
|
|
31
|
+
formatted_symbols = [
|
|
32
|
+
self.ptm.get_exchange_symbol(Common.BINANCE, symbol) for symbol in product_symbols
|
|
33
|
+
]
|
|
34
|
+
payload["symbols"] = str(formatted_symbols).replace("'", '"')
|
|
35
|
+
if symbolStatus is not None:
|
|
36
|
+
payload["symbolStatus"] = symbolStatus
|
|
37
|
+
|
|
38
|
+
res = await self._request(
|
|
39
|
+
method="GET",
|
|
40
|
+
path=SpotMarket.EXCHANGE_INFO,
|
|
41
|
+
query=payload,
|
|
42
|
+
signed=False,
|
|
43
|
+
)
|
|
44
|
+
return res
|
|
45
|
+
|
|
46
|
+
async def get_spot_orderbook(
|
|
47
|
+
self,
|
|
48
|
+
product_symbol: str,
|
|
49
|
+
limit: int | None = None,
|
|
50
|
+
) -> dict:
|
|
51
|
+
"""
|
|
52
|
+
Get spot order book data.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
product_symbol: Trading pair symbol (e.g., 'BTCUSDT')
|
|
56
|
+
limit: Number of orders to return (5, 10, 20, 50, 100, 500, 1000, 5000)
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
dict: Order book data including bids and asks
|
|
60
|
+
"""
|
|
61
|
+
payload = {
|
|
62
|
+
"symbol": self.ptm.get_exchange_symbol(Common.BINANCE, product_symbol),
|
|
63
|
+
}
|
|
64
|
+
if limit is not None:
|
|
65
|
+
payload["limit"] = str(limit)
|
|
66
|
+
res = await self._request(
|
|
67
|
+
method="GET",
|
|
68
|
+
path=SpotMarket.ORDERBOOK,
|
|
69
|
+
query=payload,
|
|
70
|
+
signed=False,
|
|
71
|
+
)
|
|
72
|
+
return res
|
|
73
|
+
|
|
74
|
+
async def get_spot_trades(
|
|
75
|
+
self,
|
|
76
|
+
product_symbol: str,
|
|
77
|
+
limit: int | None = None,
|
|
78
|
+
) -> dict:
|
|
79
|
+
"""
|
|
80
|
+
Get recent spot trades.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
product_symbol: Trading pair symbol (e.g., 'BTCUSDT')
|
|
84
|
+
limit: Number of trades to return (max 1000)
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
dict: Recent trade data
|
|
88
|
+
"""
|
|
89
|
+
payload = {
|
|
90
|
+
"symbol": self.ptm.get_exchange_symbol(Common.BINANCE, product_symbol),
|
|
91
|
+
}
|
|
92
|
+
if limit is not None:
|
|
93
|
+
payload["limit"] = str(limit)
|
|
94
|
+
|
|
95
|
+
res = await self._request(
|
|
96
|
+
method="GET",
|
|
97
|
+
path=SpotMarket.TRADES,
|
|
98
|
+
query=payload,
|
|
99
|
+
signed=False,
|
|
100
|
+
)
|
|
101
|
+
return res
|
|
102
|
+
|
|
103
|
+
async def get_spot_price(
|
|
104
|
+
self,
|
|
105
|
+
product_symbol: str | None = None,
|
|
106
|
+
product_symbols: list | None = None,
|
|
107
|
+
) -> dict:
|
|
108
|
+
"""
|
|
109
|
+
Get spot price information.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
product_symbol: Single trading pair symbol (e.g., 'BTCUSDT')
|
|
113
|
+
product_symbols: List of trading pair symbols
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
dict: Price information
|
|
117
|
+
"""
|
|
118
|
+
payload = {}
|
|
119
|
+
if product_symbol is not None:
|
|
120
|
+
payload["symbol"] = self.ptm.get_exchange_symbol(Common.BINANCE, product_symbol)
|
|
121
|
+
if product_symbols is not None:
|
|
122
|
+
formatted_symbols = [
|
|
123
|
+
self.ptm.get_exchange_symbol(Common.BINANCE, symbol) for symbol in product_symbols
|
|
124
|
+
]
|
|
125
|
+
payload["symbols"] = str(formatted_symbols).replace("'", '"')
|
|
126
|
+
|
|
127
|
+
res = await self._request(
|
|
128
|
+
method="GET",
|
|
129
|
+
path=SpotMarket.PRICE,
|
|
130
|
+
query=payload,
|
|
131
|
+
signed=False,
|
|
132
|
+
)
|
|
133
|
+
return res
|
|
134
|
+
|
|
135
|
+
async def get_klines(
|
|
136
|
+
self,
|
|
137
|
+
product_symbol: str,
|
|
138
|
+
interval: str,
|
|
139
|
+
start_time: int | None = None,
|
|
140
|
+
limit: int | None = None,
|
|
141
|
+
) -> dict:
|
|
142
|
+
"""
|
|
143
|
+
Get kline/candlestick data.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
product_symbol: Trading pair symbol (e.g., 'BTCUSDT')
|
|
147
|
+
interval: Time interval (1m, 3m, 5m, 15m, 30m, 1h, 2h, 4h, 6h, 8h, 12h, 1d, 3d, 1w, 1M)
|
|
148
|
+
start_time: Start time in milliseconds
|
|
149
|
+
limit: Number of klines to return (max 1000)
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
dict: Kline data including OHLCV
|
|
153
|
+
"""
|
|
154
|
+
payload = {
|
|
155
|
+
"symbol": self.ptm.get_exchange_symbol(Common.BINANCE, product_symbol),
|
|
156
|
+
"interval": interval,
|
|
157
|
+
}
|
|
158
|
+
if start_time is not None:
|
|
159
|
+
payload["startTime"] = str(start_time)
|
|
160
|
+
if limit is not None:
|
|
161
|
+
payload["limit"] = str(limit)
|
|
162
|
+
|
|
163
|
+
res = await self._request(
|
|
164
|
+
method="GET",
|
|
165
|
+
path=SpotMarket.KLINES
|
|
166
|
+
if self.ptm.get_exchange_type(Common.BINANCE, product_symbol=product_symbol)
|
|
167
|
+
== BinanceProductType.SPOT
|
|
168
|
+
else FuturesMarket.KLINES,
|
|
169
|
+
query=payload,
|
|
170
|
+
signed=False,
|
|
171
|
+
)
|
|
172
|
+
return res
|
|
173
|
+
|
|
174
|
+
async def get_futures_exchange_info(
|
|
175
|
+
self,
|
|
176
|
+
) -> dict:
|
|
177
|
+
"""
|
|
178
|
+
Get futures exchange information.
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
dict: Futures exchange information including symbols and trading rules
|
|
182
|
+
"""
|
|
183
|
+
res = await self._request(
|
|
184
|
+
method="GET",
|
|
185
|
+
path=FuturesMarket.EXCHANGE_INFO,
|
|
186
|
+
query={},
|
|
187
|
+
signed=False,
|
|
188
|
+
)
|
|
189
|
+
return res
|
|
190
|
+
|
|
191
|
+
async def get_futures_ticker(
|
|
192
|
+
self,
|
|
193
|
+
product_symbol: str | None = None,
|
|
194
|
+
) -> dict:
|
|
195
|
+
"""
|
|
196
|
+
Get futures ticker information.
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
product_symbol: Trading pair symbol (e.g., 'BTCUSDT')
|
|
200
|
+
|
|
201
|
+
Returns:
|
|
202
|
+
dict: Futures ticker data including price and volume
|
|
203
|
+
"""
|
|
204
|
+
payload = {}
|
|
205
|
+
if product_symbol is not None:
|
|
206
|
+
payload["symbol"] = self.ptm.get_exchange_symbol(Common.BINANCE, product_symbol)
|
|
207
|
+
|
|
208
|
+
res = await self._request(
|
|
209
|
+
method="GET",
|
|
210
|
+
path=FuturesMarket.BOOK_TICKER,
|
|
211
|
+
query=payload,
|
|
212
|
+
signed=False,
|
|
213
|
+
)
|
|
214
|
+
return res
|
|
215
|
+
|
|
216
|
+
async def get_futures_premium_index(
|
|
217
|
+
self,
|
|
218
|
+
product_symbol: str | None = None,
|
|
219
|
+
) -> dict:
|
|
220
|
+
"""
|
|
221
|
+
Get futures premium index.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
product_symbol: Trading pair symbol (e.g., 'BTCUSDT')
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
dict: Premium index data
|
|
228
|
+
"""
|
|
229
|
+
payload = {}
|
|
230
|
+
if product_symbol is not None:
|
|
231
|
+
payload["symbol"] = self.ptm.get_exchange_symbol(Common.BINANCE, product_symbol)
|
|
232
|
+
|
|
233
|
+
res = await self._request(
|
|
234
|
+
method="GET",
|
|
235
|
+
path=FuturesMarket.PREMIUM_INDEX,
|
|
236
|
+
query=payload,
|
|
237
|
+
signed=False,
|
|
238
|
+
)
|
|
239
|
+
return res
|
|
240
|
+
|
|
241
|
+
async def get_futures_funding_rate(
|
|
242
|
+
self,
|
|
243
|
+
product_symbol: str | None = None,
|
|
244
|
+
startTime: int | None = None,
|
|
245
|
+
endTime: int | None = None,
|
|
246
|
+
limit: int | None = None,
|
|
247
|
+
) -> dict:
|
|
248
|
+
"""
|
|
249
|
+
Get futures funding rate history.
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
product_symbol: Trading pair symbol (e.g., 'BTCUSDT')
|
|
253
|
+
startTime: Start time in milliseconds
|
|
254
|
+
endTime: End time in milliseconds
|
|
255
|
+
limit: Number of records to return (max 1000)
|
|
256
|
+
|
|
257
|
+
Returns:
|
|
258
|
+
dict: Funding rate history data
|
|
259
|
+
"""
|
|
260
|
+
payload = {}
|
|
261
|
+
if product_symbol is not None:
|
|
262
|
+
payload["symbol"] = self.ptm.get_exchange_symbol(Common.BINANCE, product_symbol)
|
|
263
|
+
if startTime is not None:
|
|
264
|
+
payload["startTime"] = startTime
|
|
265
|
+
if endTime is not None:
|
|
266
|
+
payload["endTime"] = endTime
|
|
267
|
+
if limit is not None:
|
|
268
|
+
payload["limit"] = limit
|
|
269
|
+
|
|
270
|
+
res = await self._request(
|
|
271
|
+
method="GET",
|
|
272
|
+
path=FuturesMarket.FUNDING_RATE_HISTORY,
|
|
273
|
+
query=payload,
|
|
274
|
+
signed=False,
|
|
275
|
+
)
|
|
276
|
+
return res
|