trd-utils 0.0.4__py3-none-any.whl → 0.0.6__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.

Potentially problematic release.


This version of trd-utils might be problematic. Click here for more details.

trd_utils/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
 
2
-
2
+ __version__ = "0.0.6"
3
3
 
@@ -0,0 +1,11 @@
1
+
2
+ from decimal import Decimal
3
+
4
+
5
+ default_quantize = Decimal("1.00")
6
+
7
+ def dec_to_str(dec_value: Decimal) -> str:
8
+ return format(dec_value.quantize(default_quantize), "f")
9
+
10
+ def dec_to_normalize(dec_value: Decimal) -> str:
11
+ return format(dec_value.normalize(), "f")
@@ -0,0 +1,11 @@
1
+
2
+ from .exchange_base import ExchangeBase
3
+ from .blofin import BlofinClient
4
+ from .bx_ultra import BXUltraClient
5
+
6
+
7
+ __all__ = [
8
+ ExchangeBase,
9
+ BXUltraClient,
10
+ BlofinClient,
11
+ ]
@@ -0,0 +1,6 @@
1
+
2
+ from .blofin_client import BlofinClient
3
+
4
+ __all__ = [
5
+ BlofinClient,
6
+ ]
@@ -0,0 +1,238 @@
1
+ from decimal import Decimal
2
+ import json
3
+ import logging
4
+ from typing import Type
5
+ import httpx
6
+
7
+ import time
8
+ from pathlib import Path
9
+
10
+ from trd_utils.exchanges.blofin.blofin_types import (
11
+ BlofinApiResponse,
12
+ CmsColorResponse,
13
+ CopyTraderInfoResponse,
14
+ CopyTraderOrderHistoryResponse,
15
+ CopyTraderOrderListResponse,
16
+ ShareConfigResponse,
17
+ )
18
+ from trd_utils.cipher import AESCipher
19
+ from trd_utils.exchanges.exchange_base import ExchangeBase
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+
24
+ class BlofinClient(ExchangeBase):
25
+ ###########################################################
26
+ # region client parameters
27
+ blofin_api_base_host: str = "https://\u0062lofin.co\u006d"
28
+ blofin_api_base_url: str = "https://\u0062lofin.co\u006d/uapi/v1"
29
+ origin_header: str = "https://\u0062lofin.co\u006d"
30
+
31
+ timezone: str = "Etc/UTC"
32
+
33
+ # endregion
34
+ ###########################################################
35
+ # region client constructor
36
+ def __init__(
37
+ self,
38
+ account_name: str = "default",
39
+ http_verify: bool = True,
40
+ fav_letter: str = "^",
41
+ read_session_file: bool = True,
42
+ sessions_dir: str = "sessions",
43
+ ):
44
+ self.httpx_client = httpx.AsyncClient(
45
+ verify=http_verify,
46
+ http2=True,
47
+ http1=False,
48
+ )
49
+ self.account_name = account_name
50
+ self._fav_letter = fav_letter
51
+ self.sessions_dir = sessions_dir
52
+
53
+ if read_session_file:
54
+ self.read_from_session_file(f"{sessions_dir}/{self.account_name}.bf")
55
+
56
+ # endregion
57
+ ###########################################################
58
+ # region v1/cms/
59
+ async def get_share_config(self) -> ShareConfigResponse:
60
+ headers = self.get_headers()
61
+ return await self.invoke_get(
62
+ f"{self.blofin_api_base_url}/cms/share_config",
63
+ headers=headers,
64
+ model=ShareConfigResponse,
65
+ )
66
+
67
+ async def get_cms_color(self) -> CmsColorResponse:
68
+ headers = self.get_headers()
69
+ return await self.invoke_get(
70
+ f"{self.blofin_api_base_url}/cms/color",
71
+ headers=headers,
72
+ model=CmsColorResponse,
73
+ )
74
+
75
+ # endregion
76
+ ###########################################################
77
+ # region copy/trader
78
+ async def get_copy_trader_info(self, uid: int) -> CopyTraderInfoResponse:
79
+ payload = {
80
+ "uid": uid,
81
+ }
82
+ headers = self.get_headers()
83
+ return await self.invoke_post(
84
+ f"{self.blofin_api_base_url}/copy/trader/info",
85
+ headers=headers,
86
+ content=payload,
87
+ model=CopyTraderInfoResponse,
88
+ )
89
+
90
+ async def get_copy_trader_order_list(
91
+ self,
92
+ from_param: int,
93
+ limit_param: 0,
94
+ uid: int,
95
+ ) -> CopyTraderOrderListResponse:
96
+ payload = {
97
+ "from": from_param,
98
+ "limit": limit_param,
99
+ "uid": uid,
100
+ }
101
+ headers = self.get_headers()
102
+ return await self.invoke_post(
103
+ f"{self.blofin_api_base_url}/copy/trader/order/list",
104
+ headers=headers,
105
+ content=payload,
106
+ model=CopyTraderOrderListResponse,
107
+ )
108
+
109
+ async def get_copy_trader_order_history(
110
+ self,
111
+ from_param: int,
112
+ limit_param: 0,
113
+ uid: int,
114
+ ) -> CopyTraderOrderHistoryResponse:
115
+ payload = {
116
+ "from": from_param,
117
+ "limit": limit_param,
118
+ "uid": uid,
119
+ }
120
+ headers = self.get_headers()
121
+ return await self.invoke_post(
122
+ f"{self.blofin_api_base_url}/copy/trader/order/history",
123
+ headers=headers,
124
+ content=payload,
125
+ model=CopyTraderOrderHistoryResponse,
126
+ )
127
+
128
+ # endregion
129
+ ###########################################################
130
+ # region client helper methods
131
+ def get_headers(self, payload=None, needs_auth: bool = False) -> dict:
132
+ the_timestamp = int(time.time() * 1000)
133
+ the_headers = {
134
+ # "Host": self.blofin_api_base_host,
135
+ "Content-Type": "application/json",
136
+ "Accept": "application/json",
137
+ "Origin": self.origin_header,
138
+ "X-Tz": self.timezone,
139
+ "Fp-Request-Id": f"{the_timestamp}.n1fDrN",
140
+ "Accept-Encoding": "gzip, deflate, br, zstd",
141
+ "User-Agent": self.user_agent,
142
+ "Connection": "close",
143
+ "appsiteid": "0",
144
+ }
145
+
146
+ if self.x_requested_with:
147
+ the_headers["X-Requested-With"] = self.x_requested_with
148
+
149
+ if needs_auth:
150
+ the_headers["Authorization"] = f"Bearer {self.authorization_token}"
151
+ return the_headers
152
+
153
+ async def invoke_get(
154
+ self,
155
+ url: str,
156
+ headers: dict | None = None,
157
+ params: dict | None = None,
158
+ model: Type[BlofinApiResponse] | None = None,
159
+ parse_float=Decimal,
160
+ ) -> "BlofinApiResponse":
161
+ """
162
+ Invokes the specific request to the specific url with the specific params and headers.
163
+ """
164
+ response = await self.httpx_client.get(
165
+ url=url,
166
+ headers=headers,
167
+ params=params,
168
+ )
169
+ return model.deserialize(response.json(parse_float=parse_float))
170
+
171
+ async def invoke_post(
172
+ self,
173
+ url: str,
174
+ headers: dict | None = None,
175
+ params: dict | None = None,
176
+ content: dict | str | bytes = "",
177
+ model: Type[BlofinApiResponse] | None = None,
178
+ parse_float=Decimal,
179
+ ) -> "BlofinApiResponse":
180
+ """
181
+ Invokes the specific request to the specific url with the specific params and headers.
182
+ """
183
+
184
+ if isinstance(content, dict):
185
+ content = json.dumps(content, separators=(",", ":"), sort_keys=True)
186
+
187
+ response = await self.httpx_client.post(
188
+ url=url,
189
+ headers=headers,
190
+ params=params,
191
+ content=content,
192
+ )
193
+ if not model:
194
+ return response.json()
195
+
196
+ return model.deserialize(response.json(parse_float=parse_float))
197
+
198
+ async def aclose(self) -> None:
199
+ await self.httpx_client.aclose()
200
+ logger.info("BlofinClient closed")
201
+ return True
202
+
203
+ def read_from_session_file(self, file_path: str) -> None:
204
+ """
205
+ Reads from session file; if it doesn't exist, creates it.
206
+ """
207
+ # check if path exists
208
+ target_path = Path(file_path)
209
+ if not target_path.exists():
210
+ return self._save_session_file(file_path=file_path)
211
+
212
+ aes = AESCipher(key=f"bf_{self.account_name}_bf", fav_letter=self._fav_letter)
213
+ content = aes.decrypt(target_path.read_text()).decode("utf-8")
214
+ json_data: dict = json.loads(content)
215
+
216
+ self.authorization_token = json_data.get(
217
+ "authorization_token",
218
+ self.authorization_token,
219
+ )
220
+ self.timezone = json_data.get("timezone", self.timezone)
221
+ self.user_agent = json_data.get("user_agent", self.user_agent)
222
+
223
+ def _save_session_file(self, file_path: str) -> None:
224
+ """
225
+ Saves current information to the session file.
226
+ """
227
+
228
+ json_data = {
229
+ "authorization_token": self.authorization_token,
230
+ "timezone": self.timezone,
231
+ "user_agent": self.user_agent,
232
+ }
233
+ aes = AESCipher(key=f"bf_{self.account_name}_bf", fav_letter=self._fav_letter)
234
+ target_path = Path(file_path)
235
+ target_path.write_text(aes.encrypt(json.dumps(json_data)))
236
+
237
+ # endregion
238
+ ###########################################################
@@ -0,0 +1,144 @@
1
+ # from typing import Any, Optional
2
+ # from decimal import Decimal
3
+ # from datetime import datetime, timedelta
4
+ # import pytz
5
+
6
+ from decimal import Decimal
7
+ from typing import Any
8
+ from trd_utils.types_helper import BaseModel
9
+
10
+ # from trd_utils.common_utils.float_utils import (
11
+ # dec_to_str,
12
+ # dec_to_normalize,
13
+ # )
14
+
15
+
16
+
17
+ class BlofinApiResponse(BaseModel):
18
+ code: int = None
19
+ timestamp: int = None
20
+ msg: str = None
21
+
22
+ def __str__(self):
23
+ return f"code: {self.code}; timestamp: {self.timestamp}"
24
+
25
+ def __repr__(self):
26
+ return f"code: {self.code}; timestamp: {self.timestamp}"
27
+
28
+
29
+ ###########################################################
30
+
31
+ class PnlShareListInfo(BaseModel):
32
+ background_color: str = None
33
+ background_img_up: str = None
34
+ background_img_down: str = None
35
+
36
+ class ShareConfigResult(BaseModel):
37
+ pnl_share_list: list[PnlShareListInfo] = None
38
+
39
+ class ShareConfigResponse(BlofinApiResponse):
40
+ data: ShareConfigResult = None
41
+
42
+ class CmsColorResult(BaseModel):
43
+ color: str = None
44
+ city: str = None
45
+ country: str = None
46
+ ip: str = None
47
+
48
+ class CmsColorResponse(BlofinApiResponse):
49
+ data: CmsColorResult = None
50
+
51
+ ###########################################################
52
+
53
+
54
+ class CopyTraderInfoResult(BaseModel):
55
+ aum: str = None
56
+ can_copy: bool = None
57
+ copier_whitelist: bool = None
58
+ follow_state: int = None
59
+ followers: int = None
60
+ followers_max: int = None
61
+ forbidden_follow_type: int = None
62
+ hidden_all: bool = None
63
+ hidden_order: bool = None
64
+ joined_date: int = None
65
+ max_draw_down: Decimal = None
66
+ nick_name: str = None
67
+ order_amount_limit: None
68
+ profile: str = None
69
+ profit_sharing_ratio: Decimal = None
70
+ real_pnl: Decimal = None
71
+ roi_d7: Decimal = None
72
+ self_introduction: str = None
73
+ sharing_period: str = None
74
+ source: int = None
75
+ uid: int = None
76
+ whitelist_copier: bool = None
77
+ win_rate: Decimal = None
78
+
79
+ def get_profile_url(self) -> str:
80
+ return f"https://blofin.com/copy-trade/details/{self.uid}"
81
+
82
+ class CopyTraderInfoResponse(BlofinApiResponse):
83
+ data: CopyTraderInfoResult = None
84
+
85
+ class CopyTraderSingleOrderInfo(BaseModel):
86
+ id: int = None
87
+ symbol: str = None
88
+ leverage: int = None
89
+ order_side: str = None
90
+ avg_open_price: str = None
91
+ quantity: str = None
92
+ quantity_cont: None
93
+ open_time: int = None
94
+ close_time: Any = None
95
+ avg_close_price: Decimal = None
96
+ real_pnl: Any = None
97
+ close_type: Any = None
98
+ roe: Decimal = None
99
+ followers_profit: Decimal = None
100
+ followers: Any = None
101
+ order_id: Any = None
102
+ sharing: Any = None
103
+ order_state: None
104
+ trader_name: None
105
+ mark_price: None
106
+ tp_trigger_price: None
107
+ tp_order_type: None
108
+ sl_trigger_price: None
109
+ sl_order_type: None
110
+ margin_mode: str = None
111
+ time_in_force: None
112
+ position_side: str = None
113
+ order_category: None
114
+ price: None
115
+ fill_quantity: None
116
+ fill_quantity_cont: None
117
+ pnl: None
118
+ cancel_source: None
119
+ order_type: None
120
+ order_open_state: None
121
+ amount: None
122
+ filled_amount: None
123
+ create_time: None
124
+ update_time: None
125
+ open_fee: None
126
+ close_fee: None
127
+ id_md5: None
128
+ tp_sl: None
129
+ trader_uid: None
130
+ available_quantity: None
131
+ available_quantity_cont: None
132
+ show_in_kline: None
133
+ unrealized_pnl: None
134
+ unrealized_pnl_ratio: None
135
+ broker_id: None
136
+ position_change_history: None
137
+ user_id: None
138
+
139
+ class CopyTraderOrderListResponse(BlofinApiResponse):
140
+ data: list[CopyTraderSingleOrderInfo] = None
141
+
142
+
143
+ class CopyTraderOrderHistoryResponse(BlofinApiResponse):
144
+ data: list[CopyTraderSingleOrderInfo] = None
@@ -1,10 +1,11 @@
1
1
  from typing import Any, Optional
2
- from ..types_helper import BaseModel
3
2
  from decimal import Decimal
4
3
  from datetime import datetime, timedelta
5
4
  import pytz
6
5
 
7
- from .common_utils import (
6
+ from trd_utils.types_helper import BaseModel
7
+
8
+ from trd_utils.common_utils.float_utils import (
8
9
  dec_to_str,
9
10
  dec_to_normalize,
10
11
  )
@@ -4,6 +4,7 @@ import asyncio
4
4
  from decimal import Decimal
5
5
  import json
6
6
  import logging
7
+ from typing import Type
7
8
  import uuid
8
9
 
9
10
  import httpx
@@ -11,8 +12,8 @@ import httpx
11
12
  import time
12
13
  from pathlib import Path
13
14
 
14
- from .common_utils import do_ultra_ss
15
- from .bx_types import (
15
+ from trd_utils.exchanges.bx_ultra.bx_utils import do_ultra_ss
16
+ from trd_utils.exchanges.bx_ultra.bx_types import (
16
17
  AssetsInfoResponse,
17
18
  ContractOrdersHistoryResponse,
18
19
  ContractsListResponse,
@@ -26,8 +27,11 @@ from .bx_types import (
26
27
  UserFavoriteQuotationResponse,
27
28
  ZenDeskABStatusResponse,
28
29
  ZoneModuleListResponse,
30
+ BxApiResponse,
29
31
  )
30
- from ..cipher import AESCipher
32
+ from trd_utils.cipher import AESCipher
33
+
34
+ from trd_utils.exchanges.exchange_base import ExchangeBase
31
35
 
32
36
  PLATFORM_ID_ANDROID = "10"
33
37
  PLATFORM_ID_WEB = "30"
@@ -44,25 +48,16 @@ TG_APP_VERSION = "5.0.15"
44
48
  logger = logging.getLogger(__name__)
45
49
 
46
50
 
47
- class BXUltraClient:
51
+ class BXUltraClient(ExchangeBase):
48
52
  ###########################################################
49
53
  # region client parameters
50
54
  we_api_base_host: str = "\u0061pi-\u0061pp.w\u0065-\u0061pi.com"
51
55
  we_api_base_url: str = "https://\u0061pi-\u0061pp.w\u0065-\u0061pi.com/\u0061pi"
52
-
53
56
  original_base_host: str = "https://\u0062ing\u0078.co\u006d"
54
-
55
57
  qq_os_base_host: str = "https://\u0061pi-\u0061pp.\u0071\u0071-os.com"
56
58
  qq_os_base_url: str = "https://\u0061pi-\u0061pp.\u0071\u0071-os.com/\u0061pi"
57
59
 
58
- device_id: str = None
59
- trace_id: str = None
60
- app_version: str = "4.28.3"
61
- platform_id: str = "10"
62
- install_channel: str = "officialAPK"
63
- channel_header: str = "officialAPK"
64
60
  origin_header: str = "https://\u0062ing\u0078.co\u006d"
65
- authorization_token: str = None
66
61
  app_id: str = "30004"
67
62
  main_app_id: str = "10009"
68
63
  trade_env: str = "real"
@@ -71,12 +66,6 @@ class BXUltraClient:
71
66
  device_brand: str = "SM-N976N"
72
67
  platform_lang: str = "en"
73
68
  sys_lang: str = "en"
74
- user_agent: str = "okhttp/4.12.0"
75
- x_requested_with: str = None
76
- httpx_client: httpx.AsyncClient = None
77
- account_name: str = "default"
78
-
79
- _fav_letter: str = "^"
80
69
 
81
70
  # endregion
82
71
  ###########################################################
@@ -89,6 +78,7 @@ class BXUltraClient:
89
78
  app_version: str = ANDROID_APP_VERSION,
90
79
  http_verify: bool = True,
91
80
  fav_letter: str = "^",
81
+ sessions_dir: str = "sessions",
92
82
  ):
93
83
  self.httpx_client = httpx.AsyncClient(
94
84
  verify=http_verify, http2=True, http1=False
@@ -99,13 +89,15 @@ class BXUltraClient:
99
89
  self.app_version = app_version
100
90
  self._fav_letter = fav_letter
101
91
 
102
- self.read_from_session_file(f"{self.account_name}.bx")
92
+ self.read_from_session_file(f"{sessions_dir}/{self.account_name}.bx")
103
93
 
104
94
  # endregion
105
95
  ###########################################################
106
96
  # region api/coin/v1
107
97
  async def get_zone_module_info(
108
- self, only_one_position: int = 0, biz_type: int = 10
98
+ self,
99
+ only_one_position: int = 0,
100
+ biz_type: int = 10,
109
101
  ) -> ZoneModuleListResponse:
110
102
  """
111
103
  Fetches and returns zone module info from the API.
@@ -118,101 +110,113 @@ class BXUltraClient:
118
110
  }
119
111
  headers = self.get_headers(params)
120
112
  headers["Only_one_position"] = f"{only_one_position}"
121
- response = await self.httpx_client.get(
113
+ return await self.invoke_get(
122
114
  f"{self.we_api_base_url}/coin/v1/zone/module-info",
123
115
  headers=headers,
124
116
  params=params,
117
+ model=ZoneModuleListResponse,
125
118
  )
126
- return ZoneModuleListResponse.deserialize(response.json(parse_float=Decimal))
127
119
 
128
120
  async def get_user_favorite_quotation(
129
- self, only_one_position: int = 0, biz_type: int = 1
130
- ):
121
+ self,
122
+ only_one_position: int = 0,
123
+ biz_type: int = 1,
124
+ ) -> UserFavoriteQuotationResponse:
131
125
  params = {
132
126
  "bizType": f"{biz_type}",
133
127
  }
134
128
  headers = self.get_headers(params)
135
129
  headers["Only_one_position"] = f"{only_one_position}"
136
- response = await self.httpx_client.get(
130
+ return await self.invoke_get(
137
131
  f"{self.we_api_base_url}/coin/v1/user/favorite/quotation",
138
132
  headers=headers,
139
133
  params=params,
140
- )
141
- return UserFavoriteQuotationResponse.deserialize(
142
- response.json(parse_float=Decimal)
134
+ model=UserFavoriteQuotationResponse,
143
135
  )
144
136
 
145
- async def get_quotation_rank(self, only_one_position: int = 0, order_flag: int = 0):
137
+ async def get_quotation_rank(
138
+ self,
139
+ only_one_position: int = 0,
140
+ order_flag: int = 0,
141
+ ) -> QuotationRankResponse:
146
142
  params = {
147
143
  "orderFlag": f"{order_flag}",
148
144
  }
149
145
  headers = self.get_headers(params)
150
146
  headers["Only_one_position"] = f"{only_one_position}"
151
- response = await self.httpx_client.get(
147
+ return await self.invoke_get(
152
148
  f"{self.we_api_base_url}/coin/v1/rank/quotation-rank",
153
149
  headers=headers,
154
150
  params=params,
151
+ model=QuotationRankResponse,
155
152
  )
156
- return QuotationRankResponse.deserialize(response.json(parse_float=Decimal))
157
153
 
158
- async def get_hot_search(self, only_one_position: int = 0, biz_type: int = 30):
154
+ async def get_hot_search(
155
+ self,
156
+ only_one_position: int = 0,
157
+ biz_type: int = 30,
158
+ ) -> HotSearchResponse:
159
159
  params = {
160
160
  "bizType": f"{biz_type}",
161
161
  }
162
162
  headers = self.get_headers(params)
163
163
  headers["Only_one_position"] = f"{only_one_position}"
164
- response = await self.httpx_client.get(
164
+ return await self.invoke_get(
165
165
  f"{self.we_api_base_url}/coin/v1/quotation/hot-search",
166
166
  headers=headers,
167
167
  params=params,
168
+ model=HotSearchResponse,
168
169
  )
169
- return HotSearchResponse.deserialize(response.json(parse_float=Decimal))
170
170
 
171
- async def get_homepage(self, only_one_position: int = 0, biz_type: int = 30):
171
+ async def get_homepage(
172
+ self,
173
+ only_one_position: int = 0,
174
+ biz_type: int = 30,
175
+ ) -> HomePageResponse:
172
176
  params = {
173
177
  "biz-type": f"{biz_type}",
174
178
  }
175
179
  headers = self.get_headers(params)
176
180
  headers["Only_one_position"] = f"{only_one_position}"
177
- response = await self.httpx_client.get(
181
+ return await self.invoke_get(
178
182
  f"{self.we_api_base_url}/coin/v1/discovery/homepage",
179
183
  headers=headers,
180
184
  params=params,
185
+ model=HomePageResponse,
181
186
  )
182
- return HomePageResponse.deserialize(response.json(parse_float=Decimal))
183
187
 
184
188
  # endregion
185
189
  ###########################################################
186
190
  # region customer
187
- async def get_zendesk_ab_status(self):
191
+ async def get_zendesk_ab_status(self) -> ZenDeskABStatusResponse:
188
192
  headers = self.get_headers()
189
- response = await self.httpx_client.get(
193
+ return await self.invoke_get(
190
194
  f"{self.we_api_base_url}/customer/v1/zendesk/ab-status",
191
195
  headers=headers,
196
+ model=ZenDeskABStatusResponse,
192
197
  )
193
- return ZenDeskABStatusResponse.deserialize(response.json(parse_float=Decimal))
194
198
 
195
199
  # endregion
196
200
  ###########################################################
197
201
  # region platform-tool
198
202
  async def get_hint_list(self) -> HintListResponse:
199
203
  headers = self.get_headers()
200
- response = await self.httpx_client.get(
204
+ return await self.invoke_get(
201
205
  f"{self.we_api_base_url}/platform-tool/v1/hint/list",
202
206
  headers=headers,
207
+ model=HintListResponse,
203
208
  )
204
- return HintListResponse.deserialize(response.json(parse_float=Decimal))
205
209
 
206
210
  # endregion
207
211
  ###########################################################
208
212
  # region asset-manager
209
213
  async def get_assets_info(self) -> AssetsInfoResponse:
210
214
  headers = self.get_headers(needs_auth=True)
211
- response = await self.httpx_client.get(
215
+ return await self.invoke_get(
212
216
  f"{self.we_api_base_url}/asset-manager/v1/assets/account-total-overview",
213
217
  headers=headers,
218
+ model=AssetsInfoResponse,
214
219
  )
215
- return AssetsInfoResponse.deserialize(response.json(parse_float=Decimal))
216
220
 
217
221
  # endregion
218
222
  ###########################################################
@@ -236,12 +240,12 @@ class BXUltraClient:
236
240
  if margin_coin_name:
237
241
  params["marginCoinName"] = margin_coin_name
238
242
  headers = self.get_headers(params, needs_auth=True)
239
- response = await self.httpx_client.get(
243
+ return await self.invoke_get(
240
244
  f"{self.we_api_base_url}/v4/contract/order/hold",
241
245
  headers=headers,
242
246
  params=params,
247
+ model=ContractsListResponse,
243
248
  )
244
- return ContractsListResponse.deserialize(response.json(parse_float=Decimal))
245
249
 
246
250
  async def get_contract_order_history(
247
251
  self,
@@ -263,13 +267,11 @@ class BXUltraClient:
263
267
  params["fromOrderNo"] = f"{from_order_no}"
264
268
 
265
269
  headers = self.get_headers(params, needs_auth=True)
266
- response = await self.httpx_client.get(
270
+ return await self.invoke_get(
267
271
  f"{self.we_api_base_url}/v2/contract/order/history",
268
272
  headers=headers,
269
273
  params=params,
270
- )
271
- return ContractOrdersHistoryResponse.deserialize(
272
- response.json(parse_float=Decimal)
274
+ model=ContractOrdersHistoryResponse,
273
275
  )
274
276
 
275
277
  async def get_today_contract_earnings(
@@ -282,7 +284,7 @@ class BXUltraClient:
282
284
  """
283
285
  Fetches today's earnings from the contract orders.
284
286
  NOTE: This method is a bit slow due to the API rate limiting.
285
- NOTE: If the user has not opened ANY contract orders today,
287
+ NOTE: If the user has not opened ANY contract orders today,
286
288
  this method will return None.
287
289
  """
288
290
  return await self._get_period_contract_earnings(
@@ -303,7 +305,7 @@ class BXUltraClient:
303
305
  """
304
306
  Fetches this week's earnings from the contract orders.
305
307
  NOTE: This method is a bit slow due to the API rate limiting.
306
- NOTE: If the user has not opened ANY contract orders this week,
308
+ NOTE: If the user has not opened ANY contract orders this week,
307
309
  this method will return None.
308
310
  """
309
311
  return await self._get_period_contract_earnings(
@@ -324,7 +326,7 @@ class BXUltraClient:
324
326
  """
325
327
  Fetches this month's earnings from the contract orders.
326
328
  NOTE: This method is a bit slow due to the API rate limiting.
327
- NOTE: If the user has not opened ANY contract orders this week,
329
+ NOTE: If the user has not opened ANY contract orders this week,
328
330
  this method will return None.
329
331
  """
330
332
  return await self._get_period_contract_earnings(
@@ -368,11 +370,11 @@ class BXUltraClient:
368
370
  if result.get_orders_len() < page_size:
369
371
  break
370
372
  await asyncio.sleep(delay_per_fetch)
371
-
373
+
372
374
  if not has_earned_any:
373
375
  return None
374
376
  return total_earnings
375
-
377
+
376
378
  # endregion
377
379
  ###########################################################
378
380
  # region copy-trade-facade
@@ -392,13 +394,11 @@ class BXUltraClient:
392
394
  "copyTradeLabelType": f"{copy_trade_label_type}",
393
395
  }
394
396
  headers = self.get_headers(params)
395
- response = await self.httpx_client.get(
397
+ return await self.invoke_get(
396
398
  f"{self.we_api_base_url}/copy-trade-facade/v2/real/trader/positions",
397
399
  headers=headers,
398
400
  params=params,
399
- )
400
- return CopyTraderTradePositionsResponse.deserialize(
401
- response.json(parse_float=Decimal)
401
+ model=CopyTraderTradePositionsResponse,
402
402
  )
403
403
 
404
404
  async def search_copy_traders(
@@ -430,25 +430,25 @@ class BXUltraClient:
430
430
  "nickName": nick_name,
431
431
  }
432
432
  headers = self.get_headers(payload)
433
- response = await self.httpx_client.post(
433
+ return await self.invoke_post(
434
434
  f"{self.we_api_base_url}/v6/copy-trade/search/search",
435
435
  headers=headers,
436
436
  params=params,
437
- content=json.dumps(payload, separators=(",", ":"), sort_keys=True),
437
+ content=payload,
438
+ model=SearchCopyTradersResponse,
438
439
  )
439
- return SearchCopyTradersResponse.deserialize(response.json(parse_float=Decimal))
440
440
 
441
441
  # endregion
442
442
  ###########################################################
443
443
  # region welfare
444
444
  async def do_daily_check_in(self):
445
445
  headers = self.get_headers(needs_auth=True)
446
- response = await self.httpx_client.post(
446
+ return await self.invoke_post(
447
447
  f"{self.original_base_host}/api/act-operation/v1/welfare/sign-in/do",
448
448
  headers=headers,
449
449
  content="",
450
+ model=None,
450
451
  )
451
- return response.json()
452
452
 
453
453
  # endregion
454
454
  ###########################################################
@@ -484,7 +484,7 @@ class BXUltraClient:
484
484
  payload_data=payload,
485
485
  ),
486
486
  "Timestamp": f"{the_timestamp}",
487
- # 'Accept-Encoding': 'gzip, deflate',
487
+ 'Accept-Encoding': 'gzip, deflate',
488
488
  "User-Agent": self.user_agent,
489
489
  "Connection": "close",
490
490
  "appsiteid": "0",
@@ -497,6 +497,51 @@ class BXUltraClient:
497
497
  the_headers["Authorization"] = f"Bearer {self.authorization_token}"
498
498
  return the_headers
499
499
 
500
+ async def invoke_get(
501
+ self,
502
+ url: str,
503
+ headers: dict | None = None,
504
+ params: dict | None = None,
505
+ model: Type[BxApiResponse] | None = None,
506
+ parse_float=Decimal,
507
+ ) -> "BxApiResponse":
508
+ """
509
+ Invokes the specific request to the specific url with the specific params and headers.
510
+ """
511
+ response = await self.httpx_client.get(
512
+ url=url,
513
+ headers=headers,
514
+ params=params,
515
+ )
516
+ return model.deserialize(response.json(parse_float=parse_float))
517
+
518
+ async def invoke_post(
519
+ self,
520
+ url: str,
521
+ headers: dict | None = None,
522
+ params: dict | None = None,
523
+ content: dict | str | bytes = "",
524
+ model: Type[BxApiResponse] | None = None,
525
+ parse_float=Decimal,
526
+ ) -> "BxApiResponse":
527
+ """
528
+ Invokes the specific request to the specific url with the specific params and headers.
529
+ """
530
+
531
+ if isinstance(content, dict):
532
+ content = json.dumps(content, separators=(",", ":"), sort_keys=True)
533
+
534
+ response = await self.httpx_client.post(
535
+ url=url,
536
+ headers=headers,
537
+ params=params,
538
+ content=content,
539
+ )
540
+ if not model:
541
+ return response.json()
542
+
543
+ return model.deserialize(response.json(parse_float=parse_float))
544
+
500
545
  async def aclose(self) -> None:
501
546
  await self.httpx_client.aclose()
502
547
  logger.info("BXUltraClient closed")
@@ -1,9 +1,7 @@
1
1
  import hashlib
2
2
  import json
3
3
  import uuid
4
- from decimal import Decimal
5
4
 
6
- default_quantize = Decimal("1.00")
7
5
 
8
6
  default_e: str = (
9
7
  "\u0039\u0035\u0064\u0036\u0035\u0063\u0037\u0033\u0064\u0063\u0035"
@@ -17,12 +15,6 @@ long_accept_header1: str = (
17
15
  + "image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"
18
16
  )
19
17
 
20
- def dec_to_str(dec_value: Decimal) -> str:
21
- return format(dec_value.quantize(default_quantize), "f")
22
-
23
- def dec_to_normalize(dec_value: Decimal) -> str:
24
- return format(dec_value.normalize(), "f")
25
-
26
18
  def do_ultra_ss(
27
19
  e_param: str,
28
20
  se_param: str,
@@ -0,0 +1,76 @@
1
+
2
+ from decimal import Decimal
3
+ from typing import Any
4
+ from abc import ABC
5
+
6
+ import httpx
7
+
8
+
9
+ class ExchangeBase(ABC):
10
+ ###########################################################
11
+ # region client parameters
12
+ user_agent: str = "okhttp/4.12.0"
13
+ x_requested_with: str = None
14
+ httpx_client: httpx.AsyncClient = None
15
+ account_name: str = "default"
16
+ sessions_dir: str = "sessions"
17
+
18
+ authorization_token: str = None
19
+ device_id: str = None
20
+ trace_id: str = None
21
+ app_version: str = "4.28.3"
22
+ platform_id: str = "10"
23
+ install_channel: str = "officialAPK"
24
+ channel_header: str = "officialAPK"
25
+
26
+ _fav_letter: str = "^"
27
+ # endregion
28
+ ###########################################################
29
+ # region client helper methods
30
+ def get_headers(self, payload=None, needs_auth: bool = False) -> dict:
31
+ pass
32
+
33
+ async def invoke_get(
34
+ self,
35
+ url: str,
36
+ headers: dict | None,
37
+ params: dict | None,
38
+ model: Any,
39
+ parse_float=Decimal,
40
+ ) -> Any:
41
+ """
42
+ Invokes the specific request to the specific url with the specific params and headers.
43
+ """
44
+ pass
45
+
46
+ async def invoke_post(
47
+ self,
48
+ url: str,
49
+ headers: dict | None = None,
50
+ params: dict | None = None,
51
+ content: str | bytes = "",
52
+ model: None = None,
53
+ parse_float=Decimal,
54
+ ):
55
+ """
56
+ Invokes the specific request to the specific url with the specific params and headers.
57
+ """
58
+ pass
59
+
60
+ async def aclose(self) -> None:
61
+ pass
62
+
63
+ def read_from_session_file(self, file_path: str) -> None:
64
+ """
65
+ Reads from session file; if it doesn't exist, creates it.
66
+ """
67
+ pass
68
+
69
+ def _save_session_file(self, file_path: str) -> None:
70
+ """
71
+ Saves current information to the session file.
72
+ """
73
+ pass
74
+
75
+ # endregion
76
+ ###########################################################
@@ -1,6 +1,3 @@
1
-
2
-
3
-
4
1
  import json
5
2
  from typing import Optional
6
3
 
@@ -10,41 +7,45 @@ from .tradingview_types import CoinScanInfo
10
7
  class TradingViewClient:
11
8
  """TradingViewClient class to interact with TradingView API."""
12
9
 
13
-
14
10
  def __init__(self) -> None:
15
11
  pass
16
12
 
17
- async def get_coin_scan(self,
18
- coin_filter: Optional[str] = None,
19
- limit: int = 200,
20
- offset: int = 0) -> list['CoinScanInfo']:
13
+ async def get_coin_scan(
14
+ self,
15
+ coin_filter: Optional[str] = None,
16
+ limit: int = 200,
17
+ offset: int = 0,
18
+ ) -> list["CoinScanInfo"]:
21
19
  import httpx
20
+
22
21
  cookies = {
23
- 'cookiesSettings': '{"analytics":true,"advertising":true}',
24
- 'cookiePrivacyPreferenceBannerProduction': 'accepted',
22
+ "cookiesSettings": '{"analytics":true,"advertising":true}',
23
+ "cookiePrivacyPreferenceBannerProduction": "accepted",
25
24
  }
26
25
 
27
26
  headers = {
28
- 'accept': 'application/json',
29
- 'accept-language': 'en-US,en;q=0.9',
30
- 'cache-control': 'no-cache',
31
- 'content-type': 'text/plain;charset=UTF-8',
32
- 'origin': 'https://www.tradingview.com',
33
- 'pragma': 'no-cache',
34
- 'priority': 'u=1, i',
35
- 'referer': 'https://www.tradingview.com/',
36
- 'sec-ch-ua': '"Not)A;Brand";v="99", "Google Chrome";v="127", "Chromium";v="127"',
37
- 'sec-ch-ua-mobile': '?0',
38
- 'sec-ch-ua-platform': '"Windows"',
39
- 'sec-fetch-dest': 'empty',
40
- 'sec-fetch-mode': 'cors',
41
- 'sec-fetch-site': 'same-site',
42
- 'user-agent': ('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ' +
43
- '(KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36'),
27
+ "accept": "application/json",
28
+ "accept-language": "en-US,en;q=0.9",
29
+ "cache-control": "no-cache",
30
+ "content-type": "text/plain;charset=UTF-8",
31
+ "origin": "https://www.tradingview.com",
32
+ "pragma": "no-cache",
33
+ "priority": "u=1, i",
34
+ "referer": "https://www.tradingview.com/",
35
+ "sec-ch-ua": '"Not)A;Brand";v="99", "Google Chrome";v="127", "Chromium";v="127"',
36
+ "sec-ch-ua-mobile": "?0",
37
+ "sec-ch-ua-platform": '"Windows"',
38
+ "sec-fetch-dest": "empty",
39
+ "sec-fetch-mode": "cors",
40
+ "sec-fetch-site": "same-site",
41
+ "user-agent": (
42
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
43
+ + "(KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36"
44
+ ),
44
45
  }
45
46
 
46
47
  params = {
47
- 'label-product': 'screener-coin',
48
+ "label-product": "screener-coin",
48
49
  }
49
50
 
50
51
  data = {
@@ -83,19 +84,14 @@ class TradingViewClient:
83
84
  "Volatility.D",
84
85
  ],
85
86
  "ignore_unknown_fields": False,
86
- "options": {
87
- "lang": "en"
88
- },
89
- "range":[
87
+ "options": {"lang": "en"},
88
+ "range": [
90
89
  offset,
91
90
  offset + limit,
92
91
  ],
93
- "sort": {
94
- "sortBy": "crypto_total_rank",
95
- "sortOrder": "asc"
96
- },
92
+ "sort": {"sortBy": "crypto_total_rank", "sortOrder": "asc"},
97
93
  "symbols": {},
98
- "markets": ["coin"]
94
+ "markets": ["coin"],
99
95
  }
100
96
 
101
97
  if coin_filter:
@@ -103,13 +99,13 @@ class TradingViewClient:
103
99
  {
104
100
  "left": "base_currency,base_currency_desc",
105
101
  "operation": "match",
106
- "right": f"{coin_filter}"
102
+ "right": f"{coin_filter}",
107
103
  }
108
104
  ]
109
105
  data = json.dumps(data)
110
106
  async with httpx.AsyncClient() as client:
111
107
  response = await client.post(
112
- 'https://scanner.tradingview.com/coin/scan',
108
+ "https://scanner.tradingview.com/coin/scan",
113
109
  params=params,
114
110
  cookies=cookies,
115
111
  headers=headers,
@@ -127,4 +123,4 @@ class TradingViewClient:
127
123
  continue
128
124
  all_infos.append(CoinScanInfo._parse(current_data.get("d", [])))
129
125
 
130
- return all_infos
126
+ return all_infos
@@ -1,10 +1,9 @@
1
1
  import json
2
2
  from typing import (
3
- Optional,
4
3
  Union,
5
4
  get_type_hints,
6
5
  Any,
7
- get_args as get_type_args
6
+ get_args as get_type_args,
8
7
  )
9
8
 
10
9
  from trd_utils.html_utils.html_formats import camel_to_snake
@@ -20,6 +19,7 @@ ULTRA_LIST_ENABLED: bool = False
20
19
  # attribute names are converted to snake_case.
21
20
  SET_CAMEL_ATTR_NAMES = False
22
21
 
22
+
23
23
  def get_my_field_types(cls):
24
24
  type_hints = {}
25
25
  for current_cls in cls.__class__.__mro__:
@@ -28,24 +28,31 @@ def get_my_field_types(cls):
28
28
  type_hints.update(get_type_hints(current_cls))
29
29
  return type_hints
30
30
 
31
+
31
32
  def get_real_attr(cls, attr_name):
32
33
  if cls is None:
33
34
  return None
34
-
35
+
35
36
  if isinstance(cls, dict):
36
37
  return cls.get(attr_name, None)
37
38
 
38
39
  if hasattr(cls, attr_name):
39
40
  return getattr(cls, attr_name)
40
-
41
+
41
42
  return None
42
43
 
44
+
45
+ def is_any_type(target_type: type) -> bool:
46
+ return target_type == Any or target_type is type(None)
47
+
48
+
43
49
  class UltraList(list):
44
50
  def __getattr__(self, attr):
45
51
  if len(self) == 0:
46
52
  return None
47
53
  return UltraList([get_real_attr(item, attr) for item in self])
48
54
 
55
+
49
56
  def convert_to_ultra_list(value: Any) -> UltraList:
50
57
  if not value:
51
58
  return UltraList()
@@ -62,7 +69,7 @@ def convert_to_ultra_list(value: Any) -> UltraList:
62
69
  return tuple(convert_to_ultra_list(v) for v in value)
63
70
  elif isinstance(value, set):
64
71
  return {convert_to_ultra_list(v) for v in value}
65
-
72
+
66
73
  for attr, attr_value in get_my_field_types(value).items():
67
74
  if isinstance(attr_value, list):
68
75
  setattr(value, attr, convert_to_ultra_list(getattr(value, attr)))
@@ -71,6 +78,7 @@ def convert_to_ultra_list(value: Any) -> UltraList:
71
78
  except Exception:
72
79
  return value
73
80
 
81
+
74
82
  class BaseModel:
75
83
  def __init__(self, **kwargs):
76
84
  annotations = get_my_field_types(self)
@@ -84,17 +92,19 @@ class BaseModel:
84
92
  # just ignore and continue
85
93
  annotations[key] = Any
86
94
  annotations[corrected_key] = Any
87
-
95
+
88
96
  expected_type = annotations[corrected_key]
89
97
  if hasattr(self, "_get_" + corrected_key + "_type"):
90
98
  try:
91
- overridden_type = getattr(self, "_get_" + corrected_key + "_type")(kwargs)
99
+ overridden_type = getattr(self, "_get_" + corrected_key + "_type")(
100
+ kwargs
101
+ )
92
102
  if overridden_type:
93
103
  expected_type = overridden_type
94
104
  except Exception:
95
105
  pass
96
-
97
- is_optional_type = getattr(expected_type, '_name', None) == 'Optional'
106
+
107
+ is_optional_type = getattr(expected_type, "_name", None) == "Optional"
98
108
  # maybe in the future we can have some other usages for is_optional_type
99
109
  # variable or something like that.
100
110
  if is_optional_type:
@@ -103,11 +113,11 @@ class BaseModel:
103
113
  except Exception:
104
114
  # something went wrong, just ignore and continue
105
115
  expected_type = Any
106
-
116
+
107
117
  # Handle nested models
108
118
  if isinstance(value, dict) and issubclass(expected_type, BaseModel):
109
119
  value = expected_type(**value)
110
-
120
+
111
121
  elif isinstance(value, list):
112
122
  type_args = get_type_args(expected_type)
113
123
  if not type_args:
@@ -119,27 +129,29 @@ class BaseModel:
119
129
  nested_type = type_args[0]
120
130
  if issubclass(nested_type, BaseModel):
121
131
  value = [nested_type(**item) for item in value]
122
-
132
+
123
133
  if ULTRA_LIST_ENABLED and isinstance(value, list):
124
134
  value = convert_to_ultra_list(value)
125
-
135
+
126
136
  # Type checking
127
- elif expected_type != Any and not isinstance(value, expected_type):
137
+ elif not (is_any_type(expected_type) or isinstance(value, expected_type)):
128
138
  try:
129
139
  value = expected_type(value)
130
140
  except Exception:
131
- raise TypeError(f"Field {corrected_key} must be of type {expected_type}," +
132
- f" but it's {type(value)}")
133
-
141
+ raise TypeError(
142
+ f"Field {corrected_key} must be of type {expected_type},"
143
+ + f" but it's {type(value)}"
144
+ )
145
+
134
146
  setattr(self, corrected_key, value)
135
147
  if SET_CAMEL_ATTR_NAMES and key != corrected_key:
136
148
  setattr(self, key, value)
137
-
149
+
138
150
  # Check if all required fields are present
139
151
  # for field in self.__annotations__:
140
152
  # if not hasattr(self, field):
141
153
  # raise ValueError(f"Missing required field: {field}")
142
-
154
+
143
155
  @classmethod
144
156
  def deserialize(cls, json_data: Union[str, dict]):
145
157
  if isinstance(json_data, str):
@@ -147,4 +159,3 @@ class BaseModel:
147
159
  else:
148
160
  data = json_data
149
161
  return cls(**data)
150
-
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2024 ALi.w
3
+ Copyright (c) 2024-2025 ALi.w
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -1,8 +1,7 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: trd_utils
3
- Version: 0.0.4
3
+ Version: 0.0.6
4
4
  Summary: Common Basic Utils for Python3. By ALiwoto.
5
- Home-page: https://github.com/ALiwoto/trd_utils
6
5
  Keywords: utils,trd_utils,basic-utils,common-utils
7
6
  Author: ALiwoto
8
7
  Author-email: aminnimaj@gmail.com
@@ -20,9 +19,20 @@ Classifier: Programming Language :: Python :: 3.12
20
19
  Classifier: Programming Language :: Python :: 3.13
21
20
  Requires-Dist: cryptography (>=41.0.7)
22
21
  Requires-Dist: httpx (>=0.21.0)
22
+ Project-URL: Homepage, https://github.com/ALiwoto/trd_utils
23
23
  Description-Content-Type: text/markdown
24
24
 
25
25
  # Trd Utils
26
26
 
27
27
  Basic common utils for Python.
28
28
 
29
+ ## How to run tests
30
+
31
+ Use this command first:
32
+
33
+ ```bash
34
+ pip install -e .
35
+ ```
36
+
37
+ Then run the tests in vscode.
38
+
@@ -0,0 +1,23 @@
1
+ trd_utils/__init__.py,sha256=BbjDqNvJRwxv1bgE42Omkkm7kDmKwwd0BpdQwilcoEE,24
2
+ trd_utils/cipher/__init__.py,sha256=V05KNuzQwCic-ihMVHlC8sENaJGc3I8MCb4pg4849X8,1765
3
+ trd_utils/common_utils/float_utils.py,sha256=W-jv7nzjl88xwGB6gsEXmDDhF6DseOrrVT2qx7OvyCo,266
4
+ trd_utils/exchanges/__init__.py,sha256=SQJt5cIXh305miWuDumkOLZHzqDUyOqSmlhTT9Xc9RY,180
5
+ trd_utils/exchanges/blofin/__init__.py,sha256=dQkY9aSbI5fZJDOSbkrbrbpHSbWbJjLEmjpkXxDMDD4,74
6
+ trd_utils/exchanges/blofin/blofin_client.py,sha256=IPmTTT437UdLW-FiGwi6ZEEurjD_rc8VxvykNIEdQFs,7628
7
+ trd_utils/exchanges/blofin/blofin_types.py,sha256=QlhgpBvFr-RwZB8agGwQSmhegw3__0TAsBg6oEXScYM,3664
8
+ trd_utils/exchanges/bx_ultra/__init__.py,sha256=8Ssy-eOemQR32Nv1-FoPHm87nRqRO4Fm2PU5GHEFKfQ,80
9
+ trd_utils/exchanges/bx_ultra/bx_types.py,sha256=FiJish58XpF6_vDRWxvpBVlPwXjC7uzK5K7CaA6Y8AI,25217
10
+ trd_utils/exchanges/bx_ultra/bx_ultra_client.py,sha256=4Zsybe7lzB7qSvOByp8KdoVvFcOAnFuStJJQuGWT6po,21164
11
+ trd_utils/exchanges/bx_ultra/bx_utils.py,sha256=PwapomwDW33arVmKIDj6cL-aP0ptu4BYy_lOCqSAPOo,1392
12
+ trd_utils/exchanges/exchange_base.py,sha256=SQERi-z9nvDAzuM4oZ3_v-wYWiw_eZV75jmAL2E4MG4,1976
13
+ trd_utils/html_utils/__init__.py,sha256=1WWs8C7JszRjTkmzIRLHpxWECHur_DrulTPGIeX88oM,426
14
+ trd_utils/html_utils/html_formats.py,sha256=unKsvOiiDmYTTaM0DYZEUNLEUzWQKKrqASJXvY54kvU,2299
15
+ trd_utils/tradingview/__init__.py,sha256=H0QYb-O5qvy7qC3yswtlcSWLmeBnaS6oJ3JtjvmaV_Y,154
16
+ trd_utils/tradingview/tradingview_client.py,sha256=g_eWYaCRQAL8Kvd-r6AnAdbH7Jha6C_GAyCuxh-RQUU,3917
17
+ trd_utils/tradingview/tradingview_types.py,sha256=z21MXPVdWHAduEl3gSeMIRhxtBN9yK-jPYHfZSMIbSA,6144
18
+ trd_utils/types_helper/__init__.py,sha256=SB9_5bQkuxV059AKC4cXYwsLPT3lM4LOu2m8LpMhZlQ,60
19
+ trd_utils/types_helper/base_model.py,sha256=xclJlC8BxaWzJ06Gb88lEYOFHel_pFeRz1EF_tQU04k,5698
20
+ trd_utils-0.0.6.dist-info/LICENSE,sha256=J1EP2xt87RjjmsTV1jTjHDQMLIM9FjdwEftTpw8hyv4,1067
21
+ trd_utils-0.0.6.dist-info/METADATA,sha256=_9LU-hyREa10cefKN1qa4YqPMq_U68UxQYTCq-ZNcbI,1094
22
+ trd_utils-0.0.6.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
23
+ trd_utils-0.0.6.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.9.1
2
+ Generator: poetry-core 2.1.3
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,17 +0,0 @@
1
- trd_utils/__init__.py,sha256=ajz1GSNU9xYVrFEDSz6Xwg7amWQ_yvW75tQa1ZvRIWc,3
2
- trd_utils/bx_ultra/__init__.py,sha256=8Ssy-eOemQR32Nv1-FoPHm87nRqRO4Fm2PU5GHEFKfQ,80
3
- trd_utils/bx_ultra/bx_types.py,sha256=eaNIjGiWh9T9j9jfCdcni_vVTHS5AW1FyA3aeyCssqU,25187
4
- trd_utils/bx_ultra/bx_ultra_client.py,sha256=4fNtu0G04ZXqy2SHaYkwbwWDTb7M-InOggRN-a-bprs,20504
5
- trd_utils/bx_ultra/common_utils.py,sha256=p9u3D52jCa9DNJzo-oA1yK_lrdb7_ahkGHaTuiX5wGE,1656
6
- trd_utils/cipher/__init__.py,sha256=V05KNuzQwCic-ihMVHlC8sENaJGc3I8MCb4pg4849X8,1765
7
- trd_utils/html_utils/__init__.py,sha256=1WWs8C7JszRjTkmzIRLHpxWECHur_DrulTPGIeX88oM,426
8
- trd_utils/html_utils/html_formats.py,sha256=unKsvOiiDmYTTaM0DYZEUNLEUzWQKKrqASJXvY54kvU,2299
9
- trd_utils/tradingview/__init__.py,sha256=H0QYb-O5qvy7qC3yswtlcSWLmeBnaS6oJ3JtjvmaV_Y,154
10
- trd_utils/tradingview/tradingview_client.py,sha256=iiNSLSKr5PnDcGiVFn515gsnGtqm5Htu99IwShHr2wM,4007
11
- trd_utils/tradingview/tradingview_types.py,sha256=z21MXPVdWHAduEl3gSeMIRhxtBN9yK-jPYHfZSMIbSA,6144
12
- trd_utils/types_helper/__init__.py,sha256=SB9_5bQkuxV059AKC4cXYwsLPT3lM4LOu2m8LpMhZlQ,60
13
- trd_utils/types_helper/base_model.py,sha256=J3SdOB9UNgw0cI8gaG8zjnfqa1qiVmlpz4o3aBxHqas,5631
14
- trd_utils-0.0.4.dist-info/LICENSE,sha256=msfwzd8S06fL8ORRneycnonTKmyuXzfeBT0V2figir8,1062
15
- trd_utils-0.0.4.dist-info/METADATA,sha256=4RQOqx9CFCx1HzBbzBJsYyyRCn-EWCiupNs37429BPA,975
16
- trd_utils-0.0.4.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
17
- trd_utils-0.0.4.dist-info/RECORD,,
File without changes