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