trd-utils 0.0.6__py3-none-any.whl → 0.0.8__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 +1 -1
- trd_utils/exchanges/blofin/blofin_client.py +104 -4
- trd_utils/exchanges/blofin/blofin_types.py +5 -0
- trd_utils/types_helper/__init__.py +3 -1
- trd_utils/types_helper/base_model.py +129 -9
- {trd_utils-0.0.6.dist-info → trd_utils-0.0.8.dist-info}/METADATA +1 -1
- {trd_utils-0.0.6.dist-info → trd_utils-0.0.8.dist-info}/RECORD +9 -9
- {trd_utils-0.0.6.dist-info → trd_utils-0.0.8.dist-info}/LICENSE +0 -0
- {trd_utils-0.0.6.dist-info → trd_utils-0.0.8.dist-info}/WHEEL +0 -0
trd_utils/__init__.py
CHANGED
|
@@ -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,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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
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,10 +1,10 @@
|
|
|
1
|
-
trd_utils/__init__.py,sha256=
|
|
1
|
+
trd_utils/__init__.py,sha256=x8Q5rZnCFxZ07OYHc9lo38vz24idlsR1ABA1wjVy9wU,24
|
|
2
2
|
trd_utils/cipher/__init__.py,sha256=V05KNuzQwCic-ihMVHlC8sENaJGc3I8MCb4pg4849X8,1765
|
|
3
3
|
trd_utils/common_utils/float_utils.py,sha256=W-jv7nzjl88xwGB6gsEXmDDhF6DseOrrVT2qx7OvyCo,266
|
|
4
4
|
trd_utils/exchanges/__init__.py,sha256=SQJt5cIXh305miWuDumkOLZHzqDUyOqSmlhTT9Xc9RY,180
|
|
5
5
|
trd_utils/exchanges/blofin/__init__.py,sha256=dQkY9aSbI5fZJDOSbkrbrbpHSbWbJjLEmjpkXxDMDD4,74
|
|
6
|
-
trd_utils/exchanges/blofin/blofin_client.py,sha256=
|
|
7
|
-
trd_utils/exchanges/blofin/blofin_types.py,sha256=
|
|
6
|
+
trd_utils/exchanges/blofin/blofin_client.py,sha256=SoA272BtqvG8FpxZYpwWx-flJxHEm1-qVMzklmDYZkQ,11611
|
|
7
|
+
trd_utils/exchanges/blofin/blofin_types.py,sha256=8uTJEMYL3Tq3Q_-yAZ4naGKQdQCgyjVn7bE1GRIr8kA,3845
|
|
8
8
|
trd_utils/exchanges/bx_ultra/__init__.py,sha256=8Ssy-eOemQR32Nv1-FoPHm87nRqRO4Fm2PU5GHEFKfQ,80
|
|
9
9
|
trd_utils/exchanges/bx_ultra/bx_types.py,sha256=FiJish58XpF6_vDRWxvpBVlPwXjC7uzK5K7CaA6Y8AI,25217
|
|
10
10
|
trd_utils/exchanges/bx_ultra/bx_ultra_client.py,sha256=4Zsybe7lzB7qSvOByp8KdoVvFcOAnFuStJJQuGWT6po,21164
|
|
@@ -15,9 +15,9 @@ trd_utils/html_utils/html_formats.py,sha256=unKsvOiiDmYTTaM0DYZEUNLEUzWQKKrqASJX
|
|
|
15
15
|
trd_utils/tradingview/__init__.py,sha256=H0QYb-O5qvy7qC3yswtlcSWLmeBnaS6oJ3JtjvmaV_Y,154
|
|
16
16
|
trd_utils/tradingview/tradingview_client.py,sha256=g_eWYaCRQAL8Kvd-r6AnAdbH7Jha6C_GAyCuxh-RQUU,3917
|
|
17
17
|
trd_utils/tradingview/tradingview_types.py,sha256=z21MXPVdWHAduEl3gSeMIRhxtBN9yK-jPYHfZSMIbSA,6144
|
|
18
|
-
trd_utils/types_helper/__init__.py,sha256=
|
|
19
|
-
trd_utils/types_helper/base_model.py,sha256=
|
|
20
|
-
trd_utils-0.0.
|
|
21
|
-
trd_utils-0.0.
|
|
22
|
-
trd_utils-0.0.
|
|
23
|
-
trd_utils-0.0.
|
|
18
|
+
trd_utils/types_helper/__init__.py,sha256=VlEXDzOyn6fYH-dE86EGJ6u_el08QvdyOtJkj-0EAVA,65
|
|
19
|
+
trd_utils/types_helper/base_model.py,sha256=diowqH_nh21HDUki_0nbrEtZd8IxGCpwrN8yCVN8QDE,9311
|
|
20
|
+
trd_utils-0.0.8.dist-info/LICENSE,sha256=J1EP2xt87RjjmsTV1jTjHDQMLIM9FjdwEftTpw8hyv4,1067
|
|
21
|
+
trd_utils-0.0.8.dist-info/METADATA,sha256=jssYMPDPHbev914kYnL9xsRRy3NDuP9Yry9yPLzhvgU,1094
|
|
22
|
+
trd_utils-0.0.8.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
23
|
+
trd_utils-0.0.8.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|