trd-utils 0.0.6__tar.gz → 0.0.8__tar.gz

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.

Files changed (24) hide show
  1. {trd_utils-0.0.6 → trd_utils-0.0.8}/PKG-INFO +1 -1
  2. {trd_utils-0.0.6 → trd_utils-0.0.8}/pyproject.toml +1 -1
  3. trd_utils-0.0.8/trd_utils/__init__.py +3 -0
  4. {trd_utils-0.0.6 → trd_utils-0.0.8}/trd_utils/exchanges/blofin/blofin_client.py +104 -4
  5. {trd_utils-0.0.6 → trd_utils-0.0.8}/trd_utils/exchanges/blofin/blofin_types.py +5 -0
  6. {trd_utils-0.0.6 → trd_utils-0.0.8}/trd_utils/types_helper/__init__.py +3 -1
  7. {trd_utils-0.0.6 → trd_utils-0.0.8}/trd_utils/types_helper/base_model.py +129 -9
  8. trd_utils-0.0.6/trd_utils/__init__.py +0 -3
  9. {trd_utils-0.0.6 → trd_utils-0.0.8}/LICENSE +0 -0
  10. {trd_utils-0.0.6 → trd_utils-0.0.8}/README.md +0 -0
  11. {trd_utils-0.0.6 → trd_utils-0.0.8}/trd_utils/cipher/__init__.py +0 -0
  12. {trd_utils-0.0.6 → trd_utils-0.0.8}/trd_utils/common_utils/float_utils.py +0 -0
  13. {trd_utils-0.0.6 → trd_utils-0.0.8}/trd_utils/exchanges/__init__.py +0 -0
  14. {trd_utils-0.0.6 → trd_utils-0.0.8}/trd_utils/exchanges/blofin/__init__.py +0 -0
  15. {trd_utils-0.0.6 → trd_utils-0.0.8}/trd_utils/exchanges/bx_ultra/__init__.py +0 -0
  16. {trd_utils-0.0.6 → trd_utils-0.0.8}/trd_utils/exchanges/bx_ultra/bx_types.py +0 -0
  17. {trd_utils-0.0.6 → trd_utils-0.0.8}/trd_utils/exchanges/bx_ultra/bx_ultra_client.py +0 -0
  18. {trd_utils-0.0.6 → trd_utils-0.0.8}/trd_utils/exchanges/bx_ultra/bx_utils.py +0 -0
  19. {trd_utils-0.0.6 → trd_utils-0.0.8}/trd_utils/exchanges/exchange_base.py +0 -0
  20. {trd_utils-0.0.6 → trd_utils-0.0.8}/trd_utils/html_utils/__init__.py +0 -0
  21. {trd_utils-0.0.6 → trd_utils-0.0.8}/trd_utils/html_utils/html_formats.py +0 -0
  22. {trd_utils-0.0.6 → trd_utils-0.0.8}/trd_utils/tradingview/__init__.py +0 -0
  23. {trd_utils-0.0.6 → trd_utils-0.0.8}/trd_utils/tradingview/tradingview_client.py +0 -0
  24. {trd_utils-0.0.6 → trd_utils-0.0.8}/trd_utils/tradingview/tradingview_types.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: trd_utils
3
- Version: 0.0.6
3
+ Version: 0.0.8
4
4
  Summary: Common Basic Utils for Python3. By ALiwoto.
5
5
  Keywords: utils,trd_utils,basic-utils,common-utils
6
6
  Author: ALiwoto
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "trd_utils"
3
- version = "0.0.6"
3
+ version = "0.0.8"
4
4
  description = "Common Basic Utils for Python3. By ALiwoto."
5
5
  authors = ["ALiwoto <aminnimaj@gmail.com>"]
6
6
  packages = [
@@ -0,0 +1,3 @@
1
+
2
+ __version__ = "0.0.8"
3
+
@@ -1,3 +1,4 @@
1
+ import asyncio
1
2
  from decimal import Decimal
2
3
  import json
3
4
  import logging
@@ -10,6 +11,8 @@ from pathlib import Path
10
11
  from trd_utils.exchanges.blofin.blofin_types import (
11
12
  BlofinApiResponse,
12
13
  CmsColorResponse,
14
+ CopyTraderAllOrderHistory,
15
+ CopyTraderAllOrderList,
13
16
  CopyTraderInfoResponse,
14
17
  CopyTraderOrderHistoryResponse,
15
18
  CopyTraderOrderListResponse,
@@ -89,9 +92,9 @@ class BlofinClient(ExchangeBase):
89
92
 
90
93
  async def get_copy_trader_order_list(
91
94
  self,
92
- from_param: int,
93
- limit_param: 0,
94
95
  uid: int,
96
+ from_param: int = 0,
97
+ limit_param: int = 20,
95
98
  ) -> CopyTraderOrderListResponse:
96
99
  payload = {
97
100
  "from": from_param,
@@ -106,11 +109,60 @@ class BlofinClient(ExchangeBase):
106
109
  model=CopyTraderOrderListResponse,
107
110
  )
108
111
 
112
+ async def get_copy_trader_all_order_list(
113
+ self,
114
+ uid: int,
115
+ from_param: int = 0,
116
+ chunk_limit: int = 20,
117
+ sleep_delay: int = 0.5,
118
+ ) -> CopyTraderAllOrderList:
119
+ if chunk_limit < 1:
120
+ raise ValueError("chunk_limit parameter has to be more than 1")
121
+
122
+ result = CopyTraderAllOrderList(
123
+ code=200,
124
+ data=[],
125
+ total_count=0,
126
+ )
127
+ current_id_from = from_param
128
+ while True:
129
+ total_ignored = 0
130
+ current_result = await self.get_copy_trader_order_list(
131
+ uid=uid,
132
+ from_param=current_id_from,
133
+ limit_param=chunk_limit,
134
+ )
135
+ if not current_result or not isinstance(current_result, CopyTraderOrderListResponse) or \
136
+ not current_result.data:
137
+ return result
138
+
139
+ if current_result.data[0].id == current_id_from:
140
+ if len(current_result.data) < 2:
141
+ return result
142
+ current_result.data = current_result.data[1:]
143
+ total_ignored += 1
144
+ elif current_id_from:
145
+ raise ValueError(
146
+ "Expected first array to have the same value as from_param: "
147
+ f"current_id_from: {current_id_from}; but was: {current_result.data[0].id}"
148
+ )
149
+
150
+ current_id_from = current_result.data[-1].id
151
+ result.data.extend(current_result.data)
152
+ result.total_count += len(current_result.data)
153
+ if len(current_result.data) < chunk_limit - total_ignored:
154
+ # the trader doesn't have any more open orders
155
+ return result
156
+ if result.total_count > len(current_result.data) and sleep_delay:
157
+ # we don't want to sleep after 1 request only
158
+ await asyncio.sleep(sleep_delay)
159
+
160
+
109
161
  async def get_copy_trader_order_history(
110
162
  self,
111
- from_param: int,
112
- limit_param: 0,
113
163
  uid: int,
164
+ from_param: int = 0,
165
+ limit_param: int = 20,
114
166
  ) -> CopyTraderOrderHistoryResponse:
115
167
  payload = {
116
168
  "from": from_param,
@@ -125,6 +177,54 @@ class BlofinClient(ExchangeBase):
125
177
  model=CopyTraderOrderHistoryResponse,
126
178
  )
127
179
 
180
+ async def get_copy_trader_all_order_history(
181
+ self,
182
+ uid: int,
183
+ from_param: int = 0,
184
+ chunk_limit: int = 20,
185
+ sleep_delay: int = 0.5,
186
+ ) -> CopyTraderAllOrderHistory:
187
+ if chunk_limit < 1:
188
+ raise ValueError("chunk_limit parameter has to be more than 1")
189
+
190
+ result = CopyTraderAllOrderHistory(
191
+ code=200,
192
+ data=[],
193
+ total_count=0,
194
+ )
195
+ current_id_from = from_param
196
+ while True:
197
+ total_ignored = 0
198
+ current_result = await self.get_copy_trader_order_history(
199
+ uid=uid,
200
+ from_param=current_id_from,
201
+ limit_param=chunk_limit,
202
+ )
203
+ if not current_result or not isinstance(current_result, CopyTraderOrderHistoryResponse) or \
204
+ not current_result.data:
205
+ return result
206
+
207
+ if current_result.data[0].id == current_id_from:
208
+ if len(current_result.data) < 2:
209
+ return result
210
+ current_result.data = current_result.data[1:]
211
+ total_ignored += 1
212
+ elif current_id_from:
213
+ raise ValueError(
214
+ "Expected first array to have the same value as from_param: "
215
+ f"current_id_from: {current_id_from}; but was: {current_result.data[0].id}"
216
+ )
217
+
218
+ current_id_from = current_result.data[-1].id
219
+ result.data.extend(current_result.data)
220
+ result.total_count += len(current_result.data)
221
+ if len(current_result.data) < chunk_limit - total_ignored:
222
+ # the trader doesn't have any more orders history
223
+ return result
224
+ if result.total_count > len(current_result.data) and sleep_delay:
225
+ # we don't want to sleep after 1 request only
226
+ await asyncio.sleep(sleep_delay)
227
+
128
228
  # endregion
129
229
  ###########################################################
130
230
  # region client helper methods
@@ -139,6 +139,11 @@ class CopyTraderSingleOrderInfo(BaseModel):
139
139
  class CopyTraderOrderListResponse(BlofinApiResponse):
140
140
  data: list[CopyTraderSingleOrderInfo] = None
141
141
 
142
+ class CopyTraderAllOrderList(CopyTraderOrderListResponse):
143
+ total_count: int = None
142
144
 
143
145
  class CopyTraderOrderHistoryResponse(BlofinApiResponse):
144
146
  data: list[CopyTraderSingleOrderInfo] = None
147
+
148
+ class CopyTraderAllOrderHistory(CopyTraderOrderHistoryResponse):
149
+ total_count: int = None
@@ -1,4 +1,6 @@
1
1
  from .base_model import BaseModel
2
2
 
3
3
 
4
- __all__ = ["BaseModel"]
4
+ __all__ = [
5
+ BaseModel,
6
+ ]
@@ -1,3 +1,4 @@
1
+ from decimal import Decimal
1
2
  import json
2
3
  from typing import (
3
4
  Union,
@@ -46,6 +47,85 @@ def is_any_type(target_type: type) -> bool:
46
47
  return target_type == Any or target_type is type(None)
47
48
 
48
49
 
50
+ # TODO: add support for max_depth for this...
51
+ def value_to_normal_obj(value):
52
+ """
53
+ Converts a custom value, to a corresponding "normal object" which can be used
54
+ in dict.
55
+ """
56
+ if isinstance(value, BaseModel):
57
+ return value.to_dict()
58
+
59
+ if isinstance(value, list):
60
+ results = []
61
+ for current in value:
62
+ results.append(value_to_normal_obj(current))
63
+ return results
64
+
65
+ if isinstance(value, (int, str)) or value is None:
66
+ return value
67
+
68
+ if isinstance(value, Decimal):
69
+ return str(value)
70
+
71
+ if isinstance(value, dict):
72
+ result = {}
73
+ for inner_key, inner_value in value.items():
74
+ result[inner_key] = value_to_normal_obj(inner_value)
75
+
76
+ return result
77
+
78
+ raise TypeError(f"unsupported type provided: {type(value)}")
79
+
80
+
81
+ def generic_obj_to_value(
82
+ expected_type: type,
83
+ expected_type_args: tuple[type],
84
+ value: Any,
85
+ ):
86
+ """
87
+ Converts a normal JSON-compatible "object" to a customized python value.
88
+ """
89
+ if not expected_type_args:
90
+ expected_type_args = get_type_args(expected_type)
91
+
92
+ if isinstance(value, list):
93
+ result = []
94
+ for current in value:
95
+ result.append(generic_obj_to_value(
96
+ expected_type=expected_type_args[0],
97
+ expected_type_args=expected_type_args[1:],
98
+ value=current,
99
+ ))
100
+ return result
101
+
102
+ expected_type_name = getattr(expected_type, "__name__", None)
103
+ if expected_type_name == "dict" and isinstance(value, dict):
104
+ result = {}
105
+ for inner_key, inner_value in value.items():
106
+ result[expected_type_args[0](inner_key)] = generic_obj_to_value(
107
+ expected_type=expected_type_args[1],
108
+ expected_type_args=expected_type_args[1:],
109
+ value=inner_value,
110
+ )
111
+ return result
112
+
113
+ if isinstance(value, dict) and issubclass(expected_type, BaseModel):
114
+ if len(expected_type_args) > 1:
115
+ raise ValueError(
116
+ "unsupported operation: at this time we cannot have"
117
+ " expected type args at all...",
118
+ )
119
+ return expected_type(**value)
120
+
121
+ if not expected_type_args:
122
+ if isinstance(value, expected_type):
123
+ return value
124
+ return expected_type(value)
125
+
126
+ raise TypeError(f"unsupported type: {type(value)}")
127
+
128
+
49
129
  class UltraList(list):
50
130
  def __getattr__(self, attr):
51
131
  if len(self) == 0:
@@ -104,31 +184,44 @@ class BaseModel:
104
184
  except Exception:
105
185
  pass
106
186
 
107
- is_optional_type = getattr(expected_type, "_name", None) == "Optional"
187
+ expected_type_args = get_type_args(expected_type)
188
+ expected_type_name = getattr(expected_type, "__name__", None)
189
+ is_optional_type = expected_type_name == "Optional"
190
+ is_dict_type = expected_type_name == "dict"
108
191
  # maybe in the future we can have some other usages for is_optional_type
109
192
  # variable or something like that.
110
193
  if is_optional_type:
111
194
  try:
112
- expected_type = get_type_args(expected_type)[0]
195
+ expected_type = expected_type_args[0]
113
196
  except Exception:
114
197
  # something went wrong, just ignore and continue
115
198
  expected_type = Any
116
199
 
200
+ if value is None:
201
+ # just skip...
202
+ pass
203
+ elif isinstance(value, dict) and is_dict_type:
204
+ value = generic_obj_to_value(
205
+ expected_type=expected_type,
206
+ expected_type_args=expected_type_args,
207
+ value=value,
208
+ )
209
+
117
210
  # Handle nested models
118
- if isinstance(value, dict) and issubclass(expected_type, BaseModel):
211
+ elif isinstance(value, dict) and issubclass(expected_type, BaseModel):
119
212
  value = expected_type(**value)
120
213
 
121
214
  elif isinstance(value, list):
122
- type_args = get_type_args(expected_type)
123
- if not type_args:
215
+ if not expected_type_args:
124
216
  # if it's Any, it means we shouldn't really care about the type
125
217
  if expected_type != Any:
126
218
  value = expected_type(value)
127
219
  else:
128
- # Handle list of nested models
129
- nested_type = type_args[0]
130
- if issubclass(nested_type, BaseModel):
131
- value = [nested_type(**item) for item in value]
220
+ value = generic_obj_to_value(
221
+ expected_type=expected_type,
222
+ expected_type_args=expected_type_args,
223
+ value=value,
224
+ )
132
225
 
133
226
  if ULTRA_LIST_ENABLED and isinstance(value, list):
134
227
  value = convert_to_ultra_list(value)
@@ -159,3 +252,30 @@ class BaseModel:
159
252
  else:
160
253
  data = json_data
161
254
  return cls(**data)
255
+
256
+ def serialize(
257
+ self,
258
+ separators=(",", ":"),
259
+ ensure_ascii=True,
260
+ sort_keys=True,
261
+ ) -> bytes:
262
+ return json.dumps(
263
+ obj=self.to_dict(),
264
+ ensure_ascii=ensure_ascii,
265
+ separators=separators,
266
+ sort_keys=sort_keys,
267
+ )
268
+
269
+ def to_dict(self) -> dict:
270
+ annotations = get_my_field_types(self)
271
+ result_dict = {}
272
+ for key, _ in annotations.items():
273
+ if not isinstance(key, str):
274
+ continue
275
+
276
+ if key.startswith("__"):
277
+ # ignore private attributes
278
+ continue
279
+
280
+ result_dict[key] = value_to_normal_obj(getattr(self, key))
281
+ return result_dict
@@ -1,3 +0,0 @@
1
-
2
- __version__ = "0.0.6"
3
-
File without changes
File without changes