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.
Files changed (44) hide show
  1. trd_utils/__init__.py +3 -0
  2. trd_utils/cipher/__init__.py +44 -0
  3. trd_utils/common_utils/float_utils.py +21 -0
  4. trd_utils/common_utils/wallet_utils.py +26 -0
  5. trd_utils/date_utils/__init__.py +8 -0
  6. trd_utils/date_utils/datetime_helpers.py +25 -0
  7. trd_utils/exchanges/README.md +203 -0
  8. trd_utils/exchanges/__init__.py +28 -0
  9. trd_utils/exchanges/base_types.py +229 -0
  10. trd_utils/exchanges/binance/__init__.py +13 -0
  11. trd_utils/exchanges/binance/binance_client.py +389 -0
  12. trd_utils/exchanges/binance/binance_types.py +116 -0
  13. trd_utils/exchanges/blofin/__init__.py +6 -0
  14. trd_utils/exchanges/blofin/blofin_client.py +375 -0
  15. trd_utils/exchanges/blofin/blofin_types.py +173 -0
  16. trd_utils/exchanges/bx_ultra/__init__.py +6 -0
  17. trd_utils/exchanges/bx_ultra/bx_types.py +1338 -0
  18. trd_utils/exchanges/bx_ultra/bx_ultra_client.py +1123 -0
  19. trd_utils/exchanges/bx_ultra/bx_utils.py +51 -0
  20. trd_utils/exchanges/errors.py +10 -0
  21. trd_utils/exchanges/exchange_base.py +301 -0
  22. trd_utils/exchanges/hyperliquid/README.md +3 -0
  23. trd_utils/exchanges/hyperliquid/__init__.py +7 -0
  24. trd_utils/exchanges/hyperliquid/hyperliquid_client.py +292 -0
  25. trd_utils/exchanges/hyperliquid/hyperliquid_types.py +183 -0
  26. trd_utils/exchanges/okx/__init__.py +6 -0
  27. trd_utils/exchanges/okx/okx_client.py +219 -0
  28. trd_utils/exchanges/okx/okx_types.py +197 -0
  29. trd_utils/exchanges/price_fetcher.py +48 -0
  30. trd_utils/html_utils/__init__.py +26 -0
  31. trd_utils/html_utils/html_formats.py +72 -0
  32. trd_utils/tradingview/__init__.py +8 -0
  33. trd_utils/tradingview/tradingview_client.py +128 -0
  34. trd_utils/tradingview/tradingview_types.py +185 -0
  35. trd_utils/types_helper/__init__.py +12 -0
  36. trd_utils/types_helper/base_model.py +350 -0
  37. trd_utils/types_helper/decorators.py +20 -0
  38. trd_utils/types_helper/model_config.py +6 -0
  39. trd_utils/types_helper/ultra_list.py +39 -0
  40. trd_utils/types_helper/utils.py +40 -0
  41. trd_utils-0.0.57.dist-info/METADATA +42 -0
  42. trd_utils-0.0.57.dist-info/RECORD +44 -0
  43. trd_utils-0.0.57.dist-info/WHEEL +4 -0
  44. 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
+ ###########################################################