siglab-py 0.2.0__py3-none-any.whl → 0.2.2__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/constants.py CHANGED
@@ -1,3 +1,12 @@
1
+ import enum
1
2
  from typing import Union, List, Dict, Any
2
3
 
3
- JSON_SERIALIZABLE_TYPES = Union[str, bool, int, float, None, List[Any], Dict[Any, Any]]
4
+ JSON_SERIALIZABLE_TYPES = Union[str, bool, int, float, None, List[Any], Dict[Any, Any]]
5
+
6
+ class LogLevel(enum.Enum):
7
+ CRITICAL = 50
8
+ ERROR = 40
9
+ WARNING = 30
10
+ INFO = 20
11
+ DEBUG = 10
12
+ NOTSET = 0
@@ -25,14 +25,19 @@ import ccxt.pro as ccxtpro
25
25
 
26
26
  from siglab_py.exchanges.any_exchange import AnyExchange
27
27
  from siglab_py.ordergateway.client import Order, DivisiblePosition
28
+ from siglab_py.util.notification_util import dispatch_notification
29
+ from siglab_py.constants import LogLevel
28
30
 
29
31
  '''
30
32
  Usage:
31
- python gateway.py --gateway_id bybit_01 --default_type linear --rate_limit_ms 100
33
+ python gateway.py --gateway_id hyperliquid_01 --default_type linear --rate_limit_ms 100
32
34
 
33
35
  --default_type defaults to linear
34
36
  --rate_limit_ms defaults to 100
35
37
 
38
+ --slack_info_url, --slack_critical_url and --slack_alert_url are if you want gateway to dispatch Slack notification on events.
39
+ How to get Slack webhook urls? https://medium.com/@natalia_assad/how-send-a-table-to-slack-using-python-d1a20b08abe0
40
+
36
41
  This script is pypy compatible:
37
42
  pypy gateway.py --gateway_id bybit_01 --default_type linear --rate_limit_ms 100
38
43
 
@@ -82,7 +87,11 @@ To debug from vscode, launch.json:
82
87
  "--aws_kms_key_id", "",
83
88
  "--apikey", "xxx",
84
89
  "--secret", "xxx",
85
- "--passphrase", "xxx"
90
+ "--passphrase", "xxx",
91
+
92
+ "--slack_info_url", "https://hooks.slack.com/services/xxx",
93
+ "--slack_critial_url", "https://hooks.slack.com/services/xxx",
94
+ "--slack_alert_url", "https://hooks.slack.com/services/xxx",
86
95
  ],
87
96
  "env": {
88
97
  "PYTHONPATH": "${workspaceFolder}"
@@ -188,6 +197,17 @@ param : Dict = {
188
197
  "loop_freq_ms" : 500, # reduce this if you need trade faster
189
198
  "loops_random_delay_multiplier" : 1, # Add randomness to time between slices are sent off. Set to 1 if no random delay needed.
190
199
 
200
+ 'notification' : {
201
+ 'footer' : None,
202
+
203
+ # slack webhook url's for notifications
204
+ 'slack' : {
205
+ 'info' : { 'webhook_url' : None },
206
+ 'critical' : { 'webhook_url' : None },
207
+ 'alert' : { 'webhook_url' : None },
208
+ }
209
+ },
210
+
191
211
  'mds' : {
192
212
  'topics' : {
193
213
 
@@ -268,6 +288,10 @@ def parse_args():
268
288
  parser.add_argument("--secret", help="Exchange secret", default=None)
269
289
  parser.add_argument("--passphrase", help="Exchange passphrase", default=None)
270
290
 
291
+ parser.add_argument("--slack_info_url", help="Slack webhook url for INFO", default=None)
292
+ parser.add_argument("--slack_critial_url", help="Slack webhook url for CRITICAL", default=None)
293
+ parser.add_argument("--slack_alert_url", help="Slack webhook url for ALERT", default=None)
294
+
271
295
  args = parser.parse_args()
272
296
  param['gateway_id'] = args.gateway_id
273
297
 
@@ -297,6 +321,11 @@ def parse_args():
297
321
  param['secret'] = args.secret
298
322
  param['passphrase'] = args.passphrase
299
323
 
324
+ param['notification']['slack']['info'] = args.slack_info_url
325
+ param['notification']['slack']['critical'] = args.slack_critial_url
326
+ param['notification']['slack']['alert'] = args.slack_alert_url
327
+ param['notification']['footer'] = f"From gateway {param['gateway_id']}"
328
+
300
329
  def init_redis_client() -> StrictRedis:
301
330
  redis_client : StrictRedis = StrictRedis(
302
331
  host = param['mds']['redis']['host'],
@@ -389,7 +418,8 @@ async def instantiate_exchange(
389
418
  "walletAddress" : api_key,
390
419
  "privateKey" : secret,
391
420
  'enableRateLimit' : True,
392
- 'rateLimit' : rate_limit_ms
421
+ 'rateLimit' : rate_limit_ms,
422
+ "verbose": True,
393
423
  }
394
424
  ) # type: ignore
395
425
  else:
@@ -439,13 +469,14 @@ async def send_heartbeat(exchange):
439
469
  except Exception as hb_error:
440
470
  log(f'Failed to send heartbeat: {hb_error}')
441
471
  finally:
442
- await asyncio.sleep(30)
472
+ await asyncio.sleep(20)
443
473
 
444
474
  async def execute_one_position(
445
475
  exchange : AnyExchange,
446
476
  position : DivisiblePosition,
447
477
  param : Dict,
448
- executions : Dict[str, Dict[str, Any]]
478
+ executions : Dict[str, Dict[str, Any]],
479
+ notification_params : Dict[str, Any]
449
480
  ):
450
481
  try:
451
482
  market : Dict[str, Any] = exchange.markets[position.ticker] if position.ticker in exchange.markets else None # type: ignore
@@ -710,7 +741,8 @@ async def execute_one_position(
710
741
  async def work(
711
742
  param : Dict,
712
743
  exchange : AnyExchange,
713
- redis_client : StrictRedis
744
+ redis_client : StrictRedis,
745
+ notification_params : Dict[str, Any]
714
746
  ):
715
747
  incoming_orders_topic_regex : str = param['incoming_orders_topic_regex']
716
748
  incoming_orders_topic_regex = incoming_orders_topic_regex.replace("$GATEWAY_ID$", param['gateway_id'])
@@ -758,7 +790,7 @@ async def work(
758
790
  ]
759
791
 
760
792
  start = time.time()
761
- pending_executions = [ execute_one_position(exchange, position, param, executions) for position in positions ]
793
+ pending_executions = [ execute_one_position(exchange, position, param, executions, notification_params) for position in positions ]
762
794
  await asyncio.gather(*pending_executions)
763
795
  order_dispatch_elapsed_ms = int((time.time() - start) *1000)
764
796
 
@@ -819,6 +851,8 @@ async def main():
819
851
  secret : str = param['secret']
820
852
  passphrase : str = param['passphrase']
821
853
 
854
+ notification_params : Dict[str, Any] = param['notification']
855
+
822
856
  if encrypt_decrypt_with_aws_kms:
823
857
  aws_kms_key_id = str(os.getenv('AWS_KMS_KEY_ID'))
824
858
 
@@ -843,6 +877,6 @@ async def main():
843
877
  balances = await exchange.fetch_balance() # type: ignore
844
878
  log(f"{param['gateway_id']}: account balances {balances}")
845
879
 
846
- await work(param=param, exchange=exchange, redis_client=redis_client)
880
+ await work(param=param, exchange=exchange, redis_client=redis_client, notification_params=notification_params)
847
881
 
848
882
  asyncio.run(main())
@@ -6,6 +6,7 @@ from typing import Any, Dict, List, Union
6
6
  import logging
7
7
  import json
8
8
  from redis import StrictRedis
9
+ from sympy import false
9
10
 
10
11
  from ordergateway.client import DivisiblePosition, execute_positions
11
12
  from constants import JSON_SERIALIZABLE_TYPES
@@ -95,7 +96,7 @@ if __name__ == '__main__':
95
96
  side = 'sell',
96
97
  amount = 0.00100,
97
98
  leg_room_bps = 5,
98
- reduce_only=True,
99
+ reduce_only=False,
99
100
  order_type = 'limit',
100
101
  slices=1,
101
102
  wait_fill_threshold_ms=60000,
@@ -18,9 +18,20 @@ 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['timestamp_ms'] = pd_candles['timestamp_ms'].apply(
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
- )
21
+ def _fix_timestamp_ms(x):
22
+ if isinstance(x, pd.Timestamp):
23
+ return int(x.value // 10**6)
24
+ elif isinstance(x, np.datetime64):
25
+ return int(x.astype('int64') // 10**6)
26
+ elif isinstance(x, (int, float)):
27
+ x = int(x)
28
+ if len(str(abs(x))) == 13:
29
+ return x
30
+ else:
31
+ return int(x * 1000)
32
+ else:
33
+ raise ValueError(f"Unsupported type {type(x)} for timestamp conversion")
34
+ pd_candles['timestamp_ms'] = pd_candles['timestamp_ms'].apply(_fix_timestamp_ms)
24
35
  pd_candles['datetime'] = pd_candles['timestamp_ms'].apply(lambda x: datetime.fromtimestamp(int(x/1000)))
25
36
  pd_candles['datetime'] = pd.to_datetime(pd_candles['datetime'])
26
37
  pd_candles['datetime'] = pd_candles['datetime'].dt.tz_localize(None)
@@ -0,0 +1,69 @@
1
+ import json
2
+ from typing import Any, Dict, Union
3
+ import pandas as pd
4
+ import numpy as np
5
+ from tabulate import tabulate
6
+
7
+ from slack_notification_util import slack_dispatch_notification
8
+
9
+ from siglab_py.constants import LogLevel
10
+
11
+ def dispatch_notification(
12
+ title : str,
13
+ message : Union[str, Dict, pd.DataFrame],
14
+ footer : str,
15
+ params : Dict[str, Any],
16
+ log_level : LogLevel = LogLevel.INFO
17
+ ):
18
+ if isinstance(message, Dict):
19
+ _message = json.dumps(message, indent=2, separators=(' ', ':'))
20
+ elif isinstance(message, pd.DataFrame):
21
+ _message = tabulate(message, headers='keys', tablefmt='orgtbl') # type: ignore
22
+ else:
23
+ _message = message
24
+
25
+ slack_dispatch_notification(title, _message, footer, params, log_level)
26
+
27
+ if __name__ == '__main__':
28
+ params : Dict[str, Any] = {
29
+ "slack" : {
30
+ "info" : {
31
+ "webhook_url" : "https://hooks.slack.com/services/xxx"
32
+ },
33
+ "critical" : {
34
+ "webhook_url" : "https://hooks.slack.com/services/xxx"
35
+ },
36
+ "alert" : {
37
+ "webhook_url" : "https://hooks.slack.com/services/xxx"
38
+ }
39
+ },
40
+ }
41
+
42
+ log_level : LogLevel = LogLevel.CRITICAL
43
+
44
+ title : str = "Test message"
45
+ footer : str = "... some footer .."
46
+
47
+ message1 : str = "Testing 1"
48
+ dispatch_notification(title=title, message=message1, footer=footer, params=params, log_level=log_level)
49
+
50
+ message2 : Dict[str, Any] = {
51
+ 'aaa' : 123,
52
+ 'bbb' : 456,
53
+ 'ccc' : {
54
+ 'ddd' : 789
55
+ }
56
+ }
57
+ dispatch_notification(title=title, message=message2, footer=footer, params=params, log_level=log_level)
58
+
59
+ start_date = pd.to_datetime('2024-01-01 00:00:00')
60
+ datetimes = pd.date_range(start=start_date, periods=20, freq='H')
61
+ np.random.seed(42)
62
+ close_prices = np.random.uniform(80000, 90000, size=20).round(2)
63
+ data : pd.DataFrame = pd.DataFrame({
64
+ 'datetime': datetimes,
65
+ 'close': close_prices
66
+ })
67
+ data['timestamp_ms'] = data['datetime'].astype('int64')
68
+ message3 = data
69
+ dispatch_notification(title=title, message=message3, footer=footer, params=params, log_level=log_level)
@@ -0,0 +1,61 @@
1
+ '''
2
+ https://medium.com/@natalia_assad/how-send-a-table-to-slack-using-python-d1a20b08abe0
3
+ '''
4
+ import sys
5
+ from typing import Any, Dict
6
+ import json
7
+ import requests
8
+
9
+ from siglab_py.constants import LogLevel
10
+
11
+ def slack_dispatch_notification(
12
+ title : str,
13
+ message : str,
14
+ footer : str,
15
+ params : Dict[str, Any],
16
+ log_level : LogLevel = LogLevel.INFO
17
+ ):
18
+ slack_params = params['slack']
19
+
20
+ if log_level==LogLevel.INFO or log_level==LogLevel.DEBUG:
21
+ webhook_url = slack_params['info']['webhook_url']
22
+ elif log_level==LogLevel.ERROR or log_level==LogLevel.CRITICAL:
23
+ webhook_url = slack_params['critical']['webhook_url']
24
+ elif log_level==LogLevel.ERROR or log_level==LogLevel.CRITICAL:
25
+ webhook_url = slack_params['alert']['webhook_url']
26
+ else:
27
+ webhook_url = slack_params['info']['webhook_url']
28
+
29
+ slack_data = {
30
+ "username": "NotificationBot",
31
+ "type": "section",
32
+ "blocks": [
33
+ {
34
+ "type": "header",
35
+ "text": {
36
+ "type": "plain_text",
37
+ "text": f"{title}"
38
+ }
39
+ },
40
+ {
41
+ "type": "section",
42
+ "text": {
43
+ "type": "mrkdwn",
44
+ "text": message
45
+ }
46
+ },
47
+ {
48
+ "type": "section",
49
+ "text": {
50
+ "type": "plain_text",
51
+ "text": footer
52
+ }
53
+ }
54
+ ]
55
+ }
56
+
57
+ byte_length = str(sys.getsizeof(slack_data, 2000))
58
+ headers = {'Content-Type': "application/json", 'Content-Length': byte_length}
59
+ response = requests.post(webhook_url, data=json.dumps(slack_data), headers=headers)
60
+ if response.status_code != 200:
61
+ raise Exception(response.status_code, response.text)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: siglab-py
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Market data fetches, TA calculations and generic order gateway.
5
5
  Author: r0bbarh00d
6
6
  Author-email: r0bbarh00d <r0bbarh00d@gmail.com>
@@ -1,5 +1,5 @@
1
1
  siglab_py/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- siglab_py/constants.py,sha256=YGNdEsWtQ99V2oltaynZTjM8VIboSfyIjDXJKSlhv4U,132
2
+ siglab_py/constants.py,sha256=5isS_9WNoqpq9nC3ME5I5j7hJI9HAImij2bcgVVyWeY,275
3
3
  siglab_py/exchanges/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  siglab_py/exchanges/any_exchange.py,sha256=Y-zue75ZSmu9Ga1fONbjBGLNH5pDHQI01hCSjuLBkAk,889
5
5
  siglab_py/exchanges/futubull.py,sha256=6M99G8ZYsIyDyoTvvleMT_WPyNYbb6q-t2KXp0Qyi6g,20422
@@ -15,8 +15,8 @@ siglab_py/market_data_providers/test_provider.py,sha256=wBLCgcWjs7FGZJXWsNyn30lk
15
15
  siglab_py/ordergateway/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
16
  siglab_py/ordergateway/client.py,sha256=EwoVKxEcngIs8-b4MThPBdZfFIWJg1OFAKG9bwC5BYw,14826
17
17
  siglab_py/ordergateway/encrypt_keys_util.py,sha256=-qi87db8To8Yf1WS1Q_Cp2Ya7ZqgWlRqSHfNXCM7wE4,1339
18
- siglab_py/ordergateway/gateway.py,sha256=-rDx0tD8vykKbsuS478fedODN0_5ZXe7OAsMYoXtfw8,39165
19
- siglab_py/ordergateway/test_ordergateway.py,sha256=4PE2flp_soGcD3DrI7zJOzZndjkb6I5XaDrFNNq4Huo,4009
18
+ siglab_py/ordergateway/gateway.py,sha256=uhqu_TOpD8oLIWIyDPZSmheqFuCdtsQmbZRFxlZPHU4,40909
19
+ siglab_py/ordergateway/test_ordergateway.py,sha256=KajC0oidfDIgboRazkteAXzx47FffVRligThzKKqgRk,4035
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
22
22
  siglab_py/tests/integration/market_data_util_tests.py,sha256=X0CiSMDfsafKcmjVKknA03vUUbMV0fAZweb3D01ikYI,7174
@@ -26,9 +26,11 @@ 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=o9MNuOWUhw-r0id10Wjjd7a6rkL6-g3OmvajMSj1JJ4,43838
28
28
  siglab_py/util/aws_util.py,sha256=KGmjHrr1rpnnxr33nXHNzTul4tvyyxl9p6gpwNv0Ygc,2557
29
- siglab_py/util/market_data_util.py,sha256=hBZiFhko0NFf8A74mv9Qia-YYbjK4RuhzC9I6L353is,19033
29
+ siglab_py/util/market_data_util.py,sha256=9Uze8DE5z90H4Qm15R55ZllAi5trUkwCAW-BWYbfaW8,19420
30
+ siglab_py/util/notification_util.py,sha256=FvX_1lvFOGIPN59DVD9ALBAFlRET4kuSfHgCE-U2ewE,2300
30
31
  siglab_py/util/retry_util.py,sha256=mxYuRFZRZoaQQjENcwPmxhxixtd1TFvbxIdPx4RwfRc,743
31
- siglab_py-0.2.0.dist-info/METADATA,sha256=smtL3wpkrqAmHQC8wdHrA1nVWwejI8QHUdHpXGwOXGM,979
32
- siglab_py-0.2.0.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
33
- siglab_py-0.2.0.dist-info/top_level.txt,sha256=AbD4VR9OqmMOGlMJLkAVPGQMtUPIQv0t1BF5xmcLJSk,10
34
- siglab_py-0.2.0.dist-info/RECORD,,
32
+ siglab_py/util/slack_notification_util.py,sha256=WShr3gxktOkW0-bHN5pxXk1sF3N720WlnAZovJv5HpY,1926
33
+ siglab_py-0.2.2.dist-info/METADATA,sha256=5XBQsUxK3E3IlwZhixAmoj3DjwmEFjBAOppLce-l-20,979
34
+ siglab_py-0.2.2.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
35
+ siglab_py-0.2.2.dist-info/top_level.txt,sha256=AbD4VR9OqmMOGlMJLkAVPGQMtUPIQv0t1BF5xmcLJSk,10
36
+ siglab_py-0.2.2.dist-info/RECORD,,