siglab-py 0.1.19__py3-none-any.whl → 0.6.33__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.
- siglab_py/algo/__init__.py +0 -0
- siglab_py/algo/macdrsi_crosses_15m_tc_strategy.py +107 -0
- siglab_py/algo/strategy_base.py +122 -0
- siglab_py/algo/strategy_executor.py +1308 -0
- siglab_py/algo/tp_algo.py +529 -0
- siglab_py/backtests/__init__.py +0 -0
- siglab_py/backtests/backtest_core.py +2405 -0
- siglab_py/backtests/coinflip_15m_crypto.py +432 -0
- siglab_py/backtests/fibonacci_d_mv_crypto.py +541 -0
- siglab_py/backtests/macdrsi_crosses_15m_tc_crypto.py +473 -0
- siglab_py/constants.py +26 -1
- siglab_py/exchanges/binance.py +38 -0
- siglab_py/exchanges/deribit.py +83 -0
- siglab_py/exchanges/futubull.py +33 -3
- siglab_py/market_data_providers/candles_provider.py +11 -10
- siglab_py/market_data_providers/candles_ta_provider.py +5 -5
- siglab_py/market_data_providers/ccxt_candles_ta_to_csv.py +238 -0
- siglab_py/market_data_providers/futu_candles_ta_to_csv.py +224 -0
- siglab_py/market_data_providers/google_monitor.py +320 -0
- siglab_py/market_data_providers/orderbooks_provider.py +15 -12
- siglab_py/market_data_providers/tg_monitor.py +428 -0
- siglab_py/market_data_providers/{test_provider.py → trigger_provider.py} +9 -8
- siglab_py/ordergateway/client.py +172 -41
- siglab_py/ordergateway/encrypt_keys_util.py +1 -1
- siglab_py/ordergateway/gateway.py +456 -344
- siglab_py/ordergateway/test_ordergateway.py +8 -7
- siglab_py/tests/integration/market_data_util_tests.py +80 -6
- siglab_py/tests/unit/analytic_util_tests.py +67 -4
- siglab_py/tests/unit/market_data_util_tests.py +96 -0
- siglab_py/tests/unit/simple_math_tests.py +252 -0
- siglab_py/tests/unit/trading_util_tests.py +65 -0
- siglab_py/util/analytic_util.py +484 -66
- siglab_py/util/datetime_util.py +39 -0
- siglab_py/util/market_data_util.py +564 -74
- siglab_py/util/module_util.py +40 -0
- siglab_py/util/notification_util.py +78 -0
- siglab_py/util/retry_util.py +16 -3
- siglab_py/util/simple_math.py +262 -0
- siglab_py/util/slack_notification_util.py +59 -0
- siglab_py/util/trading_util.py +118 -0
- {siglab_py-0.1.19.dist-info → siglab_py-0.6.33.dist-info}/METADATA +5 -13
- siglab_py-0.6.33.dist-info/RECORD +56 -0
- {siglab_py-0.1.19.dist-info → siglab_py-0.6.33.dist-info}/WHEEL +1 -1
- siglab_py-0.1.19.dist-info/RECORD +0 -31
- {siglab_py-0.1.19.dist-info → siglab_py-0.6.33.dist-info}/top_level.txt +0 -0
siglab_py/ordergateway/client.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
from typing import List, Dict, Any, Union
|
|
3
3
|
import json
|
|
4
|
+
from datetime import datetime
|
|
4
5
|
import time
|
|
5
6
|
from redis import StrictRedis
|
|
6
7
|
|
|
@@ -46,6 +47,11 @@ class Order:
|
|
|
46
47
|
self.reduce_only = reduce_only
|
|
47
48
|
self.fees_ccy = fees_ccy
|
|
48
49
|
|
|
50
|
+
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.)
|
|
51
|
+
self.dispatched_price : Union[None, float] = None
|
|
52
|
+
|
|
53
|
+
self.timestamp_ms = int(datetime.now().timestamp() * 1000)
|
|
54
|
+
|
|
49
55
|
def to_dict(self) -> Dict[JSON_SERIALIZABLE_TYPES, JSON_SERIALIZABLE_TYPES]:
|
|
50
56
|
return {
|
|
51
57
|
"ticker" : self.ticker,
|
|
@@ -54,7 +60,10 @@ class Order:
|
|
|
54
60
|
"order_type" : self.order_type,
|
|
55
61
|
"leg_room_bps" : self.leg_room_bps,
|
|
56
62
|
"reduce_only" : self.reduce_only,
|
|
57
|
-
"fees_ccy" : self.fees_ccy
|
|
63
|
+
"fees_ccy" : self.fees_ccy,
|
|
64
|
+
"dispatched_amount" : self.dispatched_amount,
|
|
65
|
+
"dispatched_price" : self.dispatched_price,
|
|
66
|
+
"timestamp_ms" : self.timestamp_ms
|
|
58
67
|
}
|
|
59
68
|
|
|
60
69
|
'''
|
|
@@ -86,8 +95,14 @@ class DivisiblePosition(Order):
|
|
|
86
95
|
self.fees : Union[float, None] = None
|
|
87
96
|
self.pos : Union[float, None] = None # in base ccy, after execution. (Not in USDT or quote ccy, Not in # contracts)
|
|
88
97
|
|
|
98
|
+
self.done : bool = False
|
|
99
|
+
self.execution_err : str = ""
|
|
100
|
+
|
|
101
|
+
self.dispatched_slices : List[Order] = []
|
|
89
102
|
self.executions : Dict[str, Dict[str, Any]] = {}
|
|
90
103
|
|
|
104
|
+
self.timestamp_ms = int(datetime.now().timestamp() * 1000)
|
|
105
|
+
|
|
91
106
|
def to_slices(self) -> List[Order]:
|
|
92
107
|
slices : List[Order] = []
|
|
93
108
|
|
|
@@ -136,67 +151,179 @@ class DivisiblePosition(Order):
|
|
|
136
151
|
def get_executions(self) -> Dict[str, Dict[str, Any]]:
|
|
137
152
|
return self.executions
|
|
138
153
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
filled_amount = sum([ self.executions[order_id]['filled'] * 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:
|
|
154
|
+
'''
|
|
155
|
+
The purpose of patch_executions is to fix erroneous exchange response where filled, amount or average None when status already 'closed'.
|
|
149
156
|
|
|
157
|
+
Example hyperliquid don't have 'average' in response JSON:
|
|
150
158
|
{
|
|
151
159
|
'info': {
|
|
152
160
|
'order': {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
161
|
+
'coin': 'SUSHI',
|
|
162
|
+
'side': 'B',
|
|
163
|
+
'limitPx': '0.79252',
|
|
164
|
+
'sz': '0.0',
|
|
165
|
+
'oid': xxx,
|
|
166
|
+
'timestamp': xxx,
|
|
167
|
+
'origSz': '30.0'
|
|
168
|
+
},
|
|
169
|
+
'status': 'filled',
|
|
170
|
+
'statusTimestamp': xxx},
|
|
171
|
+
'id': 'xxx',
|
|
172
|
+
'clientOrderId': None,
|
|
173
|
+
'timestamp': xxx,
|
|
174
|
+
'datetime': 'xxx',
|
|
175
|
+
'lastTradeTimestamp': None,
|
|
176
|
+
'lastUpdateTimestamp': xxx,
|
|
177
|
+
'symbol': 'SUSHI/USDC:USDC',
|
|
178
|
+
'type': None,
|
|
179
|
+
'timeInForce': None,
|
|
180
|
+
'postOnly': None,
|
|
181
|
+
'reduceOnly': None,
|
|
182
|
+
'side': 'buy',
|
|
183
|
+
'price': 0.79252,
|
|
184
|
+
'triggerPrice': None,
|
|
185
|
+
'amount': 30.0,
|
|
186
|
+
'cost': 23.7756,
|
|
187
|
+
'average': None, <-- No average cost?
|
|
188
|
+
'filled': 30.0,
|
|
189
|
+
'remaining': 0.0,
|
|
190
|
+
'status': 'closed',
|
|
191
|
+
'fee': None,
|
|
192
|
+
'trades': [],
|
|
193
|
+
'fees': [],
|
|
194
|
+
'stopPrice': None,
|
|
195
|
+
'takeProfitPrice': None,
|
|
196
|
+
'stopLossPrice': None
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
A second example, sometimes even 'amount', 'filled' and 'average' both None, but status is 'filled':
|
|
200
|
+
[
|
|
201
|
+
{
|
|
202
|
+
'info': {
|
|
203
|
+
'order': {
|
|
204
|
+
'coin': 'BTC',
|
|
205
|
+
'side': 'A',
|
|
206
|
+
'limitPx': '84389.0',
|
|
207
|
+
'sz': '0.0',
|
|
208
|
+
'oid': xxx,
|
|
209
|
+
'timestamp': xxx,
|
|
210
|
+
'origSz': '0.01184'
|
|
211
|
+
},
|
|
212
|
+
'status': 'filled',
|
|
213
|
+
'statusTimestamp': xxx
|
|
160
214
|
},
|
|
161
|
-
'
|
|
162
|
-
'statusTimestamp': xxx},
|
|
163
|
-
'id': 'xxx',
|
|
215
|
+
'id': 'xxx',
|
|
164
216
|
'clientOrderId': None,
|
|
165
217
|
'timestamp': xxx,
|
|
166
|
-
'datetime': '
|
|
218
|
+
'datetime': 'xxx',
|
|
167
219
|
'lastTradeTimestamp': None,
|
|
168
|
-
'lastUpdateTimestamp':
|
|
169
|
-
'symbol': '
|
|
220
|
+
'lastUpdateTimestamp': None,
|
|
221
|
+
'symbol': 'BTC/USDC:USDC',
|
|
170
222
|
'type': None,
|
|
171
|
-
'timeInForce': None,
|
|
223
|
+
'timeInForce': None,
|
|
172
224
|
'postOnly': None,
|
|
173
225
|
'reduceOnly': None,
|
|
174
|
-
'side': '
|
|
175
|
-
'price': 0
|
|
226
|
+
'side': 'sell',
|
|
227
|
+
'price': 84389.0,
|
|
176
228
|
'triggerPrice': None,
|
|
177
|
-
'amount':
|
|
178
|
-
'cost':
|
|
179
|
-
'average': None, <-- No average
|
|
180
|
-
'filled':
|
|
229
|
+
'amount': None, <-- No amount!?!
|
|
230
|
+
'cost': None,
|
|
231
|
+
'average': None, <-- No average!?!
|
|
232
|
+
'filled': None, <-- No filled!?!
|
|
181
233
|
'remaining': 0.0,
|
|
182
234
|
'status': 'closed',
|
|
183
|
-
'fee': None,
|
|
184
|
-
'
|
|
185
|
-
|
|
186
|
-
'stopPrice': None,
|
|
187
|
-
'takeProfitPrice': None,
|
|
188
|
-
'stopLossPrice': None
|
|
189
|
-
}
|
|
235
|
+
'fee': None, 'trades': [], 'fees':
|
|
236
|
+
[], 'stopPrice': None, 'takeProfitPrice': None, 'stopLossPrice': None}
|
|
237
|
+
]
|
|
190
238
|
'''
|
|
191
|
-
|
|
192
|
-
|
|
239
|
+
def patch_executions(self):
|
|
240
|
+
i : int = 0
|
|
241
|
+
for order_id in self.executions:
|
|
242
|
+
execution = self.executions[order_id]
|
|
243
|
+
execution['patch'] = {}
|
|
244
|
+
execution['patch']['average'] = None
|
|
245
|
+
execution['patch']['filled'] = 0
|
|
246
|
+
execution['patch']['amount'] = 0
|
|
247
|
+
|
|
248
|
+
corresponding_slice = self.dispatched_slices[i]
|
|
249
|
+
|
|
250
|
+
status = execution['status']
|
|
251
|
+
if status and status.strip().lower()=='closed':
|
|
252
|
+
if 'average' in execution:
|
|
253
|
+
if not execution['average']:
|
|
254
|
+
if 'price' in execution and execution['price']:
|
|
255
|
+
execution['patch']['average'] = execution['price']
|
|
256
|
+
else:
|
|
257
|
+
execution['patch']['average'] = corresponding_slice.dispatched_price
|
|
258
|
+
else:
|
|
259
|
+
execution['patch']['average'] = execution['average']
|
|
260
|
+
|
|
261
|
+
if 'filled' in execution:
|
|
262
|
+
if not execution['filled']:
|
|
263
|
+
execution['patch']['filled'] = corresponding_slice.dispatched_amount
|
|
264
|
+
else:
|
|
265
|
+
execution['patch']['filled'] = execution['filled']
|
|
266
|
+
|
|
267
|
+
if 'amount' in execution:
|
|
268
|
+
if not execution['amount']:
|
|
269
|
+
execution['patch']['amount'] = corresponding_slice.dispatched_amount
|
|
270
|
+
else:
|
|
271
|
+
execution['patch']['amount'] = execution['amount']
|
|
272
|
+
|
|
273
|
+
i += 1
|
|
274
|
+
|
|
275
|
+
def get_filled_amount(self) -> float:
|
|
276
|
+
# filled_amount is in base ccy
|
|
277
|
+
filled_amount = sum(
|
|
278
|
+
[
|
|
279
|
+
(
|
|
280
|
+
self.executions[order_id]['filled']
|
|
281
|
+
if 'filled' in self.executions[order_id] and self.executions[order_id]['filled']
|
|
282
|
+
else self.executions[order_id]['patch']['dispatched_amount']
|
|
283
|
+
) * self.multiplier
|
|
284
|
+
for order_id in self.executions
|
|
285
|
+
]
|
|
286
|
+
)
|
|
287
|
+
if self.side=='sell':
|
|
288
|
+
filled_amount = -1 * filled_amount
|
|
289
|
+
return filled_amount
|
|
290
|
+
|
|
291
|
+
def get_average_cost(self) -> float:
|
|
292
|
+
total_amount : float = sum(
|
|
293
|
+
[
|
|
294
|
+
self.executions[order_id]['amount']
|
|
295
|
+
if 'amount' in self.executions[order_id] and self.executions[order_id]['amount']
|
|
296
|
+
else self.executions[order_id]['patch']['dispatched_amount']
|
|
297
|
+
for order_id in self.executions
|
|
298
|
+
]
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
average_cost = sum(
|
|
302
|
+
[
|
|
303
|
+
(
|
|
304
|
+
self.executions[order_id]['average']
|
|
305
|
+
if 'average' in self.executions[order_id] and self.executions[order_id]['average']
|
|
306
|
+
else (
|
|
307
|
+
self.executions[order_id]['price']
|
|
308
|
+
if self.executions[order_id]['price']
|
|
309
|
+
else self.executions[order_id]['patch']['dispatched_price']
|
|
310
|
+
)
|
|
311
|
+
) *
|
|
312
|
+
(
|
|
313
|
+
self.executions[order_id]['amount']
|
|
314
|
+
if 'amount' in self.executions[order_id] and self.executions[order_id]['amount']
|
|
315
|
+
else self.executions[order_id]['patch']['dispatched_amount']
|
|
316
|
+
)
|
|
317
|
+
for order_id in self.executions
|
|
318
|
+
]
|
|
319
|
+
)
|
|
193
320
|
average_cost = average_cost / total_amount if total_amount!=0 else 0
|
|
194
321
|
return average_cost
|
|
195
322
|
|
|
196
323
|
def get_fees(self) -> float:
|
|
197
324
|
fees : float = 0
|
|
198
325
|
if self.fees_ccy:
|
|
199
|
-
fees = sum([ float(self.executions[order_id]['fee']['cost']) for order_id in self.executions if self.executions[order_id]['fee'] and self.executions[order_id]['fee']['currency'].strip().upper()==self.fees_ccy.strip().upper() ])
|
|
326
|
+
fees = sum([ float(self.executions[order_id]['fee']['cost'] if self.executions[order_id]['fee'] and self.executions[order_id]['fee']['cost'] else 0) for order_id in self.executions if self.executions[order_id]['fee'] and self.executions[order_id]['fee']['currency'].strip().upper()==self.fees_ccy.strip().upper() ])
|
|
200
327
|
return fees
|
|
201
328
|
|
|
202
329
|
def to_dict(self) -> Dict[JSON_SERIALIZABLE_TYPES, JSON_SERIALIZABLE_TYPES]:
|
|
@@ -206,7 +333,11 @@ class DivisiblePosition(Order):
|
|
|
206
333
|
rv['executions'] = self.executions
|
|
207
334
|
rv['filled_amount'] = self.filled_amount
|
|
208
335
|
rv['average_cost'] = self.average_cost
|
|
336
|
+
rv['fees'] = self.fees
|
|
209
337
|
rv['pos'] = self.pos
|
|
338
|
+
rv['done'] = self.done
|
|
339
|
+
rv['execution_err'] = self.execution_err
|
|
340
|
+
rv['timestamp_ms'] = self.timestamp_ms
|
|
210
341
|
return rv
|
|
211
342
|
|
|
212
343
|
def execute_positions(
|