trd-utils 0.0.57__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.
- trd_utils/__init__.py +3 -0
- trd_utils/cipher/__init__.py +44 -0
- trd_utils/common_utils/float_utils.py +21 -0
- trd_utils/common_utils/wallet_utils.py +26 -0
- trd_utils/date_utils/__init__.py +8 -0
- trd_utils/date_utils/datetime_helpers.py +25 -0
- trd_utils/exchanges/README.md +203 -0
- trd_utils/exchanges/__init__.py +28 -0
- trd_utils/exchanges/base_types.py +229 -0
- trd_utils/exchanges/binance/__init__.py +13 -0
- trd_utils/exchanges/binance/binance_client.py +389 -0
- trd_utils/exchanges/binance/binance_types.py +116 -0
- trd_utils/exchanges/blofin/__init__.py +6 -0
- trd_utils/exchanges/blofin/blofin_client.py +375 -0
- trd_utils/exchanges/blofin/blofin_types.py +173 -0
- trd_utils/exchanges/bx_ultra/__init__.py +6 -0
- trd_utils/exchanges/bx_ultra/bx_types.py +1338 -0
- trd_utils/exchanges/bx_ultra/bx_ultra_client.py +1123 -0
- trd_utils/exchanges/bx_ultra/bx_utils.py +51 -0
- trd_utils/exchanges/errors.py +10 -0
- trd_utils/exchanges/exchange_base.py +301 -0
- trd_utils/exchanges/hyperliquid/README.md +3 -0
- trd_utils/exchanges/hyperliquid/__init__.py +7 -0
- trd_utils/exchanges/hyperliquid/hyperliquid_client.py +292 -0
- trd_utils/exchanges/hyperliquid/hyperliquid_types.py +183 -0
- trd_utils/exchanges/okx/__init__.py +6 -0
- trd_utils/exchanges/okx/okx_client.py +219 -0
- trd_utils/exchanges/okx/okx_types.py +197 -0
- trd_utils/exchanges/price_fetcher.py +48 -0
- trd_utils/html_utils/__init__.py +26 -0
- trd_utils/html_utils/html_formats.py +72 -0
- trd_utils/tradingview/__init__.py +8 -0
- trd_utils/tradingview/tradingview_client.py +128 -0
- trd_utils/tradingview/tradingview_types.py +185 -0
- trd_utils/types_helper/__init__.py +12 -0
- trd_utils/types_helper/base_model.py +350 -0
- trd_utils/types_helper/decorators.py +20 -0
- trd_utils/types_helper/model_config.py +6 -0
- trd_utils/types_helper/ultra_list.py +39 -0
- trd_utils/types_helper/utils.py +40 -0
- trd_utils-0.0.57.dist-info/METADATA +42 -0
- trd_utils-0.0.57.dist-info/RECORD +44 -0
- trd_utils-0.0.57.dist-info/WHEEL +4 -0
- trd_utils-0.0.57.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from decimal import Decimal
|
|
3
|
+
import asyncio
|
|
4
|
+
import logging
|
|
5
|
+
from typing import Callable
|
|
6
|
+
import httpx
|
|
7
|
+
import pytz
|
|
8
|
+
|
|
9
|
+
from trd_utils.exchanges.base_types import (
|
|
10
|
+
UnifiedFuturesMarketInfo,
|
|
11
|
+
UnifiedPositionInfo,
|
|
12
|
+
UnifiedSingleFutureMarketInfo,
|
|
13
|
+
UnifiedTraderInfo,
|
|
14
|
+
UnifiedTraderPositions,
|
|
15
|
+
)
|
|
16
|
+
from trd_utils.exchanges.exchange_base import ExchangeBase
|
|
17
|
+
from trd_utils.exchanges.binance.binance_types import (
|
|
18
|
+
BinanceLeaderboardResponse,
|
|
19
|
+
BinanceLeaderboardBaseInfoResponse,
|
|
20
|
+
BinanceTicker24h,
|
|
21
|
+
BinancePremiumIndex,
|
|
22
|
+
)
|
|
23
|
+
from trd_utils.types_helper import new_list
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
BASE_LEADERBOARD_PROFILE_URL = (
|
|
28
|
+
"https://www.binance.com/en/futures-activity/leaderboard/user/um"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class BinanceClient(ExchangeBase):
|
|
33
|
+
###########################################################
|
|
34
|
+
# region client parameters
|
|
35
|
+
|
|
36
|
+
# Public Futures API
|
|
37
|
+
binance_fapi_base_url: str = "https://fapi.binance.com"
|
|
38
|
+
|
|
39
|
+
# Internal Web API (for Leaderboard)
|
|
40
|
+
binance_bapi_base_url: str = "https://www.binance.com/bapi/futures"
|
|
41
|
+
|
|
42
|
+
default_quote_token: str = "USDT"
|
|
43
|
+
supported_quote_tokens: list[str] = [
|
|
44
|
+
"USDT",
|
|
45
|
+
"USDC",
|
|
46
|
+
"BUSD",
|
|
47
|
+
"BTC",
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
# endregion
|
|
51
|
+
###########################################################
|
|
52
|
+
# region client constructor
|
|
53
|
+
def __init__(
|
|
54
|
+
self,
|
|
55
|
+
account_name: str = "default",
|
|
56
|
+
http_verify: bool = True,
|
|
57
|
+
read_session_file: bool = False,
|
|
58
|
+
sessions_dir: str = "sessions",
|
|
59
|
+
use_http1: bool = True,
|
|
60
|
+
use_http2: bool = False,
|
|
61
|
+
):
|
|
62
|
+
# Binance supports HTTP2, but we respect the flags passed
|
|
63
|
+
self.httpx_client = httpx.AsyncClient(
|
|
64
|
+
verify=http_verify,
|
|
65
|
+
http1=use_http1,
|
|
66
|
+
http2=use_http2,
|
|
67
|
+
)
|
|
68
|
+
self.account_name = account_name
|
|
69
|
+
self.sessions_dir = sessions_dir
|
|
70
|
+
self.exchange_name = "binance"
|
|
71
|
+
|
|
72
|
+
super().__init__()
|
|
73
|
+
|
|
74
|
+
if read_session_file:
|
|
75
|
+
self.read_from_session_file(f"{sessions_dir}/{self.account_name}.bin")
|
|
76
|
+
|
|
77
|
+
# endregion
|
|
78
|
+
###########################################################
|
|
79
|
+
# region info endpoints (Leaderboard & FAPI)
|
|
80
|
+
|
|
81
|
+
async def get_leaderboard_positions(
|
|
82
|
+
self,
|
|
83
|
+
encrypted_uid: str,
|
|
84
|
+
) -> BinanceLeaderboardResponse:
|
|
85
|
+
"""
|
|
86
|
+
Fetches positions from the Binance Futures Leaderboard.
|
|
87
|
+
UID must be the 'encryptedUid'.
|
|
88
|
+
"""
|
|
89
|
+
payload = {
|
|
90
|
+
"encryptedUid": encrypted_uid,
|
|
91
|
+
"tradeType": "PERPETUAL",
|
|
92
|
+
}
|
|
93
|
+
# BAPI requires headers that look like a web browser
|
|
94
|
+
headers = self.get_headers(needs_browser_simulation=True)
|
|
95
|
+
|
|
96
|
+
return await self.invoke_post(
|
|
97
|
+
f"{self.binance_bapi_base_url}/v1/public/future/leaderboard/getOtherPosition",
|
|
98
|
+
headers=headers,
|
|
99
|
+
content=payload,
|
|
100
|
+
model_type=BinanceLeaderboardResponse,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
async def get_leaderboard_base_info(
|
|
104
|
+
self,
|
|
105
|
+
encrypted_uid: str,
|
|
106
|
+
) -> BinanceLeaderboardBaseInfoResponse:
|
|
107
|
+
payload = {
|
|
108
|
+
"encryptedUid": encrypted_uid,
|
|
109
|
+
}
|
|
110
|
+
headers = self.get_headers(needs_browser_simulation=True)
|
|
111
|
+
|
|
112
|
+
return await self.invoke_post(
|
|
113
|
+
f"{self.binance_bapi_base_url}/v2/public/future/leaderboard/getOtherLeaderboardBaseInfo",
|
|
114
|
+
headers=headers,
|
|
115
|
+
content=payload,
|
|
116
|
+
model_type=BinanceLeaderboardBaseInfoResponse,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
async def get_market_tickers(self) -> list[BinanceTicker24h]:
|
|
120
|
+
"""
|
|
121
|
+
Fetches 24hr ticker for all symbols from FAPI.
|
|
122
|
+
"""
|
|
123
|
+
headers = self.get_headers()
|
|
124
|
+
data = await self.invoke_get(
|
|
125
|
+
f"{self.binance_fapi_base_url}/fapi/v1/ticker/24hr",
|
|
126
|
+
headers=headers,
|
|
127
|
+
model_type=None, # returns a list, handled below
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
if not isinstance(data, list):
|
|
131
|
+
return []
|
|
132
|
+
|
|
133
|
+
return [BinanceTicker24h.deserialize(item) for item in data]
|
|
134
|
+
|
|
135
|
+
async def get_premium_indices(self) -> list[BinancePremiumIndex]:
|
|
136
|
+
"""
|
|
137
|
+
Fetches premium index (includes funding rates) from FAPI.
|
|
138
|
+
"""
|
|
139
|
+
headers = self.get_headers()
|
|
140
|
+
data = await self.invoke_get(
|
|
141
|
+
f"{self.binance_fapi_base_url}/fapi/v1/premiumIndex",
|
|
142
|
+
headers=headers,
|
|
143
|
+
model_type=None, # returns a list, handled below
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
if not isinstance(data, list):
|
|
147
|
+
return []
|
|
148
|
+
|
|
149
|
+
return [BinancePremiumIndex.deserialize(item) for item in data]
|
|
150
|
+
|
|
151
|
+
# endregion
|
|
152
|
+
###########################################################
|
|
153
|
+
# region client helper methods
|
|
154
|
+
def get_headers(
|
|
155
|
+
self,
|
|
156
|
+
payload=None,
|
|
157
|
+
needs_auth: bool = False,
|
|
158
|
+
needs_browser_simulation: bool = False,
|
|
159
|
+
) -> dict:
|
|
160
|
+
the_headers = {
|
|
161
|
+
"Content-Type": "application/json",
|
|
162
|
+
"Accept": "application/json",
|
|
163
|
+
"User-Agent": self.user_agent,
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if needs_browser_simulation:
|
|
167
|
+
# The internal B-API often rejects standard API or mobile user agents.
|
|
168
|
+
# We simulate a desktop browser here.
|
|
169
|
+
the_headers["User-Agent"] = (
|
|
170
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
|
|
171
|
+
"AppleWebKit/537.36 (KHTML, like Gecko) "
|
|
172
|
+
"Chrome/120.0.0.0 Safari/537.36"
|
|
173
|
+
)
|
|
174
|
+
the_headers["ClientType"] = "web"
|
|
175
|
+
the_headers["Client-Type"] = "web"
|
|
176
|
+
the_headers["lang"] = "en"
|
|
177
|
+
|
|
178
|
+
if self.x_requested_with:
|
|
179
|
+
the_headers["X-Requested-With"] = self.x_requested_with
|
|
180
|
+
|
|
181
|
+
# TODO: Implement API Key signature logic here if private endpoints are added later
|
|
182
|
+
# if needs_auth: ...
|
|
183
|
+
|
|
184
|
+
return the_headers
|
|
185
|
+
|
|
186
|
+
def read_from_session_file(self, file_path: str) -> None:
|
|
187
|
+
# Not strictly needed for public leaderboard scraping, but kept for consistency
|
|
188
|
+
# with the project structure (e.g. loading proxies or optional keys later).
|
|
189
|
+
pass
|
|
190
|
+
|
|
191
|
+
def _save_session_file(self, file_path: str) -> None:
|
|
192
|
+
pass
|
|
193
|
+
|
|
194
|
+
def _extract_quote_token_from_symbol(self, symbol: str) -> str:
|
|
195
|
+
for quote in self.supported_quote_tokens:
|
|
196
|
+
if symbol.endswith(quote):
|
|
197
|
+
return quote
|
|
198
|
+
return None
|
|
199
|
+
|
|
200
|
+
# endregion
|
|
201
|
+
###########################################################
|
|
202
|
+
# region unified methods
|
|
203
|
+
|
|
204
|
+
async def get_unified_trader_positions(
|
|
205
|
+
self,
|
|
206
|
+
uid: int | str,
|
|
207
|
+
min_margin: Decimal = 0,
|
|
208
|
+
) -> UnifiedTraderPositions:
|
|
209
|
+
"""
|
|
210
|
+
UID must be the encryptedUid (string).
|
|
211
|
+
"""
|
|
212
|
+
resp = await self.get_leaderboard_positions(encrypted_uid=str(uid))
|
|
213
|
+
|
|
214
|
+
unified_result = UnifiedTraderPositions()
|
|
215
|
+
unified_result.positions = new_list()
|
|
216
|
+
|
|
217
|
+
if (
|
|
218
|
+
not resp
|
|
219
|
+
or not resp.success
|
|
220
|
+
or not resp.data
|
|
221
|
+
or not resp.data.other_position_ret_list
|
|
222
|
+
):
|
|
223
|
+
return unified_result
|
|
224
|
+
|
|
225
|
+
for pos in resp.data.other_position_ret_list:
|
|
226
|
+
# Binance Leaderboard usually doesn't provide isolated/cross margin mode info publicly.
|
|
227
|
+
# It also doesn't provide direct margin used, so we estimate it:
|
|
228
|
+
# Margin = (Amount * MarkPrice) / Leverage
|
|
229
|
+
if not pos.mark_price or not pos.leverage:
|
|
230
|
+
continue
|
|
231
|
+
|
|
232
|
+
notional_value = abs(pos.amount * pos.mark_price)
|
|
233
|
+
margin_used = (
|
|
234
|
+
notional_value / Decimal(pos.leverage) if pos.leverage > 0 else 0
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
if min_margin and margin_used < min_margin:
|
|
238
|
+
continue
|
|
239
|
+
|
|
240
|
+
unified_pos = UnifiedPositionInfo()
|
|
241
|
+
unified_pos.position_id = pos.get_position_id(str(uid))
|
|
242
|
+
unified_pos.position_pnl = round(pos.pnl, 3)
|
|
243
|
+
unified_pos.position_side = pos.get_side()
|
|
244
|
+
unified_pos.margin_mode = "cross" # Default assumption for leaderboard
|
|
245
|
+
unified_pos.position_leverage = Decimal(pos.leverage)
|
|
246
|
+
unified_pos.position_pair = pos.symbol
|
|
247
|
+
|
|
248
|
+
# Binance timestamps are in milliseconds
|
|
249
|
+
if pos.update_time_stamp:
|
|
250
|
+
unified_pos.open_time = datetime.fromtimestamp(
|
|
251
|
+
pos.update_time_stamp / 1000, tz=pytz.UTC
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
unified_pos.open_price = pos.entry_price
|
|
255
|
+
unified_pos.open_price_unit = self.default_quote_token
|
|
256
|
+
unified_pos.position_size = abs(pos.amount)
|
|
257
|
+
unified_pos.initial_margin = margin_used
|
|
258
|
+
|
|
259
|
+
# We can fill current price if we want, but it's optional in base_types
|
|
260
|
+
unified_pos.last_price = pos.mark_price
|
|
261
|
+
|
|
262
|
+
unified_result.positions.append(unified_pos)
|
|
263
|
+
|
|
264
|
+
return unified_result
|
|
265
|
+
|
|
266
|
+
async def get_unified_trader_info(
|
|
267
|
+
self,
|
|
268
|
+
uid: int | str,
|
|
269
|
+
) -> UnifiedTraderInfo:
|
|
270
|
+
resp = await self.get_leaderboard_base_info(encrypted_uid=str(uid))
|
|
271
|
+
|
|
272
|
+
unified_info = UnifiedTraderInfo()
|
|
273
|
+
unified_info.trader_id = str(uid)
|
|
274
|
+
|
|
275
|
+
if resp.success and resp.data:
|
|
276
|
+
unified_info.trader_name = resp.data.nick_name
|
|
277
|
+
else:
|
|
278
|
+
# If the request fails or data is hidden, we set a default
|
|
279
|
+
unified_info.trader_name = "Unknown Binance Trader"
|
|
280
|
+
|
|
281
|
+
unified_info.trader_url = (
|
|
282
|
+
f"{BASE_LEADERBOARD_PROFILE_URL}?encryptedUid={uid}"
|
|
283
|
+
)
|
|
284
|
+
unified_info.win_rate = None # Not provided in the base info endpoint
|
|
285
|
+
|
|
286
|
+
return unified_info
|
|
287
|
+
|
|
288
|
+
async def get_unified_futures_market_info(
|
|
289
|
+
self,
|
|
290
|
+
sort_by: str = "percentage_change_24h",
|
|
291
|
+
descending: bool = True,
|
|
292
|
+
allow_delisted: bool = False,
|
|
293
|
+
filter_quote_token: str | None = None,
|
|
294
|
+
raise_on_invalid: bool = False,
|
|
295
|
+
filter_func: Callable | None = None,
|
|
296
|
+
) -> UnifiedFuturesMarketInfo:
|
|
297
|
+
# We fetch tickers and premium indices (for funding rates) in parallel
|
|
298
|
+
tickers_task = self.get_market_tickers()
|
|
299
|
+
premium_index_task = self.get_premium_indices()
|
|
300
|
+
|
|
301
|
+
tickers, premium_indices = await asyncio.gather(
|
|
302
|
+
tickers_task, premium_index_task
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
# Map funding rates: Symbol -> FundingRate
|
|
306
|
+
funding_map = {p.symbol: p.last_funding_rate for p in premium_indices}
|
|
307
|
+
|
|
308
|
+
unified_info = UnifiedFuturesMarketInfo()
|
|
309
|
+
unified_info.sorted_markets = []
|
|
310
|
+
|
|
311
|
+
for ticker in tickers:
|
|
312
|
+
if "_" in ticker.symbol:
|
|
313
|
+
# we don't want delivery or other special markets
|
|
314
|
+
continue
|
|
315
|
+
|
|
316
|
+
symbol = ticker.symbol
|
|
317
|
+
|
|
318
|
+
# Parse Base and Quote from the symbol string (e.g. BTCUSDT -> BTC, USDT)
|
|
319
|
+
# Binance Futures symbols are usually {Base}{Quote}.
|
|
320
|
+
base_asset = symbol
|
|
321
|
+
quote_asset = self.default_quote_token
|
|
322
|
+
|
|
323
|
+
# Basic suffix checking to separate Base and Quote
|
|
324
|
+
extracted_quote = self._extract_quote_token_from_symbol(symbol)
|
|
325
|
+
if extracted_quote:
|
|
326
|
+
quote_asset = extracted_quote
|
|
327
|
+
base_asset = symbol[:-len(quote_asset)]
|
|
328
|
+
else:
|
|
329
|
+
if raise_on_invalid:
|
|
330
|
+
raise ValueError(
|
|
331
|
+
f"Unrecognized symbol format: {symbol} "
|
|
332
|
+
"Please report this issue to the developers."
|
|
333
|
+
)
|
|
334
|
+
continue
|
|
335
|
+
|
|
336
|
+
if filter_quote_token and quote_asset != filter_quote_token:
|
|
337
|
+
continue
|
|
338
|
+
|
|
339
|
+
current_market = UnifiedSingleFutureMarketInfo()
|
|
340
|
+
current_market.name = base_asset
|
|
341
|
+
current_market.pair = f"{base_asset}/{quote_asset}:{quote_asset}"
|
|
342
|
+
|
|
343
|
+
current_market.price = ticker.last_price
|
|
344
|
+
if ticker.prev_close_price is None:
|
|
345
|
+
current_market.previous_day_price = ticker.last_price - ticker.price_change
|
|
346
|
+
else:
|
|
347
|
+
current_market.previous_day_price = ticker.prev_close_price
|
|
348
|
+
current_market.absolute_change_24h = ticker.price_change
|
|
349
|
+
current_market.percentage_change_24h = ticker.price_change_percent
|
|
350
|
+
current_market.funding_rate = funding_map.get(ticker.symbol, Decimal(0))
|
|
351
|
+
current_market.daily_volume = ticker.quote_volume # Quote Asset Volume
|
|
352
|
+
current_market.open_interest = Decimal(0)
|
|
353
|
+
|
|
354
|
+
if filter_func:
|
|
355
|
+
filter_args = {
|
|
356
|
+
"pair": current_market.pair,
|
|
357
|
+
"market_info": current_market,
|
|
358
|
+
"raw_ticker": ticker,
|
|
359
|
+
"exchange_client": self,
|
|
360
|
+
}
|
|
361
|
+
# this is defined in exchange base.
|
|
362
|
+
should_include = await self._apply_filter_func(
|
|
363
|
+
filter_func=filter_func,
|
|
364
|
+
func_args=filter_args,
|
|
365
|
+
)
|
|
366
|
+
if not should_include:
|
|
367
|
+
continue
|
|
368
|
+
|
|
369
|
+
unified_info.sorted_markets.append(current_market)
|
|
370
|
+
|
|
371
|
+
if not sort_by:
|
|
372
|
+
return unified_info
|
|
373
|
+
|
|
374
|
+
def key_fn(market: UnifiedSingleFutureMarketInfo):
|
|
375
|
+
val = getattr(market, sort_by, None)
|
|
376
|
+
if val is None:
|
|
377
|
+
return Decimal("-Infinity") if descending else Decimal("Infinity")
|
|
378
|
+
return val
|
|
379
|
+
|
|
380
|
+
unified_info.sorted_markets = new_list(sorted(
|
|
381
|
+
unified_info.sorted_markets,
|
|
382
|
+
key=key_fn,
|
|
383
|
+
reverse=descending,
|
|
384
|
+
))
|
|
385
|
+
return unified_info
|
|
386
|
+
|
|
387
|
+
# endregion
|
|
388
|
+
###########################################################
|
|
389
|
+
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
|
|
2
|
+
from decimal import Decimal
|
|
3
|
+
from trd_utils.types_helper import BaseModel
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
###########################################################
|
|
7
|
+
# region Leaderboard Types (B-API)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class BinanceLeaderboardPosition(BaseModel):
|
|
11
|
+
symbol: str = None
|
|
12
|
+
entry_price: Decimal = None
|
|
13
|
+
mark_price: Decimal = None
|
|
14
|
+
pnl: Decimal = None
|
|
15
|
+
roe: Decimal = None
|
|
16
|
+
amount: Decimal = None
|
|
17
|
+
leverage: int = None
|
|
18
|
+
update_time_stamp: int = None
|
|
19
|
+
|
|
20
|
+
# Binance sometimes returns this to indicate side, though amount sign is also used.
|
|
21
|
+
# It might be a list or a boolean depending on specific endpoint versions,
|
|
22
|
+
# but for "getOtherPosition" it is often implied.
|
|
23
|
+
# We will define it just in case the API maps it.
|
|
24
|
+
long_short: bool | list = None
|
|
25
|
+
|
|
26
|
+
def get_side(self) -> str:
|
|
27
|
+
"""
|
|
28
|
+
Determines the side (LONG/SHORT) of the position.
|
|
29
|
+
"""
|
|
30
|
+
# Strategy 1: Check specific long_short field if populated
|
|
31
|
+
if isinstance(self.long_short, bool):
|
|
32
|
+
return "LONG" if self.long_short else "SHORT"
|
|
33
|
+
|
|
34
|
+
if isinstance(self.long_short, list) and len(self.long_short) > 0:
|
|
35
|
+
# sometimes ["LONG"] or ["SHORT"]
|
|
36
|
+
val = str(self.long_short[0]).upper()
|
|
37
|
+
if "LONG" in val:
|
|
38
|
+
return "LONG"
|
|
39
|
+
if "SHORT" in val:
|
|
40
|
+
return "SHORT"
|
|
41
|
+
|
|
42
|
+
# Strategy 2: Check amount sign (standard for most derivatives APIs)
|
|
43
|
+
if self.amount is not None:
|
|
44
|
+
if self.amount > 0:
|
|
45
|
+
return "LONG"
|
|
46
|
+
elif self.amount < 0:
|
|
47
|
+
return "SHORT"
|
|
48
|
+
|
|
49
|
+
return "UNKNOWN_SIDE"
|
|
50
|
+
|
|
51
|
+
def get_position_id(self, uid: str) -> str:
|
|
52
|
+
"""
|
|
53
|
+
Generates a synthetic position ID.
|
|
54
|
+
Binance Leaderboard does not provide a persistent ID for the position.
|
|
55
|
+
"""
|
|
56
|
+
side_str = self.get_side()
|
|
57
|
+
side_code = 1 if side_str == "LONG" else 0
|
|
58
|
+
|
|
59
|
+
# ID = {UID}-{SYMBOL}-{SIDE_CODE}
|
|
60
|
+
raw_str = f"{uid}-{self.symbol}-{side_code}"
|
|
61
|
+
return raw_str.encode("utf-8").hex()
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class BinanceLeaderboardResponseData(BaseModel):
|
|
65
|
+
other_position_ret_list: list[BinanceLeaderboardPosition] = None
|
|
66
|
+
update_time_stamp: int = None
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class BinanceLeaderboardResponse(BaseModel):
|
|
70
|
+
code: str = None
|
|
71
|
+
message: str = None
|
|
72
|
+
message_detail: str = None
|
|
73
|
+
data: BinanceLeaderboardResponseData = None
|
|
74
|
+
success: bool = False
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class BinanceLeaderboardBaseInfo(BaseModel):
|
|
78
|
+
nick_name: str = None
|
|
79
|
+
user_photo_url: str = None
|
|
80
|
+
introduction: str = None
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class BinanceLeaderboardBaseInfoResponse(BaseModel):
|
|
84
|
+
data: BinanceLeaderboardBaseInfo = None
|
|
85
|
+
success: bool = False
|
|
86
|
+
code: str = None
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# endregion
|
|
90
|
+
|
|
91
|
+
###########################################################
|
|
92
|
+
# region Market Data Types (F-API)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class BinanceTicker24h(BaseModel):
|
|
96
|
+
symbol: str = None
|
|
97
|
+
last_price: Decimal = None
|
|
98
|
+
prev_close_price: Decimal | None = None
|
|
99
|
+
price_change: Decimal = None
|
|
100
|
+
price_change_percent: Decimal = None
|
|
101
|
+
weighted_avg_price: Decimal = None
|
|
102
|
+
# quote_volume is the volume in USDT (Turnover)
|
|
103
|
+
quote_volume: Decimal = None
|
|
104
|
+
# volume is the volume in Base Asset (e.g. BTC)
|
|
105
|
+
volume: Decimal = None
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class BinancePremiumIndex(BaseModel):
|
|
109
|
+
symbol: str = None
|
|
110
|
+
last_funding_rate: Decimal = None
|
|
111
|
+
mark_price: Decimal = None
|
|
112
|
+
index_price: Decimal = None
|
|
113
|
+
time: int = None
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
# endregion
|