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,1338 @@
|
|
|
1
|
+
from typing import Any, Optional
|
|
2
|
+
from decimal import Decimal
|
|
3
|
+
from datetime import datetime, timedelta
|
|
4
|
+
import pytz
|
|
5
|
+
|
|
6
|
+
from trd_utils.exchanges.errors import ExchangeError
|
|
7
|
+
from trd_utils.exchanges.price_fetcher import MinimalCandleInfo
|
|
8
|
+
from trd_utils.types_helper import BaseModel
|
|
9
|
+
|
|
10
|
+
from trd_utils.common_utils.float_utils import (
|
|
11
|
+
as_decimal,
|
|
12
|
+
dec_to_str,
|
|
13
|
+
dec_to_normalize,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
###########################################################
|
|
17
|
+
|
|
18
|
+
# region constant variables
|
|
19
|
+
|
|
20
|
+
ORDER_TYPES_MAP = {
|
|
21
|
+
0: "LONG",
|
|
22
|
+
1: "SHORT",
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
# endregion
|
|
26
|
+
|
|
27
|
+
###########################################################
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# region Common types
|
|
31
|
+
class BxApiResponse(BaseModel):
|
|
32
|
+
code: int = None
|
|
33
|
+
timestamp: int = None
|
|
34
|
+
msg: str = None
|
|
35
|
+
|
|
36
|
+
def __str__(self):
|
|
37
|
+
return f"code: {self.code}; timestamp: {self.timestamp}"
|
|
38
|
+
|
|
39
|
+
def __repr__(self):
|
|
40
|
+
return f"code: {self.code}; timestamp: {self.timestamp}"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class CoinQuotationInfo(BaseModel):
|
|
44
|
+
name: str = None
|
|
45
|
+
coin_type: int = None
|
|
46
|
+
valuation_coin_name: str = None
|
|
47
|
+
coin_name: str = None
|
|
48
|
+
icon_name: str = None
|
|
49
|
+
slug: str = None
|
|
50
|
+
quotation_id: int = None
|
|
51
|
+
open: Decimal = None
|
|
52
|
+
close: Decimal = None
|
|
53
|
+
weight: int = None
|
|
54
|
+
favorite_flag: Any = None # unknown
|
|
55
|
+
precision: int = None
|
|
56
|
+
coin_precision: int = None
|
|
57
|
+
valuation_precision: int = None
|
|
58
|
+
market_status: Any = None # unknown
|
|
59
|
+
trader_scale: Decimal = None
|
|
60
|
+
coin_id: int = None
|
|
61
|
+
valuation_coin_id: int = None
|
|
62
|
+
status: Any = None # unknown
|
|
63
|
+
open_time: Any = None # unknown
|
|
64
|
+
open_price: Any = None # unknown
|
|
65
|
+
high24: Optional[Decimal] = None
|
|
66
|
+
low24: Optional[Decimal] = None
|
|
67
|
+
volume24: Optional[Decimal] = None
|
|
68
|
+
amount24: Optional[Decimal] = None
|
|
69
|
+
market_val: Optional[Decimal] = None
|
|
70
|
+
full_name: str = None
|
|
71
|
+
biz_type: int = None
|
|
72
|
+
|
|
73
|
+
def __str__(self):
|
|
74
|
+
return f"{self.coin_name}/{self.valuation_coin_name}; price: {self.close}; vol: {self.market_val}"
|
|
75
|
+
|
|
76
|
+
def __repr__(self):
|
|
77
|
+
return f"{self.coin_name}/{self.valuation_coin_name}; price: {self.close}; vol: {self.market_val}"
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class ExchangeVo(BaseModel):
|
|
81
|
+
exchange_id: int = None
|
|
82
|
+
exchange_name: str = None
|
|
83
|
+
icon: str = None
|
|
84
|
+
account_enum: str = None
|
|
85
|
+
desc: str = None
|
|
86
|
+
|
|
87
|
+
def __str__(self):
|
|
88
|
+
return f"{self.exchange_name} ({self.exchange_id}) - {self.account_enum}"
|
|
89
|
+
|
|
90
|
+
def __repr__(self):
|
|
91
|
+
return f"{self.exchange_name} ({self.exchange_id}) - {self.account_enum}"
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class OrderLeverInfo(BaseModel):
|
|
95
|
+
lever_times: int = None
|
|
96
|
+
selected: bool = False
|
|
97
|
+
|
|
98
|
+
def __str__(self):
|
|
99
|
+
return f"{self.lever_times}x"
|
|
100
|
+
|
|
101
|
+
def __repr__(self):
|
|
102
|
+
return self.__str__()
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class MarginDisplayInfo(BaseModel):
|
|
106
|
+
margin: Decimal = None
|
|
107
|
+
selected: bool = False
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class SysForceVoInfo(BaseModel):
|
|
111
|
+
begin_level: int = None
|
|
112
|
+
end_level: int = None
|
|
113
|
+
adjust_margin_rate: Decimal = None
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
# endregion
|
|
117
|
+
|
|
118
|
+
###########################################################
|
|
119
|
+
|
|
120
|
+
# region ZoneModule types
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class ZoneModuleInfo(BaseModel):
|
|
124
|
+
id: int = None
|
|
125
|
+
name: str = None
|
|
126
|
+
quotation_list: list[CoinQuotationInfo] = None
|
|
127
|
+
zone_name: str = None
|
|
128
|
+
weight: int = None
|
|
129
|
+
biz_type: int = None
|
|
130
|
+
|
|
131
|
+
def __str__(self):
|
|
132
|
+
return f"{self.name} ({self.zone_name})"
|
|
133
|
+
|
|
134
|
+
def __repr__(self):
|
|
135
|
+
return f"{self.name} ({self.zone_name})"
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class ZoneModuleListResult(BaseModel):
|
|
139
|
+
zone_module_list: list[ZoneModuleInfo] = None
|
|
140
|
+
biz_type: int = None
|
|
141
|
+
need_channel_type: list[int] = None
|
|
142
|
+
icon_url_prefix: str = None
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class ZoneModuleListResponse(BxApiResponse):
|
|
146
|
+
data: ZoneModuleListResult = None
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
# endregion
|
|
150
|
+
|
|
151
|
+
###########################################################
|
|
152
|
+
|
|
153
|
+
# region UserFavorite types
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
class UserFavoriteQuotationResult(BaseModel):
|
|
157
|
+
usdt_margin_list: list[CoinQuotationInfo] = None
|
|
158
|
+
coin_margin_list: list[CoinQuotationInfo] = None
|
|
159
|
+
swap_list: list[CoinQuotationInfo] = None
|
|
160
|
+
biz_type: int = None
|
|
161
|
+
icon_url_prefix: str = None
|
|
162
|
+
recommend: bool = None
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class UserFavoriteQuotationResponse(BxApiResponse):
|
|
166
|
+
data: UserFavoriteQuotationResult = None
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
# endregion
|
|
170
|
+
|
|
171
|
+
###########################################################
|
|
172
|
+
|
|
173
|
+
# region QuotationRank types
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
class QuotationRankBizItem(BaseModel):
|
|
177
|
+
quotation_list: list[CoinQuotationInfo] = None
|
|
178
|
+
biz_type: int = None
|
|
179
|
+
biz_name: str = None
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
class QuotationRankItem(BaseModel):
|
|
183
|
+
rank_type: int = None
|
|
184
|
+
rank_name: str = None
|
|
185
|
+
rank_biz_list: list[QuotationRankBizItem] = None
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
class QuotationRankResult(BaseModel):
|
|
189
|
+
rank_list: list[QuotationRankItem] = None
|
|
190
|
+
icon_prefix: str = None
|
|
191
|
+
icon_url_prefix: str = None
|
|
192
|
+
order_flag: int = None
|
|
193
|
+
show_favorite: bool = None
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
class QuotationRankResponse(BxApiResponse):
|
|
197
|
+
data: QuotationRankResult = None
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
# endregion
|
|
201
|
+
|
|
202
|
+
###########################################################
|
|
203
|
+
|
|
204
|
+
# region HotSearch types
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
class HotSearchItem(BaseModel):
|
|
208
|
+
symbol: str = None
|
|
209
|
+
coin_name: str = None
|
|
210
|
+
val_coin_name: str = None
|
|
211
|
+
weight: int = None
|
|
212
|
+
|
|
213
|
+
def __str__(self):
|
|
214
|
+
return f"{self.coin_name} ({self.symbol})"
|
|
215
|
+
|
|
216
|
+
def __repr__(self):
|
|
217
|
+
return f"{self.coin_name} ({self.symbol})"
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
class HotSearchResult(BaseModel):
|
|
221
|
+
result: list[HotSearchItem] = None
|
|
222
|
+
hint_ab_test: bool = None
|
|
223
|
+
page_id: int = None
|
|
224
|
+
total: int = None
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
class HotSearchResponse(BxApiResponse):
|
|
228
|
+
data: HotSearchResult = None
|
|
229
|
+
|
|
230
|
+
def __str__(self):
|
|
231
|
+
if not self.data:
|
|
232
|
+
return "HotSearchResponse: No data"
|
|
233
|
+
|
|
234
|
+
str_result = "HotSearchResponse: \n"
|
|
235
|
+
for current_item in self.data.result:
|
|
236
|
+
str_result += f" - {current_item}\n"
|
|
237
|
+
|
|
238
|
+
return str_result
|
|
239
|
+
|
|
240
|
+
def __repr__(self):
|
|
241
|
+
return self.__str__()
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
# endregion
|
|
245
|
+
|
|
246
|
+
###########################################################
|
|
247
|
+
|
|
248
|
+
# region HomePage types
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
class CoinModuleInfoBase(BaseModel):
|
|
252
|
+
name: str = None
|
|
253
|
+
coin_name: str = None
|
|
254
|
+
icon_name: str = None
|
|
255
|
+
valuation_coin_name: str = None
|
|
256
|
+
open: Decimal = None
|
|
257
|
+
close: Decimal = None
|
|
258
|
+
precision: int = None
|
|
259
|
+
biz_type: int = None
|
|
260
|
+
market_val: Decimal = None
|
|
261
|
+
status: int = None
|
|
262
|
+
open_time: int = None
|
|
263
|
+
open_price: Decimal = None
|
|
264
|
+
full_name: str = None
|
|
265
|
+
amount24: Decimal = None
|
|
266
|
+
global_first_publish: bool = None
|
|
267
|
+
st_tag: int = None
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
class HomePageModuleIncreaseRankData(CoinModuleInfoBase):
|
|
271
|
+
pass
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
class HomePageModuleIncreaseRank(BaseModel):
|
|
275
|
+
item_name: str = None
|
|
276
|
+
sub_module_type: int = None
|
|
277
|
+
data: list[HomePageModuleIncreaseRankData] = None
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
class HomePageModuleHotZoneData(CoinModuleInfoBase):
|
|
281
|
+
zone_desc: str = None
|
|
282
|
+
zone_name: str = None
|
|
283
|
+
zone_id: int = None
|
|
284
|
+
zone_price_rate: Decimal = None
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
class HomePageModuleHotZone(BaseModel):
|
|
288
|
+
data: list[HomePageModuleHotZoneData] = None
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
class HomePageModuleRecentHotData(CoinModuleInfoBase):
|
|
292
|
+
pass
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
class HomePageModuleRecentHot(BaseModel):
|
|
296
|
+
data: list[HomePageModuleRecentHotData] = None
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
class HomePageModuleGlobalDebutData(CoinModuleInfoBase):
|
|
300
|
+
pass
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
class HomePageModuleGlobalDebut(BaseModel):
|
|
304
|
+
data: list[HomePageModuleGlobalDebutData] = None
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
class HomePageModuleMainMarketData(CoinModuleInfoBase):
|
|
308
|
+
pass
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
class HomePageModuleMainMarket(BaseModel):
|
|
312
|
+
data: list[HomePageModuleMainMarketData] = None
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
class HomePageModulePreOnlineData(CoinModuleInfoBase):
|
|
316
|
+
pass
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
class HomePageModulePreOnline(BaseModel):
|
|
320
|
+
data: list[HomePageModulePreOnlineData] = None
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
class HomePageModuleBannerData(BaseModel):
|
|
324
|
+
banner_title: str = None
|
|
325
|
+
banner_img: str = None
|
|
326
|
+
banner_jump_url: str = None
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
class HomePageModuleBanner(BaseModel):
|
|
330
|
+
data: list[HomePageModuleBannerData] = None
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
class HomePageModuleInfo(BaseModel):
|
|
334
|
+
module_type: int = None
|
|
335
|
+
module_name: str = None
|
|
336
|
+
module_desc: str = None
|
|
337
|
+
item_list: list = None
|
|
338
|
+
|
|
339
|
+
def _check_module_name(
|
|
340
|
+
self, j_data: dict, module_name: str, module_type: int
|
|
341
|
+
) -> bool:
|
|
342
|
+
return (
|
|
343
|
+
self.module_name == module_name
|
|
344
|
+
or j_data.get("moduleName", None) == module_name
|
|
345
|
+
or self.module_type == module_type
|
|
346
|
+
or j_data.get("moduleType", None) == module_type
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
def _get_item_list_type(self, j_data: dict = None) -> type:
|
|
350
|
+
if not j_data:
|
|
351
|
+
# for more safety
|
|
352
|
+
j_data = {}
|
|
353
|
+
|
|
354
|
+
if self._check_module_name(j_data, "PRE_ONLINE", 1):
|
|
355
|
+
return list[HomePageModulePreOnline]
|
|
356
|
+
|
|
357
|
+
elif self._check_module_name(j_data, "MAIN_MARKET", 2):
|
|
358
|
+
return list[HomePageModuleMainMarket]
|
|
359
|
+
|
|
360
|
+
elif self._check_module_name(j_data, "RECENT_HOT", 3):
|
|
361
|
+
return list[HomePageModuleRecentHot]
|
|
362
|
+
|
|
363
|
+
elif self._check_module_name(j_data, "HOT_ZONE", 5):
|
|
364
|
+
return list[HomePageModuleHotZone]
|
|
365
|
+
|
|
366
|
+
elif self._check_module_name(j_data, "INCREASE_RANK", 6):
|
|
367
|
+
return list[HomePageModuleIncreaseRank]
|
|
368
|
+
|
|
369
|
+
elif self._check_module_name(j_data, "BANNER", 7):
|
|
370
|
+
return list[HomePageModuleBanner]
|
|
371
|
+
|
|
372
|
+
elif self._check_module_name(j_data, "GLOBAL_DEBUT", 8):
|
|
373
|
+
return list[HomePageModuleGlobalDebut]
|
|
374
|
+
|
|
375
|
+
return None
|
|
376
|
+
|
|
377
|
+
def __str__(self):
|
|
378
|
+
return (
|
|
379
|
+
f"{self.module_name} ({self.module_type}): {self.module_desc};"
|
|
380
|
+
+ f" {len(self.item_list)} items"
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
def __repr__(self):
|
|
384
|
+
return (
|
|
385
|
+
f"{self.module_name} ({self.module_type}): {self.module_desc};"
|
|
386
|
+
+ f" {len(self.item_list)} items"
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
class HomePageResult(BaseModel):
|
|
391
|
+
icon_prefix: str = None
|
|
392
|
+
green_amount_img_prefix: str = None
|
|
393
|
+
red_amount_img_prefix: str = None
|
|
394
|
+
module_list: list[HomePageModuleInfo] = None
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
class HomePageResponse(BxApiResponse):
|
|
398
|
+
data: HomePageResult = None
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
# endregion
|
|
402
|
+
|
|
403
|
+
###########################################################
|
|
404
|
+
|
|
405
|
+
# region ZenDesk types
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
class ZenDeskABStatusResult(BaseModel):
|
|
409
|
+
ab_status: int = None
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
class ZenDeskABStatusResponse(BxApiResponse):
|
|
413
|
+
data: ZenDeskABStatusResult = None
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
class ZenDeskAuthResult(BaseModel):
|
|
417
|
+
jwt: str = None
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
class ZenDeskAuthResponse(BxApiResponse):
|
|
421
|
+
data: ZenDeskAuthResult = None
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
# endregion
|
|
425
|
+
|
|
426
|
+
###########################################################
|
|
427
|
+
|
|
428
|
+
# region HintList types
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
class HintListResult(BaseModel):
|
|
432
|
+
hints: list = None # unknown
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
class HintListResponse(BxApiResponse):
|
|
436
|
+
data: HintListResult = None
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
# endregion
|
|
440
|
+
|
|
441
|
+
###########################################################
|
|
442
|
+
|
|
443
|
+
# region CopyTrading types
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
class CopyTradingSymbolConfigInfo(BaseModel):
|
|
447
|
+
price_precision: int = None
|
|
448
|
+
quantity_precision: int = None
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
class CopyTraderPositionInfo(BaseModel):
|
|
452
|
+
avg_price: Decimal = None
|
|
453
|
+
coin_name: str = None
|
|
454
|
+
leverage: Decimal = None
|
|
455
|
+
liquidated_price: Decimal = None
|
|
456
|
+
margin: Decimal = None
|
|
457
|
+
mark_price: Decimal = None
|
|
458
|
+
position_earning_rate: Decimal = None
|
|
459
|
+
position_no: int = None
|
|
460
|
+
position_side: str = None
|
|
461
|
+
position_side_and_symbol: str = None
|
|
462
|
+
symbol: str = None
|
|
463
|
+
symbol_config: CopyTradingSymbolConfigInfo = None
|
|
464
|
+
unrealized_pnl: Decimal = None
|
|
465
|
+
valuation_coin_name: str = None
|
|
466
|
+
volume: Decimal = None
|
|
467
|
+
search_result: Optional[bool] = None
|
|
468
|
+
short_position_rate: Decimal = None
|
|
469
|
+
total: int = None
|
|
470
|
+
|
|
471
|
+
def __str__(self):
|
|
472
|
+
return (
|
|
473
|
+
f"{self.coin_name} / {self.valuation_coin_name} {self.position_side} "
|
|
474
|
+
+ f"{dec_to_str(self.leverage)}x "
|
|
475
|
+
+ f"vol: {dec_to_str(self.volume)}; "
|
|
476
|
+
+ f"price: {dec_to_normalize(self.avg_price)}; "
|
|
477
|
+
+ f"margin: {dec_to_str(self.margin)}; "
|
|
478
|
+
+ f"unrealized-PnL: {dec_to_str(self.unrealized_pnl)}; "
|
|
479
|
+
+ f"ROI: {dec_to_str((self.position_earning_rate * 100))}%"
|
|
480
|
+
)
|
|
481
|
+
|
|
482
|
+
def __repr__(self):
|
|
483
|
+
return self.__str__()
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
class CopyTraderTradePositionsResult(BaseModel):
|
|
487
|
+
hide: int = None
|
|
488
|
+
long_position_rate: Decimal = None
|
|
489
|
+
page_id: int = None
|
|
490
|
+
positions: list[CopyTraderPositionInfo] = None
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
class CopyTraderTradePositionsResponse(BxApiResponse):
|
|
494
|
+
data: CopyTraderTradePositionsResult = None
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
class CopyTraderFuturesStatsResult(BaseModel):
|
|
498
|
+
api_identity: int = None
|
|
499
|
+
dis_play_name: str = None
|
|
500
|
+
icon: str = None
|
|
501
|
+
valid: int = None
|
|
502
|
+
risk_status: int = None
|
|
503
|
+
is_relation: int = None
|
|
504
|
+
copier_status: int = None
|
|
505
|
+
vst_copier_status: int = None
|
|
506
|
+
follower_full: bool = None
|
|
507
|
+
being_invite: bool = None
|
|
508
|
+
str_follower_num: int = None
|
|
509
|
+
equity: str = None
|
|
510
|
+
total_earnings: str = None
|
|
511
|
+
follower_earning: str = None
|
|
512
|
+
max_draw_down: str = None
|
|
513
|
+
str_total_earnings_rate: str = None
|
|
514
|
+
str_recent30_days_rate: str = None
|
|
515
|
+
str_recent7_days_rate: str = None
|
|
516
|
+
str_recent90_days_rate: str = None
|
|
517
|
+
str_recent180_days_rate: str = None
|
|
518
|
+
exchange_vo: ExchangeVo = None
|
|
519
|
+
update_time: datetime = None
|
|
520
|
+
commission_rate: float = None
|
|
521
|
+
risk_level7_days: int = None
|
|
522
|
+
risk_level30_days: int = None
|
|
523
|
+
risk_level90_days: int = None
|
|
524
|
+
risk_level180_days: int = None
|
|
525
|
+
str_acc_follower_num: int = None
|
|
526
|
+
win_rate: str = None
|
|
527
|
+
total_transactions: int = None
|
|
528
|
+
profit_count: int = None
|
|
529
|
+
avg_profit_amount: str = None
|
|
530
|
+
avg_profit_rate: str = None
|
|
531
|
+
loss_count: int = None
|
|
532
|
+
avg_loss_amount: str = None
|
|
533
|
+
avg_loss_rate: str = None
|
|
534
|
+
pnl_rate: str = None
|
|
535
|
+
avg_hold_time: int = None
|
|
536
|
+
weekly_trade_frequency: str = None
|
|
537
|
+
trade_days: int = None
|
|
538
|
+
last_trade_time: datetime = None
|
|
539
|
+
expand: int = None
|
|
540
|
+
recent7_day_follower_num_change: int = None
|
|
541
|
+
recent30_day_follower_num_change: int = None
|
|
542
|
+
recent90_day_follower_num_change: int = None
|
|
543
|
+
recent180_day_follower_num_change: int = None
|
|
544
|
+
latest30_days_median_margin: str = None
|
|
545
|
+
latest30_days_median_lever_times: str = None
|
|
546
|
+
cumulative_profit_loss7_d: float = None
|
|
547
|
+
cumulative_profit_loss30_d: float = None
|
|
548
|
+
cumulative_profit_loss90_d: float = None
|
|
549
|
+
cumulative_profit_loss180_d: float = None
|
|
550
|
+
maximum_draw_down: int = None
|
|
551
|
+
max_draw_down7_d: float = None
|
|
552
|
+
max_draw_down30_d: float = None
|
|
553
|
+
max_draw_down90_d: float = None
|
|
554
|
+
max_draw_down180_d: float = None
|
|
555
|
+
total_position_count: int = None
|
|
556
|
+
profitable_position_count: int = None
|
|
557
|
+
loss_position_count: int = None
|
|
558
|
+
profit_realized_pnl_u: float = None
|
|
559
|
+
loss_realized_pnl_u: float = None
|
|
560
|
+
pnl_rate_u: str = None
|
|
561
|
+
avg_profit: float = None
|
|
562
|
+
avg_loss: float = None
|
|
563
|
+
is_pro: int = None
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
class CopyTraderFuturesStatsResponse(BxApiResponse):
|
|
567
|
+
data: CopyTraderFuturesStatsResult = None
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
class CopyTraderInfo(BaseModel):
|
|
571
|
+
nick_name: str = None
|
|
572
|
+
avatar: str = None
|
|
573
|
+
brief: str = None
|
|
574
|
+
uid: int = None
|
|
575
|
+
register_date: datetime = None
|
|
576
|
+
calling_code: str = None
|
|
577
|
+
team_id: int = None
|
|
578
|
+
short_uid: int = None
|
|
579
|
+
identity_type: int = None
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
class CopyTraderVo(BaseModel):
|
|
583
|
+
audit_status: int = None
|
|
584
|
+
trader_status: int = None
|
|
585
|
+
profit_share_rate: float = None
|
|
586
|
+
trader_role: int = None
|
|
587
|
+
recent_avg_margin: int = None
|
|
588
|
+
min_basic_copy_trade_unit: int = None
|
|
589
|
+
max_basic_copy_trade_unit: int = None
|
|
590
|
+
basic_copy_trade_unit: int = None
|
|
591
|
+
copy_trade_rate_on: bool = None
|
|
592
|
+
trader_protect_status: int = None
|
|
593
|
+
trader_public_recommend_status: int = None
|
|
594
|
+
rank_account_id: int = None
|
|
595
|
+
last_trader_time: datetime = None
|
|
596
|
+
|
|
597
|
+
|
|
598
|
+
class CopyTraderAccountGradeVO(BaseModel):
|
|
599
|
+
uid: int = None
|
|
600
|
+
api_identity: int = None
|
|
601
|
+
trader_grade: int = None
|
|
602
|
+
label: int = None
|
|
603
|
+
uid_and_api: str = None
|
|
604
|
+
|
|
605
|
+
|
|
606
|
+
class CopyTraderSharingAccount(BaseModel):
|
|
607
|
+
category: int = None
|
|
608
|
+
trader: int = None
|
|
609
|
+
api_identity: int = None
|
|
610
|
+
display_name: str = None
|
|
611
|
+
icon: str = None
|
|
612
|
+
valid: int = None
|
|
613
|
+
copier_status: int = None
|
|
614
|
+
vst_copier_status: int = None
|
|
615
|
+
follower_full: bool = None
|
|
616
|
+
copy_trade_account_enum: str = None
|
|
617
|
+
order: int = None
|
|
618
|
+
trader_account_grade_vo: Optional[CopyTraderAccountGradeVO] = None
|
|
619
|
+
hide_info: int = None
|
|
620
|
+
copy_trade_label_type: Optional[int] = None
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
class CopyTraderResumeResult(BaseModel):
|
|
624
|
+
trader_info: CopyTraderInfo = None
|
|
625
|
+
trader_vo: CopyTraderVo = None
|
|
626
|
+
tags: list = None
|
|
627
|
+
has_new: int = None
|
|
628
|
+
labels: list = None
|
|
629
|
+
has_subscribed: int = None
|
|
630
|
+
fans_num: int = None
|
|
631
|
+
follower_num: int = None
|
|
632
|
+
subscriber_num: int = None
|
|
633
|
+
category: int = None
|
|
634
|
+
api_identity: int = None
|
|
635
|
+
trader_sharing_accounts: list[CopyTraderSharingAccount] = None
|
|
636
|
+
latest30_days_earning_ratio: str = None
|
|
637
|
+
swap_copy_trade_label_type: int = None
|
|
638
|
+
is_pro: int = None
|
|
639
|
+
|
|
640
|
+
def get_account_identity_by_filter(self, filter_text: str):
|
|
641
|
+
if not self.trader_sharing_accounts:
|
|
642
|
+
return 0
|
|
643
|
+
|
|
644
|
+
for current in self.trader_sharing_accounts:
|
|
645
|
+
if current.display_name.lower().find(filter_text) != -1 or \
|
|
646
|
+
current.copy_trade_account_enum.lower().find(filter_text) != -1:
|
|
647
|
+
if current.api_identity:
|
|
648
|
+
return current.api_identity
|
|
649
|
+
return 0
|
|
650
|
+
|
|
651
|
+
|
|
652
|
+
class CopyTraderResumeResponse(BxApiResponse):
|
|
653
|
+
data: CopyTraderResumeResult = None
|
|
654
|
+
|
|
655
|
+
|
|
656
|
+
# endregion
|
|
657
|
+
|
|
658
|
+
###########################################################
|
|
659
|
+
|
|
660
|
+
# region SearchCopyTrading types
|
|
661
|
+
|
|
662
|
+
|
|
663
|
+
class SearchCopyTraderCondition(BaseModel):
|
|
664
|
+
key: str = "exchangeId"
|
|
665
|
+
selected: int = 2
|
|
666
|
+
type: str = "singleSelect"
|
|
667
|
+
|
|
668
|
+
def to_dict(self):
|
|
669
|
+
return {
|
|
670
|
+
"key": self.key,
|
|
671
|
+
"selected": f"{self.selected}",
|
|
672
|
+
"type": self.type,
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
|
|
676
|
+
class SearchedTraderChartItem(BaseModel):
|
|
677
|
+
cumulative_pnl_rate: Decimal = None
|
|
678
|
+
|
|
679
|
+
|
|
680
|
+
class SearchedTraderExchangeVoInfo(BaseModel):
|
|
681
|
+
account_enum: str = None
|
|
682
|
+
desc: str = None
|
|
683
|
+
exchange_id: int = None
|
|
684
|
+
exchange_name: str = None
|
|
685
|
+
icon: str = None
|
|
686
|
+
|
|
687
|
+
|
|
688
|
+
class SearchTraderInfoRankStat(BaseModel):
|
|
689
|
+
api_identity: int = None
|
|
690
|
+
avg_hold_time: str = None
|
|
691
|
+
avg_loss_amount: str = None
|
|
692
|
+
avg_loss_rate: str = None
|
|
693
|
+
avg_profit_amount: str = None
|
|
694
|
+
avg_profit_rate: str = None
|
|
695
|
+
being_invite: bool = None
|
|
696
|
+
chart: list[SearchedTraderChartItem] = None # unknown; list
|
|
697
|
+
copier_status: int = None
|
|
698
|
+
dis_play_name: str = None
|
|
699
|
+
equity: str = None
|
|
700
|
+
exchange_vo: ExchangeVo = None # unknown
|
|
701
|
+
expand: int = None
|
|
702
|
+
follower_earning: str = None
|
|
703
|
+
follower_full: bool = None
|
|
704
|
+
icon: str = None
|
|
705
|
+
is_pro: bool = None
|
|
706
|
+
is_relation: bool = None
|
|
707
|
+
last_trade_time: str = None
|
|
708
|
+
latest30_days_median_lever_times: str = None
|
|
709
|
+
latest30_days_median_margin: str = None
|
|
710
|
+
loss_count: int = None
|
|
711
|
+
max_draw_down: str = None
|
|
712
|
+
pnl_rate: str = None
|
|
713
|
+
profit_count: int = None
|
|
714
|
+
recent7_day_follower_num_change: int = None
|
|
715
|
+
recent30_day_follower_num_change: int = None
|
|
716
|
+
recent90_day_follower_num_change: int = None
|
|
717
|
+
recent180_day_follower_num_change: int = None
|
|
718
|
+
risk_level7_days: str = None
|
|
719
|
+
risk_level30_days: str = None
|
|
720
|
+
risk_level90_days: str = None
|
|
721
|
+
risk_level180_days: str = None
|
|
722
|
+
risk_status: int = None
|
|
723
|
+
str_acc_follower_num: str = None
|
|
724
|
+
str_follower_num: str = None
|
|
725
|
+
str_recent7_days_rate: str = None
|
|
726
|
+
str_recent30_days_rate: str = None
|
|
727
|
+
str_recent90_days_rate: str = None
|
|
728
|
+
str_recent180_days_rate: str = None
|
|
729
|
+
str_recent180_days_rate: str = None
|
|
730
|
+
str_total_earnings_rate: str = None
|
|
731
|
+
total_earnings: str = None
|
|
732
|
+
total_transactions: int = None
|
|
733
|
+
trade_days: str = None
|
|
734
|
+
update_time: str = None
|
|
735
|
+
valid: int = None
|
|
736
|
+
vst_copier_status: int = None
|
|
737
|
+
weekly_trade_frequency: str = None
|
|
738
|
+
winRate: str = None
|
|
739
|
+
|
|
740
|
+
|
|
741
|
+
class SearchedTraderInfo(BaseModel):
|
|
742
|
+
avatar: str = None
|
|
743
|
+
be_trader: bool = None
|
|
744
|
+
channel: str = None
|
|
745
|
+
flag: str = None
|
|
746
|
+
ip_country: str = None
|
|
747
|
+
nation: str = None
|
|
748
|
+
nick_name: str = None
|
|
749
|
+
register_date: str = None
|
|
750
|
+
register_ip_country: str = None
|
|
751
|
+
short_uid: int = None
|
|
752
|
+
team_id: int = None
|
|
753
|
+
uid: int = None
|
|
754
|
+
|
|
755
|
+
|
|
756
|
+
class SearchedTraderAccountGradeVoInfo(BaseModel):
|
|
757
|
+
api_identity: int = None
|
|
758
|
+
label: int = None
|
|
759
|
+
trader_grade: int = None
|
|
760
|
+
uid: int = None
|
|
761
|
+
|
|
762
|
+
|
|
763
|
+
class SearchTraderInfoContainer(BaseModel):
|
|
764
|
+
content: str = None
|
|
765
|
+
has_new: bool = None
|
|
766
|
+
labels: list = None # unknown
|
|
767
|
+
rank_stat: SearchTraderInfoRankStat = None # unknown
|
|
768
|
+
trader: SearchedTraderInfo = None # unknown
|
|
769
|
+
trader_account_grade_vo: SearchedTraderAccountGradeVoInfo = None # unknown
|
|
770
|
+
trader_public_recommend_status: Any = None # unknown
|
|
771
|
+
|
|
772
|
+
def get_nick_name(self) -> str:
|
|
773
|
+
if self.trader:
|
|
774
|
+
return self.trader.nick_name
|
|
775
|
+
|
|
776
|
+
return
|
|
777
|
+
|
|
778
|
+
def get_uid(self) -> int:
|
|
779
|
+
if self.trader:
|
|
780
|
+
return self.trader.uid
|
|
781
|
+
|
|
782
|
+
return
|
|
783
|
+
|
|
784
|
+
def get_api_identity(self) -> int:
|
|
785
|
+
if self.trader_account_grade_vo:
|
|
786
|
+
return self.trader_account_grade_vo.api_identity
|
|
787
|
+
|
|
788
|
+
if self.rank_stat:
|
|
789
|
+
return self.rank_stat.api_identity
|
|
790
|
+
|
|
791
|
+
# TODO: later on add support for more cases
|
|
792
|
+
return None
|
|
793
|
+
|
|
794
|
+
def __str__(self):
|
|
795
|
+
if not self.trader:
|
|
796
|
+
return "No trader info"
|
|
797
|
+
|
|
798
|
+
return f"uid: {self.trader.uid}; name: {self.trader.nick_name}; country: {self.trader.nation}"
|
|
799
|
+
|
|
800
|
+
def __repr__(self):
|
|
801
|
+
return self.__str__()
|
|
802
|
+
|
|
803
|
+
|
|
804
|
+
class SearchCopyTradersResult(BaseModel):
|
|
805
|
+
expand_display: Any = None # unknown
|
|
806
|
+
fold_display: Any = None # unknown
|
|
807
|
+
page_id: int = None
|
|
808
|
+
rank_desc: str = None
|
|
809
|
+
rank_short_desc: str = None
|
|
810
|
+
rank_statistic_days: int = None
|
|
811
|
+
rank_tags: Any = None # unknown
|
|
812
|
+
rank_title: str = None
|
|
813
|
+
rank_type: str = None
|
|
814
|
+
result: list[SearchTraderInfoContainer] = None # unknown
|
|
815
|
+
search_result: bool = None
|
|
816
|
+
total: int = None
|
|
817
|
+
|
|
818
|
+
|
|
819
|
+
class SearchCopyTradersResponse(BxApiResponse):
|
|
820
|
+
data: SearchCopyTradersResult = None
|
|
821
|
+
|
|
822
|
+
|
|
823
|
+
# endregion
|
|
824
|
+
|
|
825
|
+
###########################################################
|
|
826
|
+
|
|
827
|
+
# region Account Assets types
|
|
828
|
+
|
|
829
|
+
|
|
830
|
+
class MinimalAssetInfo(BaseModel):
|
|
831
|
+
asset_id: int = None
|
|
832
|
+
asset_amount: Decimal = None
|
|
833
|
+
asset_name: str = None
|
|
834
|
+
has_value: bool = None
|
|
835
|
+
|
|
836
|
+
|
|
837
|
+
class TotalAssetsInfo(BaseModel):
|
|
838
|
+
amount: Any = None # unknown
|
|
839
|
+
currency_amount: Decimal = None
|
|
840
|
+
sign: str = None
|
|
841
|
+
|
|
842
|
+
def __str__(self):
|
|
843
|
+
return f"{dec_to_str(self.currency_amount)} {self.sign}"
|
|
844
|
+
|
|
845
|
+
def __repr__(self):
|
|
846
|
+
return self.__str__()
|
|
847
|
+
|
|
848
|
+
|
|
849
|
+
class AccountOverviewItem(BaseModel):
|
|
850
|
+
account_name: str = None
|
|
851
|
+
account_type: int = None
|
|
852
|
+
total: TotalAssetsInfo = None # unknown
|
|
853
|
+
schema: str = None
|
|
854
|
+
order: int = None
|
|
855
|
+
|
|
856
|
+
def __str__(self):
|
|
857
|
+
return f"{self.account_name} ({self.account_type}): {self.total}"
|
|
858
|
+
|
|
859
|
+
|
|
860
|
+
class AssetsInfoResult(BaseModel):
|
|
861
|
+
total: TotalAssetsInfo = None
|
|
862
|
+
account_overviews: list[AccountOverviewItem] = None
|
|
863
|
+
recharge: int = None
|
|
864
|
+
withdraw: int = None
|
|
865
|
+
transfer: int = None
|
|
866
|
+
exchange: int = None
|
|
867
|
+
fault_flag: int = None
|
|
868
|
+
fault_accounts: Any = None # unknown
|
|
869
|
+
|
|
870
|
+
|
|
871
|
+
class AssetsInfoResponse(BxApiResponse):
|
|
872
|
+
data: AssetsInfoResult = None
|
|
873
|
+
|
|
874
|
+
|
|
875
|
+
# endregion
|
|
876
|
+
|
|
877
|
+
###########################################################
|
|
878
|
+
|
|
879
|
+
# region Contracts types
|
|
880
|
+
|
|
881
|
+
|
|
882
|
+
class BasicCoinInfo(BaseModel):
|
|
883
|
+
name: str = None
|
|
884
|
+
|
|
885
|
+
def __str__(self):
|
|
886
|
+
return f"{self.name} CoinInfo"
|
|
887
|
+
|
|
888
|
+
def __repr__(self):
|
|
889
|
+
return self.__str__()
|
|
890
|
+
|
|
891
|
+
|
|
892
|
+
class QuotationCoinVOInfo(BaseModel):
|
|
893
|
+
id: int = None
|
|
894
|
+
coin: BasicCoinInfo = None
|
|
895
|
+
valuation_coin: BasicCoinInfo = None
|
|
896
|
+
precision: int = None
|
|
897
|
+
name: str = None
|
|
898
|
+
market_status: int = None
|
|
899
|
+
|
|
900
|
+
|
|
901
|
+
class OrderDebitInfo(BaseModel):
|
|
902
|
+
lend_coin: BasicCoinInfo = None
|
|
903
|
+
amount: Decimal = None
|
|
904
|
+
|
|
905
|
+
|
|
906
|
+
class OrderOpenTradeInfo(BaseModel):
|
|
907
|
+
traded_amount: Decimal = None
|
|
908
|
+
traded_cash_amount: Decimal = None
|
|
909
|
+
|
|
910
|
+
|
|
911
|
+
class ContractOrderStatus(BaseModel):
|
|
912
|
+
code: int = None
|
|
913
|
+
value: str = None
|
|
914
|
+
|
|
915
|
+
|
|
916
|
+
class ContractTakeProfitInfo(BaseModel):
|
|
917
|
+
id: int = None
|
|
918
|
+
|
|
919
|
+
# this is id of the contract that this take-profit info belongs to.
|
|
920
|
+
order_no: int = None
|
|
921
|
+
margin_coin_name: str = None
|
|
922
|
+
type: int = None
|
|
923
|
+
margin: Decimal = None
|
|
924
|
+
stop_rate: Decimal = None
|
|
925
|
+
stop_price: Decimal = None
|
|
926
|
+
close_style: int = None
|
|
927
|
+
all_close: bool = None
|
|
928
|
+
|
|
929
|
+
|
|
930
|
+
class ContractStopLossInfo(BaseModel):
|
|
931
|
+
id: int = None
|
|
932
|
+
|
|
933
|
+
# this is id of the contract that this take-profit info belongs to.
|
|
934
|
+
order_no: int = None
|
|
935
|
+
margin_coin_name: str = None
|
|
936
|
+
type: int = None
|
|
937
|
+
margin: Decimal = None
|
|
938
|
+
stop_rate: Decimal = None
|
|
939
|
+
stop_price: Decimal = None
|
|
940
|
+
close_style: int = None
|
|
941
|
+
all_close: bool = None
|
|
942
|
+
|
|
943
|
+
|
|
944
|
+
class ProfitLossInfoContainer(BaseModel):
|
|
945
|
+
loss_nums: int = None
|
|
946
|
+
profit_nums: int = None
|
|
947
|
+
profit_margin: Decimal = None
|
|
948
|
+
loss_margin: Decimal = None
|
|
949
|
+
profit_config: ContractTakeProfitInfo = None
|
|
950
|
+
loss_config: ContractStopLossInfo = None
|
|
951
|
+
|
|
952
|
+
|
|
953
|
+
class ContractOrderInfo(BaseModel):
|
|
954
|
+
order_no: int = None
|
|
955
|
+
quotation_coin_vo: QuotationCoinVOInfo = None
|
|
956
|
+
margin: Decimal = None
|
|
957
|
+
margin_coin_name: str = None
|
|
958
|
+
lever_times: Decimal = None
|
|
959
|
+
display_lever_times: Decimal = None
|
|
960
|
+
amount: Decimal = None # margin * lever_times
|
|
961
|
+
display_price: Decimal = None
|
|
962
|
+
display_close_price: Decimal = None
|
|
963
|
+
order_type: int = None
|
|
964
|
+
close_type: int = None
|
|
965
|
+
status: ContractOrderStatus = None
|
|
966
|
+
open_date: str = None
|
|
967
|
+
close_date: str = None
|
|
968
|
+
fees: Decimal = None
|
|
969
|
+
lever_fee: Decimal = None
|
|
970
|
+
name: str = None
|
|
971
|
+
order_create_type: int = None
|
|
972
|
+
hide_price: bool = None
|
|
973
|
+
fee_rate: Decimal = None
|
|
974
|
+
hide: int = None
|
|
975
|
+
liquidation_desc: str = None
|
|
976
|
+
contract_account_mode: int = None
|
|
977
|
+
current_price: Decimal = None
|
|
978
|
+
sys_force_price: Decimal = None
|
|
979
|
+
fund_type: int = None
|
|
980
|
+
interest: Decimal = None
|
|
981
|
+
order_open_trade: OrderOpenTradeInfo = None
|
|
982
|
+
order_debit: OrderDebitInfo = None
|
|
983
|
+
open_rate: Decimal = None
|
|
984
|
+
close_rate: Decimal = None
|
|
985
|
+
market_status: int = None
|
|
986
|
+
create_time: str = None
|
|
987
|
+
coupon_amount: Decimal = None
|
|
988
|
+
stop_profit_modify_time: str = None
|
|
989
|
+
stop_loss_modify_time: str = None
|
|
990
|
+
show_adjust_margin: int = None
|
|
991
|
+
trailing_stop: Decimal = None
|
|
992
|
+
trailing_close_price: Decimal = None
|
|
993
|
+
stop_rate: Decimal = None
|
|
994
|
+
profit_loss_info: ProfitLossInfoContainer = None
|
|
995
|
+
configs: Any = None # just dictionaries of take-profit and stop-loss configs.
|
|
996
|
+
stop_offset_rate: Decimal = None
|
|
997
|
+
|
|
998
|
+
def is_long(self) -> bool:
|
|
999
|
+
return self.order_type == 0
|
|
1000
|
+
|
|
1001
|
+
def get_open_price(self) -> Decimal:
|
|
1002
|
+
return self.display_price
|
|
1003
|
+
|
|
1004
|
+
def get_liquidation_price(self) -> Decimal:
|
|
1005
|
+
return self.sys_force_price
|
|
1006
|
+
|
|
1007
|
+
def get_profit_str(self) -> str:
|
|
1008
|
+
last_price = self.current_price or self.display_close_price
|
|
1009
|
+
profit_or_loss = last_price - self.display_price
|
|
1010
|
+
profit_percentage = (profit_or_loss / self.display_price) * 100
|
|
1011
|
+
profit_percentage *= 1 if self.is_long() else -1
|
|
1012
|
+
return dec_to_str(profit_percentage * self.lever_times)
|
|
1013
|
+
|
|
1014
|
+
def to_str(self, separator: str = "; ") -> str:
|
|
1015
|
+
result_str = f"{self.name} ({self.order_no}) "
|
|
1016
|
+
result_str += f"{ORDER_TYPES_MAP[self.order_type]} "
|
|
1017
|
+
result_str += f"{dec_to_str(self.lever_times)}x{separator}"
|
|
1018
|
+
result_str += f"margin: {dec_to_str(self.margin)} "
|
|
1019
|
+
result_str += f"{self.margin_coin_name}{separator}"
|
|
1020
|
+
|
|
1021
|
+
if self.profit_loss_info:
|
|
1022
|
+
if self.profit_loss_info.profit_config:
|
|
1023
|
+
tp_config = self.profit_loss_info.profit_config
|
|
1024
|
+
result_str += f"TP: {dec_to_normalize(tp_config.stop_price)} "
|
|
1025
|
+
result_str += f"{tp_config.margin_coin_name}{separator}"
|
|
1026
|
+
if self.profit_loss_info.loss_config:
|
|
1027
|
+
sl_config = self.profit_loss_info.loss_config
|
|
1028
|
+
result_str += f"SL: {dec_to_normalize(sl_config.stop_price)}"
|
|
1029
|
+
result_str += f"{sl_config.margin_coin_name}{separator}"
|
|
1030
|
+
|
|
1031
|
+
if self.sys_force_price:
|
|
1032
|
+
result_str += (
|
|
1033
|
+
f"liquidation: {dec_to_normalize(self.sys_force_price)}{separator}"
|
|
1034
|
+
)
|
|
1035
|
+
|
|
1036
|
+
if self.current_price:
|
|
1037
|
+
result_str += (
|
|
1038
|
+
f"current price: {dec_to_normalize(self.current_price)}{separator}"
|
|
1039
|
+
)
|
|
1040
|
+
elif self.display_close_price:
|
|
1041
|
+
result_str += (
|
|
1042
|
+
f"close price: {dec_to_normalize(self.display_close_price)}{separator}"
|
|
1043
|
+
)
|
|
1044
|
+
profit_str = self.get_profit_str()
|
|
1045
|
+
result_str += f"profit: {profit_str}%"
|
|
1046
|
+
|
|
1047
|
+
return result_str
|
|
1048
|
+
|
|
1049
|
+
def __str__(self):
|
|
1050
|
+
return self.to_str()
|
|
1051
|
+
|
|
1052
|
+
def __repr__(self):
|
|
1053
|
+
return self.to_str()
|
|
1054
|
+
|
|
1055
|
+
|
|
1056
|
+
class ClosedContractOrderInfo(ContractOrderInfo):
|
|
1057
|
+
close_type_name: str = None
|
|
1058
|
+
gross_earnings: Decimal = None
|
|
1059
|
+
position_order: int = None
|
|
1060
|
+
|
|
1061
|
+
|
|
1062
|
+
class MarginStatInfo(BaseModel):
|
|
1063
|
+
name: str = None
|
|
1064
|
+
margin_coin_name: str = None
|
|
1065
|
+
margin_type: int = None
|
|
1066
|
+
|
|
1067
|
+
# total count of open contract orders in this margin-type.
|
|
1068
|
+
total: int = None
|
|
1069
|
+
|
|
1070
|
+
|
|
1071
|
+
class ContractsListResult(BaseModel):
|
|
1072
|
+
orders: list[ContractOrderInfo] = None
|
|
1073
|
+
page_id: int = None
|
|
1074
|
+
margin_stats: list[MarginStatInfo] = None
|
|
1075
|
+
|
|
1076
|
+
|
|
1077
|
+
class ContractsListResponse(BxApiResponse):
|
|
1078
|
+
data: ContractsListResult = None
|
|
1079
|
+
|
|
1080
|
+
|
|
1081
|
+
class ContractOrdersHistoryResult(BaseModel):
|
|
1082
|
+
orders: list[ClosedContractOrderInfo] = None
|
|
1083
|
+
page_id: int = None
|
|
1084
|
+
|
|
1085
|
+
|
|
1086
|
+
class ContractOrdersHistoryResponse(BxApiResponse):
|
|
1087
|
+
data: ContractOrdersHistoryResult = None
|
|
1088
|
+
|
|
1089
|
+
def get_today_earnings(self, timezone: Any = pytz.UTC) -> Decimal:
|
|
1090
|
+
"""
|
|
1091
|
+
Returns the total earnings for today.
|
|
1092
|
+
NOTE: This function will return None if there are no orders for today.
|
|
1093
|
+
"""
|
|
1094
|
+
found_any_for_today: bool = False
|
|
1095
|
+
today_earnings = Decimal("0.00")
|
|
1096
|
+
today = datetime.now(timezone).date()
|
|
1097
|
+
if not self.data and self.msg:
|
|
1098
|
+
raise ExchangeError(self.msg)
|
|
1099
|
+
|
|
1100
|
+
for current_order in self.data.orders:
|
|
1101
|
+
# check if the date is for today
|
|
1102
|
+
closed_date = (
|
|
1103
|
+
datetime.strptime(
|
|
1104
|
+
current_order.close_date,
|
|
1105
|
+
"%Y-%m-%dT%H:%M:%S.%f%z",
|
|
1106
|
+
)
|
|
1107
|
+
.astimezone(timezone)
|
|
1108
|
+
.date()
|
|
1109
|
+
)
|
|
1110
|
+
if closed_date == today:
|
|
1111
|
+
today_earnings += current_order.gross_earnings
|
|
1112
|
+
found_any_for_today = True
|
|
1113
|
+
|
|
1114
|
+
if not found_any_for_today:
|
|
1115
|
+
return None
|
|
1116
|
+
|
|
1117
|
+
return today_earnings
|
|
1118
|
+
|
|
1119
|
+
def get_this_week_earnings(self, timezone: Any = pytz.UTC) -> Decimal:
|
|
1120
|
+
"""
|
|
1121
|
+
Returns the total earnings for this week.
|
|
1122
|
+
NOTE: This function will return None if there are no orders for this week.
|
|
1123
|
+
"""
|
|
1124
|
+
found_any_for_week: bool = False
|
|
1125
|
+
week_earnings = Decimal("0.00")
|
|
1126
|
+
today = datetime.now(timezone).date()
|
|
1127
|
+
week_start = today - timedelta(days=today.weekday())
|
|
1128
|
+
for current_order in self.data.orders:
|
|
1129
|
+
# check if the date is for this week
|
|
1130
|
+
closed_date = (
|
|
1131
|
+
datetime.strptime(
|
|
1132
|
+
current_order.close_date,
|
|
1133
|
+
"%Y-%m-%dT%H:%M:%S.%f%z",
|
|
1134
|
+
)
|
|
1135
|
+
.astimezone(timezone)
|
|
1136
|
+
.date()
|
|
1137
|
+
)
|
|
1138
|
+
if closed_date >= week_start:
|
|
1139
|
+
week_earnings += current_order.gross_earnings
|
|
1140
|
+
found_any_for_week = True
|
|
1141
|
+
|
|
1142
|
+
if not found_any_for_week:
|
|
1143
|
+
return None
|
|
1144
|
+
|
|
1145
|
+
return week_earnings
|
|
1146
|
+
|
|
1147
|
+
def get_this_month_earnings(self, timezone: Any = pytz.UTC) -> Decimal:
|
|
1148
|
+
"""
|
|
1149
|
+
Returns the total earnings for this month.
|
|
1150
|
+
NOTE: This function will return None if there are no orders for this month.
|
|
1151
|
+
"""
|
|
1152
|
+
found_any_for_month: bool = False
|
|
1153
|
+
month_earnings = Decimal("0.00")
|
|
1154
|
+
today = datetime.now(timezone).date()
|
|
1155
|
+
month_start = today.replace(day=1)
|
|
1156
|
+
for current_order in self.data.orders:
|
|
1157
|
+
# check if the date is for this month
|
|
1158
|
+
closed_date = (
|
|
1159
|
+
datetime.strptime(
|
|
1160
|
+
current_order.close_date,
|
|
1161
|
+
"%Y-%m-%dT%H:%M:%S.%f%z",
|
|
1162
|
+
)
|
|
1163
|
+
.astimezone(timezone)
|
|
1164
|
+
.date()
|
|
1165
|
+
)
|
|
1166
|
+
if closed_date >= month_start:
|
|
1167
|
+
month_earnings += current_order.gross_earnings
|
|
1168
|
+
found_any_for_month = True
|
|
1169
|
+
|
|
1170
|
+
if not found_any_for_month:
|
|
1171
|
+
return None
|
|
1172
|
+
|
|
1173
|
+
return month_earnings
|
|
1174
|
+
|
|
1175
|
+
def get_orders_len(self) -> int:
|
|
1176
|
+
if not self.data or not self.data.orders:
|
|
1177
|
+
return 0
|
|
1178
|
+
return len(self.data.orders)
|
|
1179
|
+
|
|
1180
|
+
|
|
1181
|
+
class ContractConfigData(BaseModel):
|
|
1182
|
+
quotation_coin_id: int = None
|
|
1183
|
+
max_lever: OrderLeverInfo = None
|
|
1184
|
+
min_amount: Decimal = None
|
|
1185
|
+
levers: list[OrderLeverInfo] = None
|
|
1186
|
+
default_stop_loss_rate: Decimal = None
|
|
1187
|
+
default_stop_profit_rate: Decimal = None
|
|
1188
|
+
max_stop_loss_rate: Decimal = None
|
|
1189
|
+
max_stop_profit_rate: Decimal = None
|
|
1190
|
+
fee_rate: Decimal = None
|
|
1191
|
+
interest_rate: Decimal = None
|
|
1192
|
+
lever_fee_rate: Decimal = None
|
|
1193
|
+
sys_force_rate: Decimal = None
|
|
1194
|
+
new_sys_force_vo_list: list[SysForceVoInfo] = None
|
|
1195
|
+
margin_displays: list[MarginDisplayInfo] = None
|
|
1196
|
+
mlr: Decimal = None
|
|
1197
|
+
lsf: Decimal = None
|
|
1198
|
+
lsh: Decimal = None
|
|
1199
|
+
hold_amount: Decimal = None
|
|
1200
|
+
msr: Decimal = None
|
|
1201
|
+
sfa: Decimal = None
|
|
1202
|
+
available_asset: MinimalAssetInfo = None
|
|
1203
|
+
coupon_asset_value: Any = None
|
|
1204
|
+
contract_account_balance: MinimalAssetInfo = None
|
|
1205
|
+
delegate_order_up_threshold_rate: Any = None
|
|
1206
|
+
delegate_order_down_threshold_rate: Any = None
|
|
1207
|
+
profit_loss_extra_vo: Any = None
|
|
1208
|
+
fund_balance: Decimal = None
|
|
1209
|
+
balance: Decimal = None
|
|
1210
|
+
up_amount: Decimal = None
|
|
1211
|
+
down_amount: Decimal = None
|
|
1212
|
+
max_amount: Decimal = None
|
|
1213
|
+
stop_offset_rate: Decimal = None
|
|
1214
|
+
|
|
1215
|
+
|
|
1216
|
+
class ContractConfigResponse(BxApiResponse):
|
|
1217
|
+
data: ContractConfigData = None
|
|
1218
|
+
|
|
1219
|
+
|
|
1220
|
+
# endregion
|
|
1221
|
+
|
|
1222
|
+
###########################################################
|
|
1223
|
+
|
|
1224
|
+
# region std futures types
|
|
1225
|
+
|
|
1226
|
+
|
|
1227
|
+
class CopyTraderStdFuturesPositionInfo(BaseModel):
|
|
1228
|
+
order_no: str = None
|
|
1229
|
+
quotation_coin_vo: QuotationCoinVOInfo = None
|
|
1230
|
+
margin: Decimal = None
|
|
1231
|
+
margin_coin_name: str = None
|
|
1232
|
+
lever_times: Decimal = None
|
|
1233
|
+
display_lever_times: Decimal = None
|
|
1234
|
+
amount: Decimal = None
|
|
1235
|
+
display_price: Decimal = None
|
|
1236
|
+
display_close_price: Decimal = None
|
|
1237
|
+
order_type: int = None
|
|
1238
|
+
close_type: int = None
|
|
1239
|
+
status: Any = None
|
|
1240
|
+
open_date: datetime = None
|
|
1241
|
+
fees: Decimal = None
|
|
1242
|
+
lever_fee: Decimal = None
|
|
1243
|
+
name: str = None
|
|
1244
|
+
order_create_type: int = None
|
|
1245
|
+
hide_price: bool = None
|
|
1246
|
+
fee_rate: Decimal = None
|
|
1247
|
+
hide: bool = None
|
|
1248
|
+
liquidation_desc: str = None
|
|
1249
|
+
contract_account_mode: Any = None
|
|
1250
|
+
current_price: Decimal = None
|
|
1251
|
+
sys_force_price: Decimal = None
|
|
1252
|
+
fund_type: int = None
|
|
1253
|
+
interest: Any = None
|
|
1254
|
+
order_open_trade: OrderOpenTradeInfo = None
|
|
1255
|
+
order_debit: OrderDebitInfo = None
|
|
1256
|
+
open_rate: Decimal = None
|
|
1257
|
+
close_rate: Decimal = None
|
|
1258
|
+
market_status: int = None
|
|
1259
|
+
create_time: datetime = None
|
|
1260
|
+
coupon_amount: Decimal = None
|
|
1261
|
+
stop_profit_rate: Decimal = None
|
|
1262
|
+
stop_loss_rate: Decimal = None
|
|
1263
|
+
stop_profit_modify_time: datetime = None
|
|
1264
|
+
stop_loss_modify_time: datetime = None
|
|
1265
|
+
show_adjust_margin: int = None
|
|
1266
|
+
trailing_stop: Decimal = None
|
|
1267
|
+
trailing_close_price: Decimal = None
|
|
1268
|
+
stop_rate: Decimal = None
|
|
1269
|
+
profit_loss_info: ProfitLossInfoContainer = None
|
|
1270
|
+
stop_offset_rate: None
|
|
1271
|
+
|
|
1272
|
+
def __str__(self):
|
|
1273
|
+
return super().__str__()
|
|
1274
|
+
|
|
1275
|
+
def __repr__(self):
|
|
1276
|
+
return self.__str__()
|
|
1277
|
+
|
|
1278
|
+
|
|
1279
|
+
class CopyTraderStdFuturesPositionsResult(BaseModel):
|
|
1280
|
+
page_id: int = None
|
|
1281
|
+
total_str: str = None
|
|
1282
|
+
positions: list[CopyTraderStdFuturesPositionInfo] = None
|
|
1283
|
+
|
|
1284
|
+
|
|
1285
|
+
class CopyTraderStdFuturesPositionsResponse(BxApiResponse):
|
|
1286
|
+
data: CopyTraderStdFuturesPositionsResult = None
|
|
1287
|
+
|
|
1288
|
+
|
|
1289
|
+
# endregion
|
|
1290
|
+
|
|
1291
|
+
###########################################################
|
|
1292
|
+
|
|
1293
|
+
# region contract delegation types
|
|
1294
|
+
|
|
1295
|
+
|
|
1296
|
+
class CreateOrderDelegationData(BaseModel):
|
|
1297
|
+
order_id: str = None
|
|
1298
|
+
spread_rate: str = None
|
|
1299
|
+
|
|
1300
|
+
|
|
1301
|
+
class CreateOrderDelegationResponse(BxApiResponse):
|
|
1302
|
+
data: CreateOrderDelegationData = None
|
|
1303
|
+
|
|
1304
|
+
|
|
1305
|
+
# endregion
|
|
1306
|
+
|
|
1307
|
+
###########################################################
|
|
1308
|
+
|
|
1309
|
+
# region candle types
|
|
1310
|
+
|
|
1311
|
+
|
|
1312
|
+
class SingleCandleInfo(MinimalCandleInfo):
|
|
1313
|
+
@staticmethod
|
|
1314
|
+
def deserialize_short(data: dict) -> "SingleCandleInfo":
|
|
1315
|
+
info = SingleCandleInfo()
|
|
1316
|
+
base: str = data.get("n", "")
|
|
1317
|
+
quote: str = data.get("m", "")
|
|
1318
|
+
info.pair = f"{base.upper()}/{quote.upper()}"
|
|
1319
|
+
info.open_price = as_decimal(data.get("o", None))
|
|
1320
|
+
info.close_price = as_decimal(data.get("c", None))
|
|
1321
|
+
info.volume = data.get("v", None)
|
|
1322
|
+
info.quote_volume = data.get("a", None)
|
|
1323
|
+
|
|
1324
|
+
return info
|
|
1325
|
+
|
|
1326
|
+
def __str__(self):
|
|
1327
|
+
return (
|
|
1328
|
+
f"{self.pair}, open: {self.open_price}, "
|
|
1329
|
+
f"close: {self.close_price}, volume: {self.quote_volume}"
|
|
1330
|
+
)
|
|
1331
|
+
|
|
1332
|
+
def __repr__(self):
|
|
1333
|
+
return super().__str__()
|
|
1334
|
+
|
|
1335
|
+
|
|
1336
|
+
# endregion
|
|
1337
|
+
|
|
1338
|
+
###########################################################
|