siglab-py 0.1.36__tar.gz → 0.1.38__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 siglab-py might be problematic. Click here for more details.

Files changed (38) hide show
  1. {siglab_py-0.1.36 → siglab_py-0.1.38}/PKG-INFO +1 -1
  2. {siglab_py-0.1.36 → siglab_py-0.1.38}/pyproject.toml +1 -1
  3. {siglab_py-0.1.36 → siglab_py-0.1.38}/setup.cfg +1 -1
  4. {siglab_py-0.1.36 → siglab_py-0.1.38}/siglab_py/market_data_providers/ccxt_candles_ta_to_csv.py +2 -1
  5. {siglab_py-0.1.36 → siglab_py-0.1.38}/siglab_py/ordergateway/client.py +158 -40
  6. {siglab_py-0.1.36 → siglab_py-0.1.38}/siglab_py/ordergateway/gateway.py +6 -1
  7. {siglab_py-0.1.36 → siglab_py-0.1.38}/siglab_py/util/market_data_util.py +1 -1
  8. {siglab_py-0.1.36 → siglab_py-0.1.38}/siglab_py.egg-info/PKG-INFO +1 -1
  9. {siglab_py-0.1.36 → siglab_py-0.1.38}/siglab_py/__init__.py +0 -0
  10. {siglab_py-0.1.36 → siglab_py-0.1.38}/siglab_py/constants.py +0 -0
  11. {siglab_py-0.1.36 → siglab_py-0.1.38}/siglab_py/exchanges/__init__.py +0 -0
  12. {siglab_py-0.1.36 → siglab_py-0.1.38}/siglab_py/exchanges/any_exchange.py +0 -0
  13. {siglab_py-0.1.36 → siglab_py-0.1.38}/siglab_py/exchanges/futubull.py +0 -0
  14. {siglab_py-0.1.36 → siglab_py-0.1.38}/siglab_py/market_data_providers/__init__.py +0 -0
  15. {siglab_py-0.1.36 → siglab_py-0.1.38}/siglab_py/market_data_providers/aggregated_orderbook_provider.py +0 -0
  16. {siglab_py-0.1.36 → siglab_py-0.1.38}/siglab_py/market_data_providers/candles_provider.py +0 -0
  17. {siglab_py-0.1.36 → siglab_py-0.1.38}/siglab_py/market_data_providers/candles_ta_provider.py +0 -0
  18. {siglab_py-0.1.36 → siglab_py-0.1.38}/siglab_py/market_data_providers/deribit_options_expiry_provider.py +0 -0
  19. {siglab_py-0.1.36 → siglab_py-0.1.38}/siglab_py/market_data_providers/futu_candles_ta_to_csv.py +0 -0
  20. {siglab_py-0.1.36 → siglab_py-0.1.38}/siglab_py/market_data_providers/orderbooks_provider.py +0 -0
  21. {siglab_py-0.1.36 → siglab_py-0.1.38}/siglab_py/market_data_providers/test_provider.py +0 -0
  22. {siglab_py-0.1.36 → siglab_py-0.1.38}/siglab_py/ordergateway/__init__.py +0 -0
  23. {siglab_py-0.1.36 → siglab_py-0.1.38}/siglab_py/ordergateway/encrypt_keys_util.py +0 -0
  24. {siglab_py-0.1.36 → siglab_py-0.1.38}/siglab_py/ordergateway/test_ordergateway.py +0 -0
  25. {siglab_py-0.1.36 → siglab_py-0.1.38}/siglab_py/tests/__init__.py +0 -0
  26. {siglab_py-0.1.36 → siglab_py-0.1.38}/siglab_py/tests/integration/__init__.py +0 -0
  27. {siglab_py-0.1.36 → siglab_py-0.1.38}/siglab_py/tests/integration/market_data_util_tests.py +0 -0
  28. {siglab_py-0.1.36 → siglab_py-0.1.38}/siglab_py/tests/unit/__init__.py +0 -0
  29. {siglab_py-0.1.36 → siglab_py-0.1.38}/siglab_py/tests/unit/analytic_util_tests.py +0 -0
  30. {siglab_py-0.1.36 → siglab_py-0.1.38}/siglab_py/tests/unit/market_data_util_tests.py +0 -0
  31. {siglab_py-0.1.36 → siglab_py-0.1.38}/siglab_py/util/__init__.py +0 -0
  32. {siglab_py-0.1.36 → siglab_py-0.1.38}/siglab_py/util/analytic_util.py +0 -0
  33. {siglab_py-0.1.36 → siglab_py-0.1.38}/siglab_py/util/aws_util.py +0 -0
  34. {siglab_py-0.1.36 → siglab_py-0.1.38}/siglab_py/util/retry_util.py +0 -0
  35. {siglab_py-0.1.36 → siglab_py-0.1.38}/siglab_py.egg-info/SOURCES.txt +0 -0
  36. {siglab_py-0.1.36 → siglab_py-0.1.38}/siglab_py.egg-info/dependency_links.txt +0 -0
  37. {siglab_py-0.1.36 → siglab_py-0.1.38}/siglab_py.egg-info/requires.txt +0 -0
  38. {siglab_py-0.1.36 → siglab_py-0.1.38}/siglab_py.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: siglab_py
3
- Version: 0.1.36
3
+ Version: 0.1.38
4
4
  Summary: Market data fetches, TA calculations and generic order gateway.
5
5
  Author: r0bbarh00d
6
6
  Author-email: r0bbarh00d <r0bbarh00d@gmail.com>
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "siglab_py"
7
- version = "0.1.36"
7
+ version = "0.1.38"
8
8
  description = "Market data fetches, TA calculations and generic order gateway."
9
9
  authors = [{name = "r0bbarh00d", email = "r0bbarh00d@gmail.com"}]
10
10
  license = {text = "MIT"}
@@ -1,6 +1,6 @@
1
1
  [metadata]
2
2
  name = siglab_py
3
- version = 0.1.36
3
+ version = 0.1.38
4
4
  description = Market data fetches, TA calculations and generic order gateway.
5
5
  author = r0bbarh00d
6
6
  author_email = r0bbarh00d@gmail.com
@@ -26,7 +26,7 @@ from siglab_py.util.analytic_util import compute_candles_stats
26
26
  '''
27
27
  Usage:
28
28
  set PYTHONPATH=%PYTHONPATH%;D:\dev\siglab\siglab_py
29
- python ccxt_candles_ta_to_csv.py --exchange_name bybit --symbol BTC/USDT:USDT --end_date "2025-03-11 0:0:0" --start_date "2021-03-11 0:0:0" --default_type linear --compute_ta Y --pypy_compatible N
29
+ python ccxt_candles_ta_to_csv.py --exchange_name bybit --symbol BTC/USDT:USDT --end_date "2025-03-25 0:0:0" --start_date "2021-03-11 0:0:0" --default_type linear --compute_ta Y --pypy_compatible N
30
30
 
31
31
  (Remember: python -mpip install siglab_py)
32
32
 
@@ -225,6 +225,7 @@ async def main():
225
225
  boillenger_std_multiples=param['boillenger_std_multiples'],
226
226
  sliding_window_how_many_candles=param['ma_long_intervals'],
227
227
  slow_fast_interval_ratio=(param['ma_long_intervals']/param['ma_short_intervals']),
228
+ hurst_exp_window_how_many_candles=param['ma_short_intervals'],
228
229
  pypy_compat=param['pypy_compatible']
229
230
  )
230
231
  compute_candles_stats_elapsed_ms = int((time.time() - start) *1000)
@@ -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
- def get_filled_amount(self) -> float:
140
- # filled_amount is in base ccy
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
- 'coin': 'SUSHI',
154
- 'side': 'B',
155
- 'limitPx': '0.79252',
156
- 'sz': '0.0',
157
- 'oid': xxx,
158
- 'timestamp': xxx,
159
- 'origSz': '30.0'
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
- 'status': 'filled',
162
- 'statusTimestamp': xxx},
163
- 'id': 'xxx',
206
+ 'id': 'xxx',
164
207
  'clientOrderId': None,
165
208
  'timestamp': xxx,
166
- 'datetime': '2025-02-26T01:00:00.522Z',
209
+ 'datetime': 'xxx',
167
210
  'lastTradeTimestamp': None,
168
- 'lastUpdateTimestamp': xxx,
169
- 'symbol': 'SUSHI/USDC:USDC',
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': 'buy',
175
- 'price': 0.79252,
217
+ 'side': 'sell',
218
+ 'price': 84389.0,
176
219
  'triggerPrice': None,
177
- 'amount': 30.0,
178
- 'cost': 23.7756,
179
- 'average': None, <-- No average cost?
180
- 'filled': 30.0,
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
- 'trades': [],
185
- 'fees': [],
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
- total_amount : float = sum([ self.executions[order_id]['amount'] for order_id in self.executions ])
192
- average_cost = sum([ (self.executions[order_id]['average'] if 'average' in self.executions[order_id] and self.executions[order_id]['average'] else 0 if self.executions[order_id]['average'] else self.executions[order_id]['price']) * self.executions[order_id]['amount'] for order_id in self.executions ])
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 devided by multiplier, rounded_slice_amount_in_base_ccy in number of contracts actually (Not in base ccy).
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()
@@ -19,7 +19,7 @@ from siglab_py.exchanges.futubull import Futubull
19
19
 
20
20
  def timestamp_to_datetime_cols(pd_candles : pd.DataFrame):
21
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))
22
+ lambda x: int(x.timestamp()) if isinstance(x, pd.Timestamp) else (int(x) if len(str(int(x)))==13 else int(x*1000))
23
23
  )
24
24
  pd_candles['datetime'] = pd_candles['timestamp_ms'].apply(lambda x: datetime.fromtimestamp(int(x)))
25
25
  pd_candles['datetime'] = pd.to_datetime(pd_candles['datetime'])
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: siglab-py
3
- Version: 0.1.36
3
+ Version: 0.1.38
4
4
  Summary: Market data fetches, TA calculations and generic order gateway.
5
5
  Author: r0bbarh00d
6
6
  Author-email: r0bbarh00d <r0bbarh00d@gmail.com>