siglab-py 0.1.50__tar.gz → 0.1.52__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.50 → siglab_py-0.1.52}/PKG-INFO +1 -1
  2. {siglab_py-0.1.50 → siglab_py-0.1.52}/pyproject.toml +1 -1
  3. {siglab_py-0.1.50 → siglab_py-0.1.52}/setup.cfg +1 -1
  4. {siglab_py-0.1.50 → siglab_py-0.1.52}/siglab_py/market_data_providers/ccxt_candles_ta_to_csv.py +1 -2
  5. {siglab_py-0.1.50 → siglab_py-0.1.52}/siglab_py/ordergateway/client.py +6 -0
  6. {siglab_py-0.1.50 → siglab_py-0.1.52}/siglab_py/ordergateway/gateway.py +241 -235
  7. {siglab_py-0.1.50 → siglab_py-0.1.52}/siglab_py.egg-info/PKG-INFO +1 -1
  8. {siglab_py-0.1.50 → siglab_py-0.1.52}/siglab_py/__init__.py +0 -0
  9. {siglab_py-0.1.50 → siglab_py-0.1.52}/siglab_py/constants.py +0 -0
  10. {siglab_py-0.1.50 → siglab_py-0.1.52}/siglab_py/exchanges/__init__.py +0 -0
  11. {siglab_py-0.1.50 → siglab_py-0.1.52}/siglab_py/exchanges/any_exchange.py +0 -0
  12. {siglab_py-0.1.50 → siglab_py-0.1.52}/siglab_py/exchanges/futubull.py +0 -0
  13. {siglab_py-0.1.50 → siglab_py-0.1.52}/siglab_py/market_data_providers/__init__.py +0 -0
  14. {siglab_py-0.1.50 → siglab_py-0.1.52}/siglab_py/market_data_providers/aggregated_orderbook_provider.py +0 -0
  15. {siglab_py-0.1.50 → siglab_py-0.1.52}/siglab_py/market_data_providers/candles_provider.py +0 -0
  16. {siglab_py-0.1.50 → siglab_py-0.1.52}/siglab_py/market_data_providers/candles_ta_provider.py +0 -0
  17. {siglab_py-0.1.50 → siglab_py-0.1.52}/siglab_py/market_data_providers/deribit_options_expiry_provider.py +0 -0
  18. {siglab_py-0.1.50 → siglab_py-0.1.52}/siglab_py/market_data_providers/futu_candles_ta_to_csv.py +0 -0
  19. {siglab_py-0.1.50 → siglab_py-0.1.52}/siglab_py/market_data_providers/orderbooks_provider.py +0 -0
  20. {siglab_py-0.1.50 → siglab_py-0.1.52}/siglab_py/market_data_providers/test_provider.py +0 -0
  21. {siglab_py-0.1.50 → siglab_py-0.1.52}/siglab_py/ordergateway/__init__.py +0 -0
  22. {siglab_py-0.1.50 → siglab_py-0.1.52}/siglab_py/ordergateway/encrypt_keys_util.py +0 -0
  23. {siglab_py-0.1.50 → siglab_py-0.1.52}/siglab_py/ordergateway/test_ordergateway.py +0 -0
  24. {siglab_py-0.1.50 → siglab_py-0.1.52}/siglab_py/tests/__init__.py +0 -0
  25. {siglab_py-0.1.50 → siglab_py-0.1.52}/siglab_py/tests/integration/__init__.py +0 -0
  26. {siglab_py-0.1.50 → siglab_py-0.1.52}/siglab_py/tests/integration/market_data_util_tests.py +0 -0
  27. {siglab_py-0.1.50 → siglab_py-0.1.52}/siglab_py/tests/unit/__init__.py +0 -0
  28. {siglab_py-0.1.50 → siglab_py-0.1.52}/siglab_py/tests/unit/analytic_util_tests.py +0 -0
  29. {siglab_py-0.1.50 → siglab_py-0.1.52}/siglab_py/tests/unit/market_data_util_tests.py +0 -0
  30. {siglab_py-0.1.50 → siglab_py-0.1.52}/siglab_py/util/__init__.py +0 -0
  31. {siglab_py-0.1.50 → siglab_py-0.1.52}/siglab_py/util/analytic_util.py +0 -0
  32. {siglab_py-0.1.50 → siglab_py-0.1.52}/siglab_py/util/aws_util.py +0 -0
  33. {siglab_py-0.1.50 → siglab_py-0.1.52}/siglab_py/util/market_data_util.py +0 -0
  34. {siglab_py-0.1.50 → siglab_py-0.1.52}/siglab_py/util/retry_util.py +0 -0
  35. {siglab_py-0.1.50 → siglab_py-0.1.52}/siglab_py.egg-info/SOURCES.txt +0 -0
  36. {siglab_py-0.1.50 → siglab_py-0.1.52}/siglab_py.egg-info/dependency_links.txt +0 -0
  37. {siglab_py-0.1.50 → siglab_py-0.1.52}/siglab_py.egg-info/requires.txt +0 -0
  38. {siglab_py-0.1.50 → siglab_py-0.1.52}/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.50
3
+ Version: 0.1.52
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.50"
7
+ version = "0.1.52"
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.50
3
+ version = 0.1.52
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-25 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-11 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,7 +225,6 @@ 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'],
229
228
  pypy_compat=param['pypy_compatible']
230
229
  )
231
230
  compute_candles_stats_elapsed_ms = int((time.time() - start) *1000)
@@ -91,6 +91,9 @@ class DivisiblePosition(Order):
91
91
  self.fees : Union[float, None] = None
92
92
  self.pos : Union[float, None] = None # in base ccy, after execution. (Not in USDT or quote ccy, Not in # contracts)
93
93
 
94
+ self.done : bool = False
95
+ self.execution_err : str = ""
96
+
94
97
  self.dispatched_slices : List[Order] = []
95
98
  self.executions : Dict[str, Dict[str, Any]] = {}
96
99
 
@@ -324,7 +327,10 @@ class DivisiblePosition(Order):
324
327
  rv['executions'] = self.executions
325
328
  rv['filled_amount'] = self.filled_amount
326
329
  rv['average_cost'] = self.average_cost
330
+ rv['fees'] = self.fees
327
331
  rv['pos'] = self.pos
332
+ rv['done'] = self.done
333
+ rv['execution_err'] = self.execution_err
328
334
  return rv
329
335
 
330
336
  def execute_positions(
@@ -1,4 +1,3 @@
1
- from ctypes import ArgumentError
2
1
  import sys
3
2
  import traceback
4
3
  import os
@@ -392,7 +391,7 @@ async def instantiate_exchange(
392
391
  }
393
392
  ) # type: ignore
394
393
  else:
395
- raise ArgumentError(f"Unsupported exchange {exchange_name}, check gateway_id {gateway_id}.")
394
+ raise ValueError(f"Unsupported exchange {exchange_name}, check gateway_id {gateway_id}.")
396
395
 
397
396
  await exchange.load_markets() # type: ignore
398
397
 
@@ -446,251 +445,258 @@ async def execute_one_position(
446
445
  param : Dict,
447
446
  executions : Dict[str, Dict[str, Any]]
448
447
  ):
449
- market : Dict[str, Any] = exchange.markets[position.ticker] if position.ticker in exchange.markets else None # type: ignore
450
- if not market:
451
- raise ArgumentError(f"Market not found for {position.ticker} under {exchange.name}") # type: ignore
452
-
453
- min_amount = float(market['limits']['amount']['min']) if market['limits']['amount']['min'] else 0 # type: ignore
454
- multiplier = market['contractSize'] if 'contractSize' in market and market['contractSize'] else 1
455
- position.multiplier = multiplier
456
-
457
- slices : List[Order] = position.to_slices()
458
- i = 0
459
- for slice in slices:
460
- try:
461
- dt_now : datetime = datetime.now()
462
-
463
- slice_amount_in_base_ccy : float = slice.amount
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
- rounded_slice_amount_in_base_ccy = exchange.amount_to_precision(position.ticker, rounded_slice_amount_in_base_ccy) # type: ignore
466
- rounded_slice_amount_in_base_ccy = float(rounded_slice_amount_in_base_ccy) if rounded_slice_amount_in_base_ccy else 0
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
468
-
469
- if rounded_slice_amount_in_base_ccy==0:
470
- log(f"{position.ticker} Slice amount rounded to zero?! slice amount before rounding: {slice.amount}")
471
- continue
472
-
473
- orderbook = await exchange.fetch_order_book(symbol=position.ticker, limit=3) # type: ignore
474
- if position.side=='buy':
475
- asks = [ ask[0] for ask in orderbook['asks'] ]
476
- best_asks = min(asks)
477
- limit_price : float= best_asks * (1 + position.leg_room_bps/10000)
478
- else:
479
- bids = [ bid[0] for bid in orderbook['bids'] ]
480
- best_bid = max(bids)
481
- limit_price : float = best_bid * (1 - position.leg_room_bps/10000)
482
-
483
- rounded_limit_price : float = exchange.price_to_precision(position.ticker, limit_price) # type: ignore
484
- rounded_limit_price = float(rounded_limit_price)
485
-
486
- order_params = {
487
- 'reduceOnly': slice.reduce_only
488
- }
489
- if position.order_type=='limit':
490
- if not param['dry_run']:
491
- executed_order = await exchange.create_order( # type: ignore
492
- symbol = position.ticker,
493
- type = position.order_type,
494
- amount = rounded_slice_amount_in_base_ccy,
495
- price = rounded_limit_price,
496
- side = position.side,
497
- params = order_params
498
- )
499
- else:
500
- executed_order = DUMMY_EXECUTION.copy()
501
- executed_order['clientOrderId'] = str(uuid.uuid4())
502
- executed_order['timestamp'] = dt_now.timestamp()
503
- executed_order['datetime'] = dt_now
504
- executed_order['symbol'] = position.ticker
505
- executed_order['type'] = position.order_type
506
- executed_order['side'] = position.side
507
- executed_order['price'] = rounded_limit_price
508
- executed_order['average'] = rounded_limit_price
509
- executed_order['cost'] = 0
510
- executed_order['amount'] = rounded_slice_amount_in_base_ccy
511
- executed_order['filled'] = rounded_slice_amount_in_base_ccy
512
- executed_order['remaining'] = 0
513
- executed_order['status'] = 'closed'
514
- executed_order['multiplier'] = position.multiplier
515
-
516
- else:
517
- if not param['dry_run']:
518
- executed_order = await exchange.create_order( # type: ignore
519
- symbol = position.ticker,
520
- type = position.order_type,
521
- amount = rounded_slice_amount_in_base_ccy,
522
- side = position.side,
523
- params = order_params
524
- )
448
+ try:
449
+ market : Dict[str, Any] = exchange.markets[position.ticker] if position.ticker in exchange.markets else None # type: ignore
450
+ if not market:
451
+ raise ArgumentError(f"Market not found for {position.ticker} under {exchange.name}") # type: ignore
452
+
453
+ min_amount = float(market['limits']['amount']['min']) if market['limits']['amount']['min'] else 0 # type: ignore
454
+ multiplier = market['contractSize'] if 'contractSize' in market and market['contractSize'] else 1
455
+ position.multiplier = multiplier
456
+
457
+ slices : List[Order] = position.to_slices()
458
+ i = 0
459
+ for slice in slices:
460
+ try:
461
+ dt_now : datetime = datetime.now()
462
+
463
+ slice_amount_in_base_ccy : float = slice.amount
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
+ rounded_slice_amount_in_base_ccy = exchange.amount_to_precision(position.ticker, rounded_slice_amount_in_base_ccy) # type: ignore
466
+ rounded_slice_amount_in_base_ccy = float(rounded_slice_amount_in_base_ccy) if rounded_slice_amount_in_base_ccy else 0
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
468
+
469
+ if rounded_slice_amount_in_base_ccy==0:
470
+ log(f"{position.ticker} Slice amount rounded to zero?! slice amount before rounding: {slice.amount}")
471
+ continue
472
+
473
+ orderbook = await exchange.fetch_order_book(symbol=position.ticker, limit=3) # type: ignore
474
+ if position.side=='buy':
475
+ asks = [ ask[0] for ask in orderbook['asks'] ]
476
+ best_asks = min(asks)
477
+ limit_price : float= best_asks * (1 + position.leg_room_bps/10000)
525
478
  else:
526
- executed_order = DUMMY_EXECUTION.copy()
527
- executed_order['clientOrderId'] = str(uuid.uuid4())
528
- executed_order['timestamp'] = dt_now.timestamp()
529
- executed_order['datetime'] = dt_now
530
- executed_order['symbol'] = position.ticker
531
- executed_order['type'] = position.order_type
532
- executed_order['side'] = position.side
533
- executed_order['price'] = rounded_limit_price
534
- executed_order['average'] = rounded_limit_price
535
- executed_order['cost'] = 0
536
- executed_order['amount'] = rounded_slice_amount_in_base_ccy
537
- executed_order['filled'] = rounded_slice_amount_in_base_ccy
538
- executed_order['remaining'] = 0
539
- executed_order['status'] = 'closed'
540
- executed_order['multiplier'] = position.multiplier
541
-
542
- executed_order['slice_id'] = i
543
-
544
- '''
545
- Format of executed_order:
546
- executed_order
547
- {'info': {'clOrdId': 'xxx', 'ordId': '2245241151525347328', 'sCode': '0', 'sMsg': 'Order placed', 'tag': 'xxx', 'ts': '1739415800635'}, 'id': '2245241151525347328', 'clientOrderId': 'xxx', 'timestamp': None, 'datetime': None, 'lastTradeTimestamp': None, 'lastUpdateTimestamp': None, 'symbol': 'SUSHI/USDT:USDT', 'type': 'limit', 'timeInForce': None, 'postOnly': None, 'side': 'buy', 'price': None, 'stopLossPrice': None, 'takeProfitPrice': None, 'triggerPrice': None, 'average': None, 'cost': None, 'amount': None, 'filled': None, 'remaining': None, 'status': None, 'fee': None, 'trades': [], 'reduceOnly': False, 'fees': [], 'stopPrice': None}
548
- special variables:
549
- function variables:
550
- 'info': {'clOrdId': 'xxx', 'ordId': '2245241151525347328', 'sCode': '0', 'sMsg': 'Order placed', 'tag': 'xxx', 'ts': '1739415800635'}
551
- 'id': '2245241151525347328'
552
- 'clientOrderId': 'xxx'
553
- 'timestamp': None
554
- 'datetime': None
555
- 'lastTradeTimestamp': None
556
- 'lastUpdateTimestamp': None
557
- 'symbol': 'SUSHI/USDT:USDT'
558
- 'type': 'limit'
559
- 'timeInForce': None
560
- 'postOnly': None
561
- 'side': 'buy'
562
- 'price': None
563
- 'stopLossPrice': None
564
- 'takeProfitPrice': None
565
- 'triggerPrice': None
566
- 'average': None
567
- 'cost': None
568
- 'amount': None
569
- 'filled': None
570
- 'remaining': None
571
- 'status': None
572
- 'fee': None
573
- 'trades': []
574
- 'reduceOnly': False
575
- 'fees': []
576
- 'stopPrice': None
577
- '''
578
- order_id = executed_order['id']
579
- order_status = executed_order['status']
580
- filled_amount = executed_order['filled']
581
- remaining_amount = executed_order['remaining']
582
- executed_order['multiplier'] = multiplier
583
- position.append_execution(order_id, executed_order)
584
-
585
- log(f"Order dispatched: {order_id}. status: {order_status}, filled_amount: {filled_amount}, remaining_amount: {remaining_amount}")
586
-
587
- if not order_status or order_status!='closed':
588
- start_time = time.time()
589
- wait_threshold_sec = position.wait_fill_threshold_ms / 1000
590
-
591
- elapsed_sec = time.time() - start_time
592
- while elapsed_sec < wait_threshold_sec:
593
- order_update = None
594
- if order_id in executions:
595
- order_update = executions[order_id]
479
+ bids = [ bid[0] for bid in orderbook['bids'] ]
480
+ best_bid = max(bids)
481
+ limit_price : float = best_bid * (1 - position.leg_room_bps/10000)
596
482
 
597
- if order_update:
598
- order_status = order_update['status']
599
- filled_amount = order_update['filled']
600
- remaining_amount = order_update['remaining']
601
- order_update['multiplier'] = multiplier
602
- position.append_execution(order_id, order_update)
603
-
604
- if remaining_amount <= 0:
605
- log(f"Limit order fully filled: {order_id}", log_level=LogLevel.INFO)
606
- break
483
+ rounded_limit_price : float = exchange.price_to_precision(position.ticker, limit_price) # type: ignore
484
+ rounded_limit_price = float(rounded_limit_price)
485
+
486
+ order_params = {
487
+ 'reduceOnly': slice.reduce_only
488
+ }
489
+ if position.order_type=='limit':
490
+ if not param['dry_run']:
491
+ executed_order = await exchange.create_order( # type: ignore
492
+ symbol = position.ticker,
493
+ type = position.order_type,
494
+ amount = rounded_slice_amount_in_base_ccy,
495
+ price = rounded_limit_price,
496
+ side = position.side,
497
+ params = order_params
498
+ )
499
+ else:
500
+ executed_order = DUMMY_EXECUTION.copy()
501
+ executed_order['clientOrderId'] = str(uuid.uuid4())
502
+ executed_order['timestamp'] = dt_now.timestamp()
503
+ executed_order['datetime'] = dt_now
504
+ executed_order['symbol'] = position.ticker
505
+ executed_order['type'] = position.order_type
506
+ executed_order['side'] = position.side
507
+ executed_order['price'] = rounded_limit_price
508
+ executed_order['average'] = rounded_limit_price
509
+ executed_order['cost'] = 0
510
+ executed_order['amount'] = rounded_slice_amount_in_base_ccy
511
+ executed_order['filled'] = rounded_slice_amount_in_base_ccy
512
+ executed_order['remaining'] = 0
513
+ executed_order['status'] = 'closed'
514
+ executed_order['multiplier'] = position.multiplier
607
515
 
608
- await asyncio.sleep(int(param['loop_freq_ms']/1000))
609
-
610
-
611
- # Cancel hung limit order, resend as market
612
- if order_status!='closed':
613
- # If no update from websocket, do one last fetch via REST
614
- order_update = await exchange.fetch_order(order_id, position.ticker) # type: ignore
615
- order_status = order_update['status']
616
- filled_amount = order_update['filled']
617
- remaining_amount = order_update['remaining']
618
- order_update['multiplier'] = multiplier
619
- position.append_execution(order_id, order_update)
620
-
621
- if order_status!='closed':
516
+ else:
517
+ if not param['dry_run']:
518
+ executed_order = await exchange.create_order( # type: ignore
519
+ symbol = position.ticker,
520
+ type = position.order_type,
521
+ amount = rounded_slice_amount_in_base_ccy,
522
+ side = position.side,
523
+ params = order_params
524
+ )
525
+ else:
526
+ executed_order = DUMMY_EXECUTION.copy()
527
+ executed_order['clientOrderId'] = str(uuid.uuid4())
528
+ executed_order['timestamp'] = dt_now.timestamp()
529
+ executed_order['datetime'] = dt_now
530
+ executed_order['symbol'] = position.ticker
531
+ executed_order['type'] = position.order_type
532
+ executed_order['side'] = position.side
533
+ executed_order['price'] = rounded_limit_price
534
+ executed_order['average'] = rounded_limit_price
535
+ executed_order['cost'] = 0
536
+ executed_order['amount'] = rounded_slice_amount_in_base_ccy
537
+ executed_order['filled'] = rounded_slice_amount_in_base_ccy
538
+ executed_order['remaining'] = 0
539
+ executed_order['status'] = 'closed'
540
+ executed_order['multiplier'] = position.multiplier
541
+
542
+ executed_order['slice_id'] = i
543
+
544
+ '''
545
+ Format of executed_order:
546
+ executed_order
547
+ {'info': {'clOrdId': 'xxx', 'ordId': '2245241151525347328', 'sCode': '0', 'sMsg': 'Order placed', 'tag': 'xxx', 'ts': '1739415800635'}, 'id': '2245241151525347328', 'clientOrderId': 'xxx', 'timestamp': None, 'datetime': None, 'lastTradeTimestamp': None, 'lastUpdateTimestamp': None, 'symbol': 'SUSHI/USDT:USDT', 'type': 'limit', 'timeInForce': None, 'postOnly': None, 'side': 'buy', 'price': None, 'stopLossPrice': None, 'takeProfitPrice': None, 'triggerPrice': None, 'average': None, 'cost': None, 'amount': None, 'filled': None, 'remaining': None, 'status': None, 'fee': None, 'trades': [], 'reduceOnly': False, 'fees': [], 'stopPrice': None}
548
+ special variables:
549
+ function variables:
550
+ 'info': {'clOrdId': 'xxx', 'ordId': '2245241151525347328', 'sCode': '0', 'sMsg': 'Order placed', 'tag': 'xxx', 'ts': '1739415800635'}
551
+ 'id': '2245241151525347328'
552
+ 'clientOrderId': 'xxx'
553
+ 'timestamp': None
554
+ 'datetime': None
555
+ 'lastTradeTimestamp': None
556
+ 'lastUpdateTimestamp': None
557
+ 'symbol': 'SUSHI/USDT:USDT'
558
+ 'type': 'limit'
559
+ 'timeInForce': None
560
+ 'postOnly': None
561
+ 'side': 'buy'
562
+ 'price': None
563
+ 'stopLossPrice': None
564
+ 'takeProfitPrice': None
565
+ 'triggerPrice': None
566
+ 'average': None
567
+ 'cost': None
568
+ 'amount': None
569
+ 'filled': None
570
+ 'remaining': None
571
+ 'status': None
572
+ 'fee': None
573
+ 'trades': []
574
+ 'reduceOnly': False
575
+ 'fees': []
576
+ 'stopPrice': None
577
+ '''
578
+ order_id = executed_order['id']
579
+ order_status = executed_order['status']
580
+ filled_amount = executed_order['filled']
581
+ remaining_amount = executed_order['remaining']
582
+ executed_order['multiplier'] = multiplier
583
+ position.append_execution(order_id, executed_order)
584
+
585
+ log(f"Order dispatched: {order_id}. status: {order_status}, filled_amount: {filled_amount}, remaining_amount: {remaining_amount}")
586
+
587
+ if not order_status or order_status!='closed':
588
+ start_time = time.time()
589
+ wait_threshold_sec = position.wait_fill_threshold_ms / 1000
590
+
591
+ elapsed_sec = time.time() - start_time
592
+ while elapsed_sec < wait_threshold_sec:
593
+ order_update = None
594
+ if order_id in executions:
595
+ order_update = executions[order_id]
596
+
597
+ if order_update:
598
+ order_status = order_update['status']
599
+ filled_amount = order_update['filled']
600
+ remaining_amount = order_update['remaining']
601
+ order_update['multiplier'] = multiplier
602
+ position.append_execution(order_id, order_update)
603
+
604
+ if remaining_amount <= 0:
605
+ log(f"Limit order fully filled: {order_id}", log_level=LogLevel.INFO)
606
+ break
607
+
608
+ await asyncio.sleep(int(param['loop_freq_ms']/1000))
609
+
610
+
611
+ # Cancel hung limit order, resend as market
612
+ if order_status!='closed':
613
+ # If no update from websocket, do one last fetch via REST
614
+ order_update = await exchange.fetch_order(order_id, position.ticker) # type: ignore
622
615
  order_status = order_update['status']
623
616
  filled_amount = order_update['filled']
624
617
  remaining_amount = order_update['remaining']
618
+ order_update['multiplier'] = multiplier
619
+ position.append_execution(order_id, order_update)
625
620
 
626
- await exchange.cancel_order(order_id, position.ticker) # type: ignore
627
- position.get_execution(order_id)['status'] = 'canceled'
628
- log(f"Canceled unfilled/partial filled order: {order_id}. Resending remaining_amount: {remaining_amount} as market order.", log_level=LogLevel.INFO)
629
-
630
- rounded_slice_amount_in_base_ccy = exchange.amount_to_precision(position.ticker, remaining_amount) # type: ignore
631
- rounded_slice_amount_in_base_ccy = float(rounded_slice_amount_in_base_ccy)
632
- rounded_slice_amount_in_base_ccy = rounded_slice_amount_in_base_ccy if rounded_slice_amount_in_base_ccy>min_amount else min_amount
633
- if rounded_slice_amount_in_base_ccy>0:
634
- executed_resent_order = await exchange.create_order( # type: ignore
635
- symbol=position.ticker,
636
- type='market',
637
- amount=remaining_amount,
638
- side=position.side
639
- )
640
-
641
- order_id = executed_resent_order['id']
642
- order_status = executed_resent_order['status']
643
- executed_resent_order['multiplier'] = multiplier
644
- position.append_execution(order_id, executed_resent_order)
645
-
646
- while not order_status or order_status!='closed':
647
- order_update = None
648
- if order_id in executions:
649
- order_update = executions[order_id]
650
-
651
- if order_update:
652
- order_id = order_update['id']
653
- order_status = order_update['status']
654
- filled_amount = order_update['filled']
655
- remaining_amount = order_update['remaining']
656
-
657
- log(f"Waiting for resent market order to close {order_id} ...")
621
+ if order_status!='closed':
622
+ order_status = order_update['status']
623
+ filled_amount = order_update['filled']
624
+ remaining_amount = order_update['remaining']
658
625
 
659
- await asyncio.sleep(int(param['loop_freq_ms']/1000))
626
+ await exchange.cancel_order(order_id, position.ticker) # type: ignore
627
+ position.get_execution(order_id)['status'] = 'canceled'
628
+ log(f"Canceled unfilled/partial filled order: {order_id}. Resending remaining_amount: {remaining_amount} as market order.", log_level=LogLevel.INFO)
629
+
630
+ rounded_slice_amount_in_base_ccy = exchange.amount_to_precision(position.ticker, remaining_amount) # type: ignore
631
+ rounded_slice_amount_in_base_ccy = float(rounded_slice_amount_in_base_ccy)
632
+ rounded_slice_amount_in_base_ccy = rounded_slice_amount_in_base_ccy if rounded_slice_amount_in_base_ccy>min_amount else min_amount
633
+ if rounded_slice_amount_in_base_ccy>0:
634
+ executed_resent_order = await exchange.create_order( # type: ignore
635
+ symbol=position.ticker,
636
+ type='market',
637
+ amount=remaining_amount,
638
+ side=position.side
639
+ )
640
+
641
+ order_id = executed_resent_order['id']
642
+ order_status = executed_resent_order['status']
643
+ executed_resent_order['multiplier'] = multiplier
644
+ position.append_execution(order_id, executed_resent_order)
645
+
646
+ while not order_status or order_status!='closed':
647
+ order_update = None
648
+ if order_id in executions:
649
+ order_update = executions[order_id]
650
+
651
+ if order_update:
652
+ order_id = order_update['id']
653
+ order_status = order_update['status']
654
+ filled_amount = order_update['filled']
655
+ remaining_amount = order_update['remaining']
656
+
657
+ log(f"Waiting for resent market order to close {order_id} ...")
658
+
659
+ await asyncio.sleep(int(param['loop_freq_ms']/1000))
660
+
661
+ log(f"Resent market order{order_id} filled. status: {order_status}, filled_amount: {filled_amount}, remaining_amount: {remaining_amount}")
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
+
667
+ log(f"Executed slice #{i}", log_level=LogLevel.INFO)
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)
669
+ if position.order_type=='limit':
670
+ log(f"{position.ticker}, limit_price: {limit_price}, rounded_limit_price, {rounded_limit_price}", log_level=LogLevel.INFO)
671
+
672
+ except Exception as slice_err:
673
+ log(
674
+ f"Failed to execute #{i} slice: {slice.to_dict()}. {slice_err} {str(sys.exc_info()[0])} {str(sys.exc_info()[1])} {traceback.format_exc()}",
675
+ log_level=LogLevel.ERROR
676
+ )
677
+ finally:
678
+ i += 1
660
679
 
661
- log(f"Resent market order{order_id} filled. status: {order_status}, filled_amount: {filled_amount}, remaining_amount: {remaining_amount}")
680
+ position.patch_executions()
681
+ position.filled_amount = position.get_filled_amount()
682
+ position.average_cost = position.get_average_cost()
683
+ position.fees = position.get_fees()
662
684
 
663
- slice.dispatched_price = rounded_limit_price
664
- slice.dispatched_amount = rounded_slice_amount_in_base_ccy
665
- position.dispatched_slices.append(slice)
685
+ balances = await exchange.fetch_balance() # type: ignore
686
+ if param['default_type']!='spot':
687
+ updated_position = await exchange.fetch_position(symbol=position.ticker) # type: ignore
688
+ # After position closed, 'updated_position' can be an empty dict. hyperliquid for example.
689
+ amount = (updated_position['contracts'] if updated_position else 0) * position.multiplier # in base ccy
690
+ else:
691
+ base_ccy : str = position.ticker.split("/")[0]
692
+ amount = balances[base_ccy]['total']
693
+ position.pos = amount
666
694
 
667
- log(f"Executed slice #{i}", log_level=LogLevel.INFO)
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)
669
- if position.order_type=='limit':
670
- log(f"{position.ticker}, limit_price: {limit_price}, rounded_limit_price, {rounded_limit_price}", log_level=LogLevel.INFO)
695
+ position.done = True
671
696
 
672
- except Exception as slice_err:
673
- log(
674
- f"Failed to execute #{i} slice: {slice.to_dict()}. {slice_err} {str(sys.exc_info()[0])} {str(sys.exc_info()[1])} {traceback.format_exc()}",
675
- log_level=LogLevel.ERROR
676
- )
677
- finally:
678
- i += 1
679
-
680
- position.patch_executions()
681
- position.filled_amount = position.get_filled_amount()
682
- position.average_cost = position.get_average_cost()
683
- position.fees = position.get_fees()
684
-
685
- balances = await exchange.fetch_balance() # type: ignore
686
- if param['default_type']!='spot':
687
- updated_position = await exchange.fetch_position(symbol=position.ticker) # type: ignore
688
- # After position closed, 'updated_position' can be an empty dict. hyperliquid for example.
689
- amount = (updated_position['contracts'] if updated_position else 0) * position.multiplier # in base ccy
690
- else:
691
- base_ccy : str = position.ticker.split("/")[0]
692
- amount = balances[base_ccy]['total']
693
- position.pos = amount
697
+ except Exception as position_execution_err:
698
+ position.done = False
699
+ position.execution_err = f"Execution failed: {position_execution_err} {str(sys.exc_info()[0])} {str(sys.exc_info()[1])} {traceback.format_exc()}"
694
700
 
695
701
  async def work(
696
702
  param : Dict,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: siglab-py
3
- Version: 0.1.50
3
+ Version: 0.1.52
4
4
  Summary: Market data fetches, TA calculations and generic order gateway.
5
5
  Author: r0bbarh00d
6
6
  Author-email: r0bbarh00d <r0bbarh00d@gmail.com>