siglab-py 0.1.29__py3-none-any.whl → 0.6.12__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.

Files changed (32) hide show
  1. siglab_py/constants.py +26 -1
  2. siglab_py/exchanges/binance.py +38 -0
  3. siglab_py/exchanges/deribit.py +83 -0
  4. siglab_py/exchanges/futubull.py +12 -2
  5. siglab_py/market_data_providers/candles_provider.py +2 -2
  6. siglab_py/market_data_providers/candles_ta_provider.py +3 -3
  7. siglab_py/market_data_providers/ccxt_candles_ta_to_csv.py +4 -4
  8. siglab_py/market_data_providers/futu_candles_ta_to_csv.py +7 -2
  9. siglab_py/market_data_providers/google_monitor.py +320 -0
  10. siglab_py/market_data_providers/orderbooks_provider.py +15 -12
  11. siglab_py/market_data_providers/tg_monitor.py +428 -0
  12. siglab_py/market_data_providers/{test_provider.py → trigger_provider.py} +9 -8
  13. siglab_py/ordergateway/client.py +172 -41
  14. siglab_py/ordergateway/encrypt_keys_util.py +1 -1
  15. siglab_py/ordergateway/gateway.py +456 -347
  16. siglab_py/ordergateway/test_ordergateway.py +8 -7
  17. siglab_py/tests/integration/market_data_util_tests.py +35 -1
  18. siglab_py/tests/unit/analytic_util_tests.py +47 -12
  19. siglab_py/tests/unit/simple_math_tests.py +235 -0
  20. siglab_py/tests/unit/trading_util_tests.py +65 -0
  21. siglab_py/util/analytic_util.py +478 -69
  22. siglab_py/util/market_data_util.py +487 -100
  23. siglab_py/util/notification_util.py +78 -0
  24. siglab_py/util/retry_util.py +11 -3
  25. siglab_py/util/simple_math.py +240 -0
  26. siglab_py/util/slack_notification_util.py +59 -0
  27. siglab_py/util/trading_util.py +118 -0
  28. {siglab_py-0.1.29.dist-info → siglab_py-0.6.12.dist-info}/METADATA +5 -9
  29. siglab_py-0.6.12.dist-info/RECORD +44 -0
  30. {siglab_py-0.1.29.dist-info → siglab_py-0.6.12.dist-info}/WHEEL +1 -1
  31. siglab_py-0.1.29.dist-info/RECORD +0 -34
  32. {siglab_py-0.1.29.dist-info → siglab_py-0.6.12.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,78 @@
1
+ import json
2
+ from typing import Any, Dict, Union
3
+ from datetime import datetime, timezone
4
+ import pandas as pd
5
+ import numpy as np
6
+ from tabulate import tabulate
7
+
8
+ from siglab_py.util.slack_notification_util import slack_dispatch_notification
9
+
10
+ from siglab_py.constants import LogLevel
11
+
12
+ def dispatch_notification(
13
+ title : str,
14
+ message : Union[str, Dict, pd.DataFrame],
15
+ footer : str,
16
+ params : Dict[str, Any],
17
+ log_level : LogLevel = LogLevel.INFO,
18
+ logger = None
19
+ ):
20
+ try:
21
+ if isinstance(message, Dict):
22
+ _message = json.dumps(message, indent=2, separators=(' ', ':'))
23
+ elif isinstance(message, pd.DataFrame):
24
+ _message = tabulate(message, headers='keys', tablefmt='orgtbl') # type: ignore
25
+ else:
26
+ _message = message
27
+
28
+ utc_time = datetime.now(timezone.utc)
29
+ footer = f"UTC {utc_time} {footer}"
30
+
31
+ slack_dispatch_notification(title, _message, footer, params, log_level)
32
+ except Exception as any_notification_error:
33
+ if logger:
34
+ logger.info(f"Failed to dispatch notification: {any_notification_error}")
35
+
36
+ if __name__ == '__main__':
37
+ params : Dict[str, Any] = {
38
+ "slack" : {
39
+ "info" : {
40
+ "webhook_url" : "https://hooks.slack.com/services/xxx"
41
+ },
42
+ "critical" : {
43
+ "webhook_url" : "https://hooks.slack.com/services/xxx"
44
+ },
45
+ "alert" : {
46
+ "webhook_url" : "https://hooks.slack.com/services/xxx"
47
+ }
48
+ },
49
+ }
50
+
51
+ log_level : LogLevel = LogLevel.CRITICAL
52
+
53
+ title : str = "Test message"
54
+ footer : str = "... some footer .."
55
+
56
+ message1 : str = "Testing 1"
57
+ dispatch_notification(title=title, message=message1, footer=footer, params=params, log_level=log_level)
58
+
59
+ message2 : Dict[str, Any] = {
60
+ 'aaa' : 123,
61
+ 'bbb' : 456,
62
+ 'ccc' : {
63
+ 'ddd' : 789
64
+ }
65
+ }
66
+ dispatch_notification(title=title, message=message2, footer=footer, params=params, log_level=log_level)
67
+
68
+ start_date = pd.to_datetime('2024-01-01 00:00:00')
69
+ datetimes = pd.date_range(start=start_date, periods=20, freq='H')
70
+ np.random.seed(42)
71
+ close_prices = np.random.uniform(80000, 90000, size=20).round(2)
72
+ data : pd.DataFrame = pd.DataFrame({
73
+ 'datetime': datetimes,
74
+ 'close': close_prices
75
+ })
76
+ data['timestamp_ms'] = data['datetime'].astype('int64')
77
+ message3 = data
78
+ dispatch_notification(title=title, message=message3, footer=footer, params=params, log_level=log_level)
@@ -1,15 +1,23 @@
1
- def retry(num_attempts : int = 1):
1
+ import time
2
+
3
+ def retry(
4
+ num_attempts : int = 1,
5
+ pause_between_retries_ms : int = 1000
6
+ ):
2
7
  def decorator(method):
3
8
  def wrapper(*args, **kw):
4
9
  for i in range(num_attempts):
5
10
  try:
6
11
  result = method(*args, **kw)
7
12
  if i>0:
8
- print(f"retry_gizmo.retry succeeded: {method.__name__} on #{i+1} invocation. {args} {kw}")
13
+ print(f"retry_util.retry done {method.__name__} on #{i+1} call. {args} {kw}")
9
14
  return result
10
15
  except Exception as retry_error:
11
16
  if i==(num_attempts-1):
12
- err_msg = f"retry_gizmo.retry failed: {method.__name__} after {num_attempts} invocations. {args} {kw}. {retry_error}"
17
+ err_msg = f"retry_util.retry gave up {method.__name__} after {num_attempts} calls. {args} {kw}. {retry_error}"
13
18
  raise Exception(err_msg) from retry_error
19
+ finally:
20
+ time.sleep(int(pause_between_retries_ms/1000))
21
+
14
22
  return wrapper
15
23
  return decorator
@@ -0,0 +1,240 @@
1
+ import math
2
+ import random
3
+ from typing import List, Dict, Union
4
+
5
+ from pandas import isna
6
+
7
+ def generate_rand_nums(
8
+ range_min : float = 0,
9
+ range_max : float = 1,
10
+ size=100, # list size
11
+ percent_in_range : float = 100,
12
+ abs_min : float = 0,
13
+ abs_max : float = 1
14
+ ) -> List[float]:
15
+ assert(range_min<range_max)
16
+
17
+ if abs_min>range_min:
18
+ abs_min = range_min
19
+ if abs_max<range_max:
20
+ abs_max = range_max
21
+
22
+ result : List[float] = []
23
+ for _ in range(int(size * percent_in_range/100)):
24
+ result.append(random.uniform(range_min, range_max))
25
+ for _ in range(size - len(result)):
26
+ if random.uniform(0, 1)>0.5:
27
+ result.append(random.uniform(abs_min, range_min))
28
+ else:
29
+ result.append(random.uniform(range_max, abs_max))
30
+
31
+ random.shuffle(result)
32
+
33
+ return result
34
+
35
+ # https://norman-lm-fung.medium.com/levels-are-psychological-7176cdefb5f2
36
+ def round_to_level(
37
+ num : float,
38
+ level_granularity : float = 0.01
39
+ ) -> float:
40
+ if math.isnan(num):
41
+ return num
42
+ level_size = num * level_granularity
43
+ magnitude = math.floor(math.log10(abs(level_size)))
44
+ base_increment = 10 ** magnitude
45
+ rounded_level_size = round(level_size / base_increment) * base_increment
46
+ rounded_num = round(num / rounded_level_size) * rounded_level_size
47
+ return rounded_num
48
+
49
+ def bucket_series(
50
+ values : List[float],
51
+ outlier_threshold_percent : float = 0,
52
+ level_granularity : float = 0.1 # 0.1 = 10%
53
+ ) -> Dict[
54
+ str,
55
+ Dict[str,Union[float, List[float]]]
56
+ ]:
57
+ buckets : Dict[
58
+ str,
59
+ Dict[str,Union[float, List[float]]]
60
+ ] = {}
61
+ list_0_to_1 : bool = True if len([x for x in values if x<0 or x>1])/len(values)*100 <= outlier_threshold_percent else False
62
+ list_m1_to_1 : bool = True if len([x for x in values if x<-1 or x>1])/len(values)*100 <= outlier_threshold_percent else False
63
+
64
+ list_0_to_100 : bool = True if len([x for x in values if x<0 or x>100])/len(values)*100 <= outlier_threshold_percent else False
65
+ if (
66
+ list_0_to_100
67
+ and (
68
+ not min(values)<100*(outlier_threshold_percent/100) or not max(values)>100*(1-outlier_threshold_percent/100)
69
+ )
70
+ ):
71
+ list_0_to_100 = False
72
+ list_m100_to_100 : bool = True if len([x for x in values if x<-100 or x>100])/len(values)*100 <= outlier_threshold_percent else False
73
+ if (
74
+ list_m100_to_100
75
+ and (
76
+ not min(values)<-100*(1-outlier_threshold_percent/100) or not max(values)>100*(1-outlier_threshold_percent/100)
77
+ )
78
+ ):
79
+ list_m100_to_100 = False
80
+
81
+ def _generate_sequence(start, stop, step):
82
+ result = []
83
+ current = start
84
+ num_steps = int((stop - start) / step) + 1
85
+ for i in range(num_steps):
86
+ result.append(round(start + i * step, 10))
87
+ return result
88
+
89
+ if list_0_to_1:
90
+ step = round_to_level(
91
+ 1 * level_granularity,
92
+ level_granularity=level_granularity
93
+ )
94
+ intervals = _generate_sequence(0.1, 1, step)
95
+ last_interval = 0
96
+ buckets[f"< 0"] = {
97
+ 'min' : float("-inf"),
98
+ 'max' : 0,
99
+ 'values' : [ x for x in values if x<0 ]
100
+ }
101
+ for interval in intervals:
102
+ buckets[f"{last_interval} - {interval}"] = {
103
+ 'min' : last_interval,
104
+ 'max' : interval,
105
+ 'values' : [ x for x in values if x>=last_interval and x<interval ]
106
+ }
107
+ last_interval = interval
108
+ buckets[f">1"] = {
109
+ 'min' : last_interval,
110
+ 'max' : float("inf"),
111
+ 'values' : [ x for x in values if x>=1 ]
112
+ }
113
+
114
+ elif not list_0_to_1 and list_m1_to_1:
115
+ step = round_to_level(
116
+ 1 * level_granularity,
117
+ level_granularity=level_granularity
118
+ )
119
+ intervals = _generate_sequence(-0.9, 1, step)
120
+ last_interval = -1
121
+ buckets[f"< -1"] = {
122
+ 'min' : float("-inf"),
123
+ 'max' : -1,
124
+ 'values' : [ x for x in values if x<-1 ]
125
+ }
126
+ for interval in intervals:
127
+ buckets[f"{last_interval} - {interval}"] = {
128
+ 'min' : last_interval,
129
+ 'max' : interval,
130
+ 'values' : [ x for x in values if x>=last_interval and x<interval ]
131
+ }
132
+ last_interval = interval
133
+ buckets[f">1"] = {
134
+ 'min' : last_interval,
135
+ 'max' : float("inf"),
136
+ 'values' : [ x for x in values if x>=1 ]
137
+ }
138
+
139
+ elif not list_0_to_1 and not list_m1_to_1 and list_0_to_100:
140
+ step = round_to_level(
141
+ 100 * level_granularity,
142
+ level_granularity=level_granularity
143
+ )
144
+ intervals = _generate_sequence(10, 100, step)
145
+ last_interval = 0
146
+ buckets[f"<0"] = {
147
+ 'min' : float("-inf"),
148
+ 'max' : 0,
149
+ 'values' : [ x for x in values if x<0 ]
150
+ }
151
+ for interval in intervals:
152
+ buckets[f"{last_interval} - {interval}"] = {
153
+ 'min' : last_interval,
154
+ 'max' : interval,
155
+ 'values' : [ x for x in values if x>=last_interval and x<interval ]
156
+ }
157
+ last_interval = interval
158
+ buckets[f">100"] = {
159
+ 'min' : last_interval,
160
+ 'max' : float("inf"),
161
+ 'values' : [ x for x in values if x>=100 ]
162
+ }
163
+
164
+ elif not list_0_to_1 and not list_m1_to_1 and not list_0_to_100 and list_m100_to_100:
165
+ step = round_to_level(
166
+ 100 * level_granularity,
167
+ level_granularity=level_granularity
168
+ )
169
+ intervals = _generate_sequence(-90, 100, step)
170
+ last_interval = -100
171
+ buckets[f"<-100"] = {
172
+ 'min' : float("-inf"),
173
+ 'max' : -100,
174
+ 'values' : [ x for x in values if x<-100 ]
175
+ }
176
+ for interval in intervals:
177
+ buckets[f"{last_interval} - {interval}"] = {
178
+ 'min' : last_interval,
179
+ 'max' : interval,
180
+ 'values' : [ x for x in values if x>=last_interval and x<interval ]
181
+ }
182
+ last_interval = interval
183
+ buckets[f">100"] = {
184
+ 'min' : last_interval,
185
+ 'max' : float("inf"),
186
+ 'values' : [ x for x in values if x>=100 ]
187
+ }
188
+
189
+ else:
190
+ range_min = round_to_level(
191
+ min(values),
192
+ level_granularity=level_granularity
193
+ )
194
+ range_max = round_to_level(
195
+ max(values),
196
+ level_granularity=level_granularity
197
+ )
198
+ step = round_to_level(
199
+ abs(range_max - range_min) * level_granularity,
200
+ level_granularity=level_granularity
201
+ )
202
+
203
+ intervals = _generate_sequence(range_min+step, range_max, step)
204
+ last_interval = range_min
205
+ buckets[f"< {range_min}"] = {
206
+ 'min' : float("-inf"),
207
+ 'max' : range_min,
208
+ 'values' : [ x for x in values if x<range_min ]
209
+ }
210
+ for interval in intervals:
211
+ buckets[f"{last_interval} - {interval}"] = {
212
+ 'min' : last_interval,
213
+ 'max' : interval,
214
+ 'values' : [ x for x in values if x>=last_interval and x<interval ]
215
+ }
216
+ last_interval = interval
217
+ buckets[f"> {range_max}"] = {
218
+ 'min' : last_interval,
219
+ 'max' : float("inf"),
220
+ 'values' : [ x for x in values if x>=range_max ]
221
+ }
222
+
223
+ for key in buckets:
224
+ bucket = buckets[key]
225
+ assert(len([x for x in bucket['values'] if x<bucket['min'] or x>bucket['max']])==0) # type: ignore
226
+
227
+ return buckets
228
+
229
+ def bucketize_val(
230
+ x : float,
231
+ buckets : Dict[
232
+ str,
233
+ Dict[str,Union[float, List[float]]]
234
+ ]
235
+ ) -> Union[str,None]:
236
+ for key in buckets:
237
+ bucket = buckets[key]
238
+ if x>=bucket['min'] and x<=bucket['max']: # type: ignore
239
+ return key
240
+ return None
@@ -0,0 +1,59 @@
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
+ max_message_len : int = 1800
18
+ ):
19
+ slack_params = params['slack']
20
+
21
+ # Slack slack ... https://stackoverflow.com/questions/60344831/slack-api-invalid-block
22
+ message = message[:max_message_len]
23
+
24
+ if log_level.value==LogLevel.INFO.value or log_level.value==LogLevel.DEBUG.value:
25
+ webhook_url = slack_params['info']['webhook_url']
26
+ elif log_level.value==LogLevel.CRITICAL.value:
27
+ webhook_url = slack_params['critical']['webhook_url']
28
+ elif log_level.value==LogLevel.ERROR.value:
29
+ webhook_url = slack_params['alert']['webhook_url']
30
+ else:
31
+ webhook_url = slack_params['info']['webhook_url']
32
+
33
+ if not webhook_url:
34
+ return
35
+
36
+ data = {
37
+ "username": "siglab_py",
38
+ "type": "section",
39
+ "blocks": [
40
+ {
41
+ "type": "header",
42
+ "text": { "type": "plain_text", "text": f"{title}" }
43
+ },
44
+ {
45
+ "type": "section",
46
+ "text": { "type": "mrkdwn", "text": message }
47
+ },
48
+ {
49
+ "type": "section",
50
+ "text": { "type": "plain_text", "text": footer }
51
+ }
52
+ ]
53
+ }
54
+
55
+ byte_size = str(sys.getsizeof(data, 2000))
56
+ req_headers = { 'Content-Length': byte_size, 'Content-Type': "application/json"}
57
+ rsp = requests.post(webhook_url, headers=req_headers, data=json.dumps(data))
58
+ if rsp.status_code != 200:
59
+ raise Exception(rsp.status_code, rsp.text)
@@ -0,0 +1,118 @@
1
+ import math
2
+
3
+ '''
4
+ pnl_percent_notional = Trade's current pnl in percent.
5
+
6
+ Examples,
7
+ y-axis:
8
+ max (i.e most tight) = 0%
9
+ sl_percent_trailing = 50% (Trailing stop loss in percent)
10
+
11
+ x-axis:
12
+ min TP = 1.5% <-- min TP
13
+ max TP = 2.5% <-- max TP
14
+
15
+ slope = (0-50)/(2.5-1.5) = -50/+1 = -50
16
+ effective_tp_trailing_percent = slope * (pnl_percent_notional - 1.5%) + sl_percent_trailing
17
+
18
+ Case 1. pnl_percent_notional = 1.5% (Trade starting off, only +50bps pnl. i.e. min TP)
19
+ effective_tp_trailing_percent = slope * (pnl_percent_notional - 1.5%) + sl_percent_trailing
20
+ = -50 * (1.5-1.5) + 50%
21
+ = 0 + 50
22
+ = 50% (Most loose)
23
+
24
+ Case 2. pnl_percent_notional = 2% (Deeper into profit, +200bps pnl)
25
+ effective_tp_trailing_percent = slope * (pnl_percent_notional - 1.5%) + sl_percent_trailing
26
+ = -50 * (2-1.5) +50%
27
+ = -25 + 50
28
+ = 25% (Somewhat tight)
29
+
30
+ Case 3. pnl_percent_notional = 2.5% (Very deep in profit, +250bps pnl. i.e. max TP)
31
+ effective_tp_trailing_percent = slope * (pnl_percent_notional - 1.5%) + sl_percent_trailing
32
+ = -50 * (2.5-1.5) +50%
33
+ = -50 + 50
34
+ = 0 (Most tight)
35
+
36
+ So you see, effective_tp_trailing_percent gets smaller and smaller as pnl approach max TP, finally zero.
37
+
38
+ How to use it?
39
+ if loss_trailing>=effective_tp_trailing_percent and pnl_percent_notional > tp_min_percent:
40
+ Fire trailing stops and take profit.
41
+
42
+ What's 'loss_trailing'? 'loss_trailing' is essentially pnl drop from max_unrealized_pnl_live.
43
+
44
+ Say, when trade started off:
45
+ unrealized_pnl_live = $80
46
+ max_unrealized_pnl_live = $100
47
+ loss_trailing = (1 - unrealized_pnl_live/max_unrealized_pnl_live) = (1-80/100) = 0.2 (Or 20%)
48
+
49
+ If pnl worsen:
50
+ unrealized_pnl_live = $40
51
+ max_unrealized_pnl_live = $100
52
+ loss_trailing = (1 - unrealized_pnl_live/max_unrealized_pnl_live) = (1-40/100) = 0.6 (Or 60%)
53
+
54
+ Have a look at this for a visual explaination how "Gradually tightened stops" works:
55
+ https://github.com/r0bbar/siglab/blob/master/siglab_py/tests/manual/trading_util_tests.ipynb
56
+ https://norman-lm-fung.medium.com/gradually-tightened-trailing-stops-f7854bf1e02b
57
+ '''
58
+ def calc_eff_trailing_sl(
59
+ tp_min_percent : float,
60
+ tp_max_percent : float,
61
+ sl_percent_trailing : float,
62
+ pnl_percent_notional : float,
63
+ default_effective_tp_trailing_percent : float = float('inf'), # inf: essentially saying, don't fire off trailing stop.
64
+ linear : bool = True,
65
+ pow : float = 5 # This is for non-linear trailing stops
66
+ ) -> float:
67
+ if pnl_percent_notional>tp_max_percent:
68
+ return 0
69
+ if pnl_percent_notional<tp_min_percent:
70
+ return default_effective_tp_trailing_percent
71
+
72
+ if linear:
73
+ slope = (0 - sl_percent_trailing) / (tp_max_percent - tp_min_percent)
74
+ effective_tp_trailing_percent = (
75
+ slope * (pnl_percent_notional - tp_min_percent) + sl_percent_trailing
76
+ if pnl_percent_notional>=tp_min_percent
77
+ else default_effective_tp_trailing_percent
78
+ )
79
+ else:
80
+ def y(
81
+ x : float,
82
+ x_shift : float,
83
+ pow : float
84
+ ) -> float:
85
+ return -1 * ( (x+x_shift)**pow)
86
+
87
+ y_min = y(
88
+ x=tp_min_percent,
89
+ x_shift=tp_min_percent,
90
+ pow=pow
91
+ )
92
+ y_max = y(
93
+ x=tp_max_percent,
94
+ x_shift=tp_min_percent,
95
+ pow=pow
96
+ )
97
+ y_shift = abs(y_max) - abs(y_min)
98
+
99
+ if y_shift!=0:
100
+ y_normalized = y(
101
+ x=pnl_percent_notional,
102
+ x_shift=tp_min_percent,
103
+ pow=pow
104
+ ) / y_shift
105
+ effective_tp_trailing_percent = (
106
+ y_normalized * sl_percent_trailing + sl_percent_trailing
107
+ if pnl_percent_notional>=tp_min_percent
108
+ else default_effective_tp_trailing_percent
109
+ )
110
+ else:
111
+ '''
112
+ y_shift = 0 when tp_max_percent==tp_min_percent.
113
+ If default_effective_tp_trailing_percent==float('inf'), essentially it means trailing stops won't fire.
114
+ Client side needs handle this.
115
+ '''
116
+ effective_tp_trailing_percent = default_effective_tp_trailing_percent
117
+
118
+ return effective_tp_trailing_percent
@@ -1,22 +1,19 @@
1
- Metadata-Version: 2.1
2
- Name: siglab-py
3
- Version: 0.1.29
1
+ Metadata-Version: 2.4
2
+ Name: siglab_py
3
+ Version: 0.6.12
4
4
  Summary: Market data fetches, TA calculations and generic order gateway.
5
5
  Author: r0bbarh00d
6
6
  Author-email: r0bbarh00d <r0bbarh00d@gmail.com>
7
7
  License: MIT
8
8
  Project-URL: Homepage, https://github.com/r0bbar/siglab/blob/master/siglab_py/README.md
9
- Classifier: Programming Language :: Python :: 3
10
- Classifier: License :: OSI Approved :: MIT License
11
- Classifier: Operating System :: OS Independent
12
9
  Requires-Python: >=3.9.19
13
10
  Description-Content-Type: text/markdown
14
11
  Requires-Dist: python-dotenv
15
12
  Requires-Dist: dotmap
16
- Requires-Dist: typing-extensions
13
+ Requires-Dist: typing_extensions
17
14
  Requires-Dist: arrow
18
15
  Requires-Dist: tzlocal
19
- Requires-Dist: nest-asyncio
16
+ Requires-Dist: nest_asyncio
20
17
  Requires-Dist: pandas
21
18
  Requires-Dist: numpy
22
19
  Requires-Dist: boto3
@@ -29,4 +26,3 @@ Requires-Dist: hurst
29
26
  Requires-Dist: redis
30
27
  Requires-Dist: redis-py-cluster
31
28
  Requires-Dist: kafka-python
32
-
@@ -0,0 +1,44 @@
1
+ siglab_py/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ siglab_py/constants.py,sha256=RlWh0-_sXHanfPIVLgDPLNbn9ljv3aZJYMv92Zd0jZI,701
3
+ siglab_py/exchanges/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ siglab_py/exchanges/any_exchange.py,sha256=Y-zue75ZSmu9Ga1fONbjBGLNH5pDHQI01hCSjuLBkAk,889
5
+ siglab_py/exchanges/binance.py,sha256=3hLeU5t4AXVLS2u6V8wlqsvdo3Uh00ebgx4MONy8X6k,1394
6
+ siglab_py/exchanges/deribit.py,sha256=gI5ezORDeu-Z1S8yxJj1Kj43OroK3ga9BckNijkuk8A,3361
7
+ siglab_py/exchanges/futubull.py,sha256=i0jNgl9BGJvgEIDeOkTfD52FaK77vX8axCKNYwJHwSI,21144
8
+ siglab_py/market_data_providers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
+ siglab_py/market_data_providers/aggregated_orderbook_provider.py,sha256=FZRobEBNRzcNGlOG3u38OVhmOZYlkNm8dVvR-S7Ii2g,23342
10
+ siglab_py/market_data_providers/candles_provider.py,sha256=FXvciwU7ONC0VdX-fggELIl2yqd7mRB2CjDeUsDzr-A,14145
11
+ siglab_py/market_data_providers/candles_ta_provider.py,sha256=az3tVjR4g0vhOCc8S5kGvjuNIpwxnXarUhMi62Z5Pzc,12013
12
+ siglab_py/market_data_providers/ccxt_candles_ta_to_csv.py,sha256=DHj51QTbkCmEd9RFNVhWWpsSPz1aLd6zTLqkUUbEkK0,11158
13
+ siglab_py/market_data_providers/deribit_options_expiry_provider.py,sha256=e9Ee8TmC8pXaid8-jouSLKIpuW6_JBBgwRTieI665yQ,8684
14
+ siglab_py/market_data_providers/futu_candles_ta_to_csv.py,sha256=SCWlI_mOuErpGP8Kxh5WKEoff9cqqxO19oLFLd04bTs,10964
15
+ siglab_py/market_data_providers/google_monitor.py,sha256=B08Aj1urL4M9hVUfjubVwTsFhfsj5-eFaf36lYqZ8-o,14028
16
+ siglab_py/market_data_providers/orderbooks_provider.py,sha256=b1XgPVSTU3y0Dxm5DOKjeldK-0ZrP15_iG3fzjBBBG8,16376
17
+ siglab_py/market_data_providers/tg_monitor.py,sha256=LY4oRm5qQ_XiuLk0RMVDc0Vdsi6CKE6O9hgeGm3VXBM,21995
18
+ siglab_py/market_data_providers/trigger_provider.py,sha256=b9B1RSBI8bFyGsM4DVXhEY8w08iTy8sPZkTqoy72VJE,2505
19
+ siglab_py/ordergateway/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
+ siglab_py/ordergateway/client.py,sha256=LvtrYirrdFOcKgTkvuqwdEN7r3nurjX320ESnk7tHE0,15095
21
+ siglab_py/ordergateway/encrypt_keys_util.py,sha256=U_M-jPrPYOTO_sU0bMVkO5ruNXge5vek8yUGa8jaE-g,1349
22
+ siglab_py/ordergateway/gateway.py,sha256=KAulWLZf8UYFo0esWwb4H8NmGnylkkckx4xBcF6-IZQ,47343
23
+ siglab_py/ordergateway/test_ordergateway.py,sha256=4PE2flp_soGcD3DrI7zJOzZndjkb6I5XaDrFNNq4Huo,4009
24
+ siglab_py/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
+ siglab_py/tests/integration/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
+ siglab_py/tests/integration/market_data_util_tests.py,sha256=XKO8CX9AF7xRjRvt4lb938v_s89d2IBLAXKfZDdUxdY,8705
27
+ siglab_py/tests/unit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
+ siglab_py/tests/unit/analytic_util_tests.py,sha256=tV7MsItlidwqdAQ_kD1000PsG4zIoQyQY1RdOn5XQAQ,5942
29
+ siglab_py/tests/unit/market_data_util_tests.py,sha256=A1y83itISmMJdn6wLpfwcr4tGola8wTf1D1xbelMvgw,2026
30
+ siglab_py/tests/unit/simple_math_tests.py,sha256=rWqq93W4Vlqmu0UeZCmSOfLirr0gPh2ASVIZ8O77qXY,9653
31
+ siglab_py/tests/unit/trading_util_tests.py,sha256=LiflZrduWXyLMbpSFQCaydA7jdJx3vFR-3KuKRRGhjQ,2927
32
+ siglab_py/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
+ siglab_py/util/analytic_util.py,sha256=KoRDlesLuYKkr0V0ytJLEqaDHh6Tyfr6GhaPQYFCU2U,65225
34
+ siglab_py/util/aws_util.py,sha256=KGmjHrr1rpnnxr33nXHNzTul4tvyyxl9p6gpwNv0Ygc,2557
35
+ siglab_py/util/market_data_util.py,sha256=zTvOaUUF2vmwQRcpQPq0c2Ix1gFAhx22Vps9pzJsOXI,36290
36
+ siglab_py/util/notification_util.py,sha256=tNZMUkkjz4q1CKqcQq62oEmZgHgNIwz2Iw9J22V22Zw,2668
37
+ siglab_py/util/retry_util.py,sha256=g-UU6pkPouWZZRZEqP99R2Z0lX5xzckYkzjwqqSDpVQ,922
38
+ siglab_py/util/simple_math.py,sha256=F7vGj0O2Y9EAGcMFR6SN1tTjBWO_a7YZeiTzk3eHaVI,8518
39
+ siglab_py/util/slack_notification_util.py,sha256=G27n-adbT3Q6oaHSMvu_Nom794rrda5PprSF-zvmzkM,1912
40
+ siglab_py/util/trading_util.py,sha256=dlIOzoMGnddLSFODcJ61EBH1Aeruq4IT2MsxIdFkV9I,5252
41
+ siglab_py-0.6.12.dist-info/METADATA,sha256=J4m8p-webGGFtNwcc9s9W5sE3vZFPkkGwxYj23CLWnI,829
42
+ siglab_py-0.6.12.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
43
+ siglab_py-0.6.12.dist-info/top_level.txt,sha256=AbD4VR9OqmMOGlMJLkAVPGQMtUPIQv0t1BF5xmcLJSk,10
44
+ siglab_py-0.6.12.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.41.2)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,34 +0,0 @@
1
- siglab_py/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- siglab_py/constants.py,sha256=YGNdEsWtQ99V2oltaynZTjM8VIboSfyIjDXJKSlhv4U,132
3
- siglab_py/exchanges/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- siglab_py/exchanges/any_exchange.py,sha256=Y-zue75ZSmu9Ga1fONbjBGLNH5pDHQI01hCSjuLBkAk,889
5
- siglab_py/exchanges/futubull.py,sha256=6M99G8ZYsIyDyoTvvleMT_WPyNYbb6q-t2KXp0Qyi6g,20422
6
- siglab_py/market_data_providers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
- siglab_py/market_data_providers/aggregated_orderbook_provider.py,sha256=FZRobEBNRzcNGlOG3u38OVhmOZYlkNm8dVvR-S7Ii2g,23342
8
- siglab_py/market_data_providers/candles_provider.py,sha256=fqHJjlECsBiBlpgyywrc4gTgxiROPNzZM8KxQBB5cOg,14139
9
- siglab_py/market_data_providers/candles_ta_provider.py,sha256=uiAhbEZZdTF-YulBHpSLwabos5LHCKU91NTiTmpUc0w,12001
10
- siglab_py/market_data_providers/ccxt_candles_ta_to_csv.py,sha256=8-cKcZXFBHLX_-KmPXBKv1QNqJYUdDp6iqa1miVlpF8,11139
11
- siglab_py/market_data_providers/deribit_options_expiry_provider.py,sha256=e9Ee8TmC8pXaid8-jouSLKIpuW6_JBBgwRTieI665yQ,8684
12
- siglab_py/market_data_providers/futu_candles_ta_to_csv.py,sha256=S4GXaJ7AveEh-Cm9-VhENBdlj_1CfyBTrQO7acTqfUE,10226
13
- siglab_py/market_data_providers/orderbooks_provider.py,sha256=olt-3LIkoyzQWfNNQRhJtKibLbkTutt_q_rCCTM7i1g,16216
14
- siglab_py/market_data_providers/test_provider.py,sha256=wBLCgcWjs7FGZJXWsNyn30lkOLa_cgpuvqRakMC0wbA,2221
15
- siglab_py/ordergateway/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
- siglab_py/ordergateway/client.py,sha256=qh4vrCJm8iITKAl07lisZxJ3V4JkbsYlmIBMYihfUaU,9575
17
- siglab_py/ordergateway/encrypt_keys_util.py,sha256=-qi87db8To8Yf1WS1Q_Cp2Ya7ZqgWlRqSHfNXCM7wE4,1339
18
- siglab_py/ordergateway/gateway.py,sha256=FoE-OUBrHJ2fdKAfvGue1yQ2P67falWoyoEGCV8ScfA,37219
19
- siglab_py/ordergateway/test_ordergateway.py,sha256=_Gz2U_VqljogGWqGyNDYYls1INqUiig9veyPttfGRpg,3901
20
- siglab_py/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
- siglab_py/tests/integration/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
- siglab_py/tests/integration/market_data_util_tests.py,sha256=X0CiSMDfsafKcmjVKknA03vUUbMV0fAZweb3D01ikYI,7174
23
- siglab_py/tests/unit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
- siglab_py/tests/unit/analytic_util_tests.py,sha256=BzT__hxfqXMRAKvqtYDVYNrcMGGDF3-gFoXhxiJ0Lew,3703
25
- siglab_py/tests/unit/market_data_util_tests.py,sha256=A1y83itISmMJdn6wLpfwcr4tGola8wTf1D1xbelMvgw,2026
26
- siglab_py/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
27
- siglab_py/util/analytic_util.py,sha256=K5YYfvNR5xHjIPUEDJ9c9L9yzolOI2KEYCh6GngtSdk,44042
28
- siglab_py/util/aws_util.py,sha256=KGmjHrr1rpnnxr33nXHNzTul4tvyyxl9p6gpwNv0Ygc,2557
29
- siglab_py/util/market_data_util.py,sha256=qRKsFKZMYsaC18ImSWug6dRAOo2_GS1NZM-j0EYMViE,19319
30
- siglab_py/util/retry_util.py,sha256=mxYuRFZRZoaQQjENcwPmxhxixtd1TFvbxIdPx4RwfRc,743
31
- siglab_py-0.1.29.dist-info/METADATA,sha256=63E7VJzd54t9AeCaL--o7uHDGBlG4TPB6Ij9_8mHT5Q,980
32
- siglab_py-0.1.29.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
33
- siglab_py-0.1.29.dist-info/top_level.txt,sha256=AbD4VR9OqmMOGlMJLkAVPGQMtUPIQv0t1BF5xmcLJSk,10
34
- siglab_py-0.1.29.dist-info/RECORD,,