siglab-py 0.1.35__py3-none-any.whl → 0.1.37__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 siglab-py might be problematic. Click here for more details.
- siglab_py/ordergateway/client.py +158 -40
- siglab_py/ordergateway/gateway.py +6 -1
- siglab_py/util/market_data_util.py +3 -2
- {siglab_py-0.1.35.dist-info → siglab_py-0.1.37.dist-info}/METADATA +1 -1
- {siglab_py-0.1.35.dist-info → siglab_py-0.1.37.dist-info}/RECORD +7 -7
- {siglab_py-0.1.35.dist-info → siglab_py-0.1.37.dist-info}/WHEEL +0 -0
- {siglab_py-0.1.35.dist-info → siglab_py-0.1.37.dist-info}/top_level.txt +0 -0
siglab_py/ordergateway/client.py
CHANGED
|
@@ -46,6 +46,9 @@ class Order:
|
|
|
46
46
|
self.reduce_only = reduce_only
|
|
47
47
|
self.fees_ccy = fees_ccy
|
|
48
48
|
|
|
49
|
+
self.dispatched_amount : float = 0 # This is amount in base ccy, with rounding + multiplier applied (So for contracts with multiplier!=1, this is no longer amount in base ccy.)
|
|
50
|
+
self.dispatched_price : Union[None, float] = None
|
|
51
|
+
|
|
49
52
|
def to_dict(self) -> Dict[JSON_SERIALIZABLE_TYPES, JSON_SERIALIZABLE_TYPES]:
|
|
50
53
|
return {
|
|
51
54
|
"ticker" : self.ticker,
|
|
@@ -54,7 +57,9 @@ class Order:
|
|
|
54
57
|
"order_type" : self.order_type,
|
|
55
58
|
"leg_room_bps" : self.leg_room_bps,
|
|
56
59
|
"reduce_only" : self.reduce_only,
|
|
57
|
-
"fees_ccy" : self.fees_ccy
|
|
60
|
+
"fees_ccy" : self.fees_ccy,
|
|
61
|
+
"dispatched_amount" : self.dispatched_amount,
|
|
62
|
+
"dispatched_price" : self.dispatched_price
|
|
58
63
|
}
|
|
59
64
|
|
|
60
65
|
'''
|
|
@@ -86,6 +91,7 @@ class DivisiblePosition(Order):
|
|
|
86
91
|
self.fees : Union[float, None] = None
|
|
87
92
|
self.pos : Union[float, None] = None # in base ccy, after execution. (Not in USDT or quote ccy, Not in # contracts)
|
|
88
93
|
|
|
94
|
+
self.dispatched_slices : List[Order] = []
|
|
89
95
|
self.executions : Dict[str, Dict[str, Any]] = {}
|
|
90
96
|
|
|
91
97
|
def to_slices(self) -> List[Order]:
|
|
@@ -136,60 +142,172 @@ class DivisiblePosition(Order):
|
|
|
136
142
|
def get_executions(self) -> Dict[str, Dict[str, Any]]:
|
|
137
143
|
return self.executions
|
|
138
144
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
filled_amount = sum([ self.executions[order_id]['filled'] if 'filled' in self.executions[order_id] and self.executions[order_id]['filled'] else 0 * self.multiplier for order_id in self.executions ])
|
|
142
|
-
if self.side=='sell':
|
|
143
|
-
filled_amount = -1 * filled_amount
|
|
144
|
-
return filled_amount
|
|
145
|
-
|
|
146
|
-
def get_average_cost(self) -> float:
|
|
147
|
-
'''
|
|
148
|
-
Example hyperliquid don't have 'average' in response JSON:
|
|
145
|
+
'''
|
|
146
|
+
The purpose of patch_executions is to fix erroneous exchange response where filled, amount or average None when status already 'closed'.
|
|
149
147
|
|
|
148
|
+
Example hyperliquid don't have 'average' in response JSON:
|
|
150
149
|
{
|
|
151
150
|
'info': {
|
|
152
151
|
'order': {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
152
|
+
'coin': 'SUSHI',
|
|
153
|
+
'side': 'B',
|
|
154
|
+
'limitPx': '0.79252',
|
|
155
|
+
'sz': '0.0',
|
|
156
|
+
'oid': xxx,
|
|
157
|
+
'timestamp': xxx,
|
|
158
|
+
'origSz': '30.0'
|
|
159
|
+
},
|
|
160
|
+
'status': 'filled',
|
|
161
|
+
'statusTimestamp': xxx},
|
|
162
|
+
'id': 'xxx',
|
|
163
|
+
'clientOrderId': None,
|
|
164
|
+
'timestamp': xxx,
|
|
165
|
+
'datetime': 'xxx',
|
|
166
|
+
'lastTradeTimestamp': None,
|
|
167
|
+
'lastUpdateTimestamp': xxx,
|
|
168
|
+
'symbol': 'SUSHI/USDC:USDC',
|
|
169
|
+
'type': None,
|
|
170
|
+
'timeInForce': None,
|
|
171
|
+
'postOnly': None,
|
|
172
|
+
'reduceOnly': None,
|
|
173
|
+
'side': 'buy',
|
|
174
|
+
'price': 0.79252,
|
|
175
|
+
'triggerPrice': None,
|
|
176
|
+
'amount': 30.0,
|
|
177
|
+
'cost': 23.7756,
|
|
178
|
+
'average': None, <-- No average cost?
|
|
179
|
+
'filled': 30.0,
|
|
180
|
+
'remaining': 0.0,
|
|
181
|
+
'status': 'closed',
|
|
182
|
+
'fee': None,
|
|
183
|
+
'trades': [],
|
|
184
|
+
'fees': [],
|
|
185
|
+
'stopPrice': None,
|
|
186
|
+
'takeProfitPrice': None,
|
|
187
|
+
'stopLossPrice': None
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
A second example, sometimes even 'amount', 'filled' and 'average' both None, but status is 'filled':
|
|
191
|
+
[
|
|
192
|
+
{
|
|
193
|
+
'info': {
|
|
194
|
+
'order': {
|
|
195
|
+
'coin': 'BTC',
|
|
196
|
+
'side': 'A',
|
|
197
|
+
'limitPx': '84389.0',
|
|
198
|
+
'sz': '0.0',
|
|
199
|
+
'oid': xxx,
|
|
200
|
+
'timestamp': xxx,
|
|
201
|
+
'origSz': '0.01184'
|
|
202
|
+
},
|
|
203
|
+
'status': 'filled',
|
|
204
|
+
'statusTimestamp': xxx
|
|
160
205
|
},
|
|
161
|
-
'
|
|
162
|
-
'statusTimestamp': xxx},
|
|
163
|
-
'id': 'xxx',
|
|
206
|
+
'id': 'xxx',
|
|
164
207
|
'clientOrderId': None,
|
|
165
208
|
'timestamp': xxx,
|
|
166
|
-
'datetime': '
|
|
209
|
+
'datetime': 'xxx',
|
|
167
210
|
'lastTradeTimestamp': None,
|
|
168
|
-
'lastUpdateTimestamp':
|
|
169
|
-
'symbol': '
|
|
211
|
+
'lastUpdateTimestamp': None,
|
|
212
|
+
'symbol': 'BTC/USDC:USDC',
|
|
170
213
|
'type': None,
|
|
171
|
-
'timeInForce': None,
|
|
214
|
+
'timeInForce': None,
|
|
172
215
|
'postOnly': None,
|
|
173
216
|
'reduceOnly': None,
|
|
174
|
-
'side': '
|
|
175
|
-
'price': 0
|
|
217
|
+
'side': 'sell',
|
|
218
|
+
'price': 84389.0,
|
|
176
219
|
'triggerPrice': None,
|
|
177
|
-
'amount':
|
|
178
|
-
'cost':
|
|
179
|
-
'average': None, <-- No average
|
|
180
|
-
'filled':
|
|
220
|
+
'amount': None, <-- No amount!?!
|
|
221
|
+
'cost': None,
|
|
222
|
+
'average': None, <-- No average!?!
|
|
223
|
+
'filled': None, <-- No filled!?!
|
|
181
224
|
'remaining': 0.0,
|
|
182
225
|
'status': 'closed',
|
|
183
|
-
'fee': None,
|
|
184
|
-
'
|
|
185
|
-
|
|
186
|
-
'stopPrice': None,
|
|
187
|
-
'takeProfitPrice': None,
|
|
188
|
-
'stopLossPrice': None
|
|
189
|
-
}
|
|
226
|
+
'fee': None, 'trades': [], 'fees':
|
|
227
|
+
[], 'stopPrice': None, 'takeProfitPrice': None, 'stopLossPrice': None}
|
|
228
|
+
]
|
|
190
229
|
'''
|
|
191
|
-
|
|
192
|
-
|
|
230
|
+
def patch_executions(self):
|
|
231
|
+
i : int = 0
|
|
232
|
+
for order_id in self.executions:
|
|
233
|
+
execution = self.executions[order_id]
|
|
234
|
+
execution['patch'] = {}
|
|
235
|
+
execution['patch']['average'] = None
|
|
236
|
+
execution['patch']['filled'] = 0
|
|
237
|
+
execution['patch']['amount'] = 0
|
|
238
|
+
|
|
239
|
+
corresponding_slice = self.dispatched_slices[i]
|
|
240
|
+
|
|
241
|
+
status = execution['status']
|
|
242
|
+
if status and status.strip().lower()=='closed':
|
|
243
|
+
if 'average' in execution:
|
|
244
|
+
if not execution['average']:
|
|
245
|
+
if 'price' in execution and execution['price']:
|
|
246
|
+
execution['patch']['average'] = execution['price']
|
|
247
|
+
else:
|
|
248
|
+
execution['patch']['average'] = corresponding_slice.dispatched_price
|
|
249
|
+
else:
|
|
250
|
+
execution['patch']['average'] = execution['average']
|
|
251
|
+
|
|
252
|
+
if 'filled' in execution:
|
|
253
|
+
if not execution['filled']:
|
|
254
|
+
execution['patch']['filled'] = corresponding_slice.dispatched_amount
|
|
255
|
+
else:
|
|
256
|
+
execution['patch']['filled'] = execution['filled']
|
|
257
|
+
|
|
258
|
+
if 'amount' in execution:
|
|
259
|
+
if not execution['amount']:
|
|
260
|
+
execution['patch']['amount'] = corresponding_slice.dispatched_amount
|
|
261
|
+
else:
|
|
262
|
+
execution['patch']['amount'] = execution['amount']
|
|
263
|
+
|
|
264
|
+
i += 1
|
|
265
|
+
|
|
266
|
+
def get_filled_amount(self) -> float:
|
|
267
|
+
# filled_amount is in base ccy
|
|
268
|
+
filled_amount = sum(
|
|
269
|
+
[
|
|
270
|
+
(
|
|
271
|
+
self.executions[order_id]['filled']
|
|
272
|
+
if 'filled' in self.executions[order_id] and self.executions[order_id]['filled']
|
|
273
|
+
else self.executions[order_id]['patch']['dispatched_amount']
|
|
274
|
+
) * self.multiplier
|
|
275
|
+
for order_id in self.executions
|
|
276
|
+
]
|
|
277
|
+
)
|
|
278
|
+
if self.side=='sell':
|
|
279
|
+
filled_amount = -1 * filled_amount
|
|
280
|
+
return filled_amount
|
|
281
|
+
|
|
282
|
+
def get_average_cost(self) -> float:
|
|
283
|
+
total_amount : float = sum(
|
|
284
|
+
[
|
|
285
|
+
self.executions[order_id]['amount']
|
|
286
|
+
if 'amount' in self.executions[order_id] and self.executions[order_id]['amount']
|
|
287
|
+
else self.executions[order_id]['patch']['dispatched_amount']
|
|
288
|
+
for order_id in self.executions
|
|
289
|
+
]
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
average_cost = sum(
|
|
293
|
+
[
|
|
294
|
+
(
|
|
295
|
+
self.executions[order_id]['average']
|
|
296
|
+
if 'average' in self.executions[order_id] and self.executions[order_id]['average']
|
|
297
|
+
else (
|
|
298
|
+
self.executions[order_id]['price']
|
|
299
|
+
if self.executions[order_id]['price']
|
|
300
|
+
else self.executions[order_id]['patch']['dispatched_price']
|
|
301
|
+
)
|
|
302
|
+
) *
|
|
303
|
+
(
|
|
304
|
+
self.executions[order_id]['amount']
|
|
305
|
+
if 'amount' in self.executions[order_id] and self.executions[order_id]['amount']
|
|
306
|
+
else self.executions[order_id]['patch']['dispatched_amount']
|
|
307
|
+
)
|
|
308
|
+
for order_id in self.executions
|
|
309
|
+
]
|
|
310
|
+
)
|
|
193
311
|
average_cost = average_cost / total_amount if total_amount!=0 else 0
|
|
194
312
|
return average_cost
|
|
195
313
|
|
|
@@ -461,7 +461,7 @@ async def execute_one_position(
|
|
|
461
461
|
dt_now : datetime = datetime.now()
|
|
462
462
|
|
|
463
463
|
slice_amount_in_base_ccy : float = slice.amount
|
|
464
|
-
rounded_slice_amount_in_base_ccy = slice_amount_in_base_ccy / multiplier # After
|
|
464
|
+
rounded_slice_amount_in_base_ccy = slice_amount_in_base_ccy / multiplier # After divided by multiplier, rounded_slice_amount_in_base_ccy in number of contracts actually (Not in base ccy).
|
|
465
465
|
rounded_slice_amount_in_base_ccy = exchange.amount_to_precision(position.ticker, rounded_slice_amount_in_base_ccy) # type: ignore
|
|
466
466
|
rounded_slice_amount_in_base_ccy = float(rounded_slice_amount_in_base_ccy) if rounded_slice_amount_in_base_ccy else 0
|
|
467
467
|
rounded_slice_amount_in_base_ccy = rounded_slice_amount_in_base_ccy if rounded_slice_amount_in_base_ccy>min_amount else min_amount
|
|
@@ -660,6 +660,10 @@ async def execute_one_position(
|
|
|
660
660
|
|
|
661
661
|
log(f"Resent market order{order_id} filled. status: {order_status}, filled_amount: {filled_amount}, remaining_amount: {remaining_amount}")
|
|
662
662
|
|
|
663
|
+
slice.dispatched_price = rounded_limit_price
|
|
664
|
+
slice.dispatched_amount = rounded_slice_amount_in_base_ccy
|
|
665
|
+
position.dispatched_slices.append(slice)
|
|
666
|
+
|
|
663
667
|
log(f"Executed slice #{i}", log_level=LogLevel.INFO)
|
|
664
668
|
log(f"{position.ticker}, multiplier: {multiplier}, slice_amount_in_base_ccy: {slice_amount_in_base_ccy}, rounded_slice_amount_in_base_ccy, {rounded_slice_amount_in_base_ccy}", log_level=LogLevel.INFO)
|
|
665
669
|
if position.order_type=='limit':
|
|
@@ -673,6 +677,7 @@ async def execute_one_position(
|
|
|
673
677
|
finally:
|
|
674
678
|
i += 1
|
|
675
679
|
|
|
680
|
+
position.patch_executions()
|
|
676
681
|
position.filled_amount = position.get_filled_amount()
|
|
677
682
|
position.average_cost = position.get_average_cost()
|
|
678
683
|
position.fees = position.get_fees()
|
|
@@ -18,9 +18,10 @@ import yfinance as yf
|
|
|
18
18
|
from siglab_py.exchanges.futubull import Futubull
|
|
19
19
|
|
|
20
20
|
def timestamp_to_datetime_cols(pd_candles : pd.DataFrame):
|
|
21
|
-
pd_candles['
|
|
22
|
-
lambda x:
|
|
21
|
+
pd_candles['timestamp_ms'] = pd_candles['timestamp_ms'].apply(
|
|
22
|
+
lambda x: int(x.timestamp()) if isinstance(x, pd.Timestamp) else (int(x / 1000) if len(str(int(x)))==13 else int(x))
|
|
23
23
|
)
|
|
24
|
+
pd_candles['datetime'] = pd_candles['timestamp_ms'].apply(lambda x: datetime.fromtimestamp(int(x)))
|
|
24
25
|
pd_candles['datetime'] = pd.to_datetime(pd_candles['datetime'])
|
|
25
26
|
pd_candles['datetime'] = pd_candles['datetime'].dt.tz_localize(None)
|
|
26
27
|
pd_candles['datetime_utc'] = pd_candles['timestamp_ms'].apply(
|
|
@@ -13,9 +13,9 @@ siglab_py/market_data_providers/futu_candles_ta_to_csv.py,sha256=S4GXaJ7AveEh-Cm
|
|
|
13
13
|
siglab_py/market_data_providers/orderbooks_provider.py,sha256=olt-3LIkoyzQWfNNQRhJtKibLbkTutt_q_rCCTM7i1g,16216
|
|
14
14
|
siglab_py/market_data_providers/test_provider.py,sha256=wBLCgcWjs7FGZJXWsNyn30lkOLa_cgpuvqRakMC0wbA,2221
|
|
15
15
|
siglab_py/ordergateway/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
-
siglab_py/ordergateway/client.py,sha256=
|
|
16
|
+
siglab_py/ordergateway/client.py,sha256=gtUyR41xXuAcBNoVCSlcg99koiQphe8gUBxlEgvWYt8,14637
|
|
17
17
|
siglab_py/ordergateway/encrypt_keys_util.py,sha256=-qi87db8To8Yf1WS1Q_Cp2Ya7ZqgWlRqSHfNXCM7wE4,1339
|
|
18
|
-
siglab_py/ordergateway/gateway.py,sha256=
|
|
18
|
+
siglab_py/ordergateway/gateway.py,sha256=VI-OIc3enhQ78cFNzAbLgX0Pzyjr2tl9VDV3IZTAtMQ,37438
|
|
19
19
|
siglab_py/ordergateway/test_ordergateway.py,sha256=_Gz2U_VqljogGWqGyNDYYls1INqUiig9veyPttfGRpg,3901
|
|
20
20
|
siglab_py/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
21
21
|
siglab_py/tests/integration/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -26,9 +26,9 @@ siglab_py/tests/unit/market_data_util_tests.py,sha256=A1y83itISmMJdn6wLpfwcr4tGo
|
|
|
26
26
|
siglab_py/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
27
27
|
siglab_py/util/analytic_util.py,sha256=xo9gD1ELQt_1v84yu9d4NgxtOIXthePZGipvDrjZAQ8,43834
|
|
28
28
|
siglab_py/util/aws_util.py,sha256=KGmjHrr1rpnnxr33nXHNzTul4tvyyxl9p6gpwNv0Ygc,2557
|
|
29
|
-
siglab_py/util/market_data_util.py,sha256=
|
|
29
|
+
siglab_py/util/market_data_util.py,sha256=E8jCOU67tyZu6GKXCnGRJjq777n3co3-xCZkMDh5uHs,19443
|
|
30
30
|
siglab_py/util/retry_util.py,sha256=mxYuRFZRZoaQQjENcwPmxhxixtd1TFvbxIdPx4RwfRc,743
|
|
31
|
-
siglab_py-0.1.
|
|
32
|
-
siglab_py-0.1.
|
|
33
|
-
siglab_py-0.1.
|
|
34
|
-
siglab_py-0.1.
|
|
31
|
+
siglab_py-0.1.37.dist-info/METADATA,sha256=FjYav-AWsN8fa7Q3HLLTbkqt0MTAGFzngS9lsQsesZ4,980
|
|
32
|
+
siglab_py-0.1.37.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
|
|
33
|
+
siglab_py-0.1.37.dist-info/top_level.txt,sha256=AbD4VR9OqmMOGlMJLkAVPGQMtUPIQv0t1BF5xmcLJSk,10
|
|
34
|
+
siglab_py-0.1.37.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|