siglab-py 0.5.25__py3-none-any.whl → 0.5.27__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
@@ -9,4 +9,15 @@ class LogLevel(enum.Enum):
9
9
  WARNING = 30
10
10
  INFO = 20
11
11
  DEBUG = 10
12
- NOTSET = 0
12
+ NOTSET = 0
13
+
14
+ class TrendDirection(enum.Enum):
15
+ UNDEFINED = 0
16
+ HIGHER_HIGHS = 1
17
+ LOWER_HIGHS = 2
18
+ SIDEWAYS = 3
19
+ HIGHER_LOWS = 4
20
+ LOWER_LOWS = 5
21
+
22
+ def to_string(self) -> str:
23
+ return self.name.lower() if self != TrendDirection.UNDEFINED else ''
@@ -57,14 +57,14 @@ class AnalyticUtilTests(unittest.TestCase):
57
57
  'ema_volume_short_periods', 'ema_volume_long_periods',
58
58
  'max_short_periods', 'max_long_periods', 'idmax_short_periods', 'idmax_long_periods', 'min_short_periods', 'min_long_periods', 'idmin_short_periods', 'idmin_long_periods',
59
59
  'price_swing_short_periods', 'price_swing_long_periods',
60
- 'higher_highs_long_periods', 'lower_lows_long_periods', 'higher_highs_short_periods', 'lower_lows_short_periods',
60
+ 'trend_from_highs_long_periods', 'trend_from_lows_long_periods', 'trend_from_highs_short_periods', 'trend_from_lows_short_periods',
61
61
  'h_l', 'h_pc', 'l_pc', 'tr', 'atr', 'atr_avg_short_periods', 'atr_avg_long_periods',
62
62
  'hurst_exp',
63
63
  'boillenger_upper', 'boillenger_lower', 'boillenger_channel_height', 'boillenger_upper_agg', 'boillenger_lower_agg', 'boillenger_channel_height_agg',
64
64
  'aggressive_up', 'aggressive_up_index', 'aggressive_up_candle_height', 'aggressive_up_candle_high', 'aggressive_up_candle_low', 'aggressive_down', 'aggressive_down_index', 'aggressive_down_candle_height', 'aggressive_down_candle_high', 'aggressive_down_candle_low',
65
65
  'fvg_low', 'fvg_high', 'fvg_gap', 'fvg_mitigated',
66
66
  'close_delta', 'close_delta_percent', 'up', 'down',
67
- 'rsi', 'ema_rsi', 'rsi_max', 'rsi_idmax', 'rsi_min', 'rsi_idmin', 'rsi_trend', 'rsi_higher_highs', 'rsi_lower_lows', 'rsi_divergence',
67
+ 'rsi', 'ema_rsi', 'rsi_max', 'rsi_idmax', 'rsi_min', 'rsi_idmin', 'rsi_trend', 'rsi_trend_from_highs', 'rsi_trend_from_lows', 'rsi_divergence',
68
68
  'typical_price',
69
69
  'money_flow', 'money_flow_positive', 'money_flow_negative', 'positive_flow_sum', 'negative_flow_sum', 'money_flow_ratio', 'mfi',
70
70
  'macd', 'signal', 'macd_minus_signal',
@@ -1,6 +1,7 @@
1
1
  import tzlocal
2
2
  from datetime import datetime, timezone
3
3
  from typing import List, Dict, Union, NoReturn, Any, Tuple
4
+ from enum import Enum
4
5
  from pathlib import Path
5
6
  import math
6
7
  import pandas as pd
@@ -11,6 +12,7 @@ from ccxt.base.exchange import Exchange as CcxtExchange
11
12
  from ccxt import deribit
12
13
 
13
14
  from siglab_py.util.market_data_util import fix_column_types
15
+ from constants import TrendDirection
14
16
 
15
17
  # Fibonacci
16
18
  MAGIC_FIB_LEVELS = [0, 0.236, 0.382, 0.5, 0.618, 0.786, 1.00, 1.618, 2.618, 3.618, 4.236]
@@ -51,30 +53,33 @@ def calculate_slope(
51
53
  pd_data[f"normalized_{slope_col_name}_idmin"] = normalized_slope_rolling.apply(lambda x : x.idxmin())
52
54
  pd_data[f"normalized_{slope_col_name}_idmax"] = normalized_slope_rolling.apply(lambda x : x.idxmax())
53
55
 
54
- def higherhighs(series: pd.Series) -> Union[str, None]:
55
- unique_maxima = series.dropna()[series.dropna().diff().ne(0)]
56
+ def trend_from_highs(series: np.ndarray) -> float:
57
+ valid_series = series[~np.isnan(series)]
58
+ unique_maxima = valid_series[np.concatenate(([True], np.diff(valid_series) != 0))]
56
59
  if len(unique_maxima) < 2:
57
- return None
58
- first, last = unique_maxima.iloc[0], unique_maxima.iloc[-1]
60
+ return TrendDirection.UNDEFINED.value
61
+ first, last = unique_maxima[0], unique_maxima[-1]
59
62
  if first > last:
60
- return 'lower_highs'
63
+ return TrendDirection.LOWER_HIGHS.value
61
64
  elif first < last:
62
- return 'higher_highs'
65
+ return TrendDirection.HIGHER_HIGHS.value
63
66
  else:
64
- return 'sideways'
67
+ return TrendDirection.SIDEWAYS.value
65
68
 
66
- def lowerlows(series: pd.Series) -> Union[str, None]:
67
- unique_minima = series.dropna()[series.dropna().diff().ne(0)]
69
+ def trend_from_lows(series: np.ndarray) -> float:
70
+ valid_series = series[~np.isnan(series)]
71
+ unique_minima = valid_series[np.concatenate(([True], np.diff(valid_series) != 0))]
68
72
  if len(unique_minima) < 2:
69
- return None
70
- first, last = unique_minima.iloc[0], unique_minima.iloc[-1]
73
+ return TrendDirection.UNDEFINED.value
74
+ first, last = unique_minima[0], unique_minima[-1]
71
75
  if first > last:
72
- return 'lower_lows'
76
+ return TrendDirection.LOWER_LOWS.value
73
77
  elif first < last:
74
- return 'higher_lows'
78
+ return TrendDirection.HIGHER_LOWS.value
75
79
  else:
76
- return 'sideways'
77
-
80
+ return TrendDirection.SIDEWAYS.value
81
+
82
+
78
83
  '''
79
84
  compute_candles_stats will calculate typical/basic technical indicators using in many trading strategies:
80
85
  a. Basic SMA/EMAs (And slopes)
@@ -204,10 +209,26 @@ def compute_candles_stats(
204
209
  pd_candles['min_long_periods'] - pd_candles['max_long_periods'] # Down swing (negative)
205
210
  )
206
211
 
207
- pd_candles['higher_highs_long_periods'] = higherhighs(pd_candles['max_long_periods'])
208
- pd_candles['lower_lows_long_periods'] = lowerlows(pd_candles['min_long_periods'])
209
- pd_candles['higher_highs_short_periods'] = higherhighs(pd_candles['max_short_periods'])
210
- pd_candles['lower_lows_short_periods'] = lowerlows(pd_candles['min_short_periods'])
212
+ pd_candles['trend_from_highs_long_periods'] = np.where(
213
+ pd.isna(pd_candles['max_long_periods']),
214
+ None, # type: ignore
215
+ pd_candles['max_long_periods'].rolling(window=sliding_window_how_many_candles).apply(trend_from_highs, raw=True)
216
+ )
217
+ pd_candles['trend_from_lows_long_periods'] = np.where(
218
+ pd.isna(pd_candles['min_long_periods']),
219
+ None, # type: ignore
220
+ pd_candles['min_long_periods'].rolling(window=sliding_window_how_many_candles).apply(trend_from_lows, raw=True)
221
+ )
222
+ pd_candles['trend_from_highs_short_periods'] = np.where(
223
+ pd.isna(pd_candles['max_short_periods']),
224
+ None, # type: ignore
225
+ pd_candles['max_short_periods'].rolling(window=int(sliding_window_how_many_candles/slow_fast_interval_ratio)).apply(trend_from_highs, raw=True)
226
+ )
227
+ pd_candles['trend_from_lows_short_periods'] = np.where(
228
+ pd.isna(pd_candles['min_short_periods']),
229
+ None, # type: ignore
230
+ pd_candles['min_short_periods'].rolling(window=int(sliding_window_how_many_candles/slow_fast_interval_ratio)).apply(trend_from_lows, raw=True)
231
+ )
211
232
 
212
233
  # ATR https://medium.com/codex/detecting-ranging-and-trending-markets-with-choppiness-index-in-python-1942e6450b58
213
234
  pd_candles.loc[:,'h_l'] = pd_candles['high'] - pd_candles['low']
@@ -425,18 +446,26 @@ def compute_candles_stats(
425
446
 
426
447
  pd_candles['rsi_trend'] = pd_candles.apply(lambda row: rsi_trend(row), axis=1)
427
448
 
428
- pd_candles['rsi_higher_highs'] = higherhighs(pd_candles['rsi_max'])
429
- pd_candles['rsi_lower_lows'] = lowerlows(pd_candles['rsi_min'])
449
+ pd_candles['rsi_trend_from_highs'] = np.where(
450
+ pd.isna(pd_candles['rsi_max']),
451
+ None, # type: ignore
452
+ pd_candles['rsi_max'].rolling(window=rsi_trend_sliding_window_how_many_candles).apply(trend_from_highs, raw=True)
453
+ )
454
+ pd_candles['rsi_trend_from_lows'] = np.where(
455
+ pd.isna(pd_candles['rsi_min']),
456
+ None, # type: ignore
457
+ pd_candles['rsi_min'].rolling(window=rsi_trend_sliding_window_how_many_candles).apply(trend_from_lows, raw=True)
458
+ )
430
459
 
431
460
  def _rsi_divergence(row):
432
- # Bullish Divergence: Price lower low + RSI higher low
433
- if row['lower_lows_long_periods']=='lower_lows' and row['rsi_lower_lows']=='higher_lows':
461
+ trend_from_highs_long_periods = TrendDirection(row['trend_from_highs_long_periods']) if row['trend_from_highs_long_periods'] is not None and not pd.isna(row['trend_from_highs_long_periods']) else None # type: ignore
462
+ rsi_trend_from_highs = TrendDirection(row['rsi_trend_from_highs']) if row['rsi_trend_from_highs'] is not None and not pd.isna(row['rsi_trend_from_highs']) else None # type: ignore
463
+
464
+ if trend_from_highs_long_periods and rsi_trend_from_highs and trend_from_highs_long_periods == TrendDirection.LOWER_HIGHS and rsi_trend_from_highs == TrendDirection.HIGHER_HIGHS:
434
465
  return 'bullish_divergence'
435
- # Bearish Divergence: Price higher high + RSI lower high
436
- elif row['higher_highs_long_periods']=='higher_highs' and row['rsi_higher_highs']=='lower_highs':
466
+ elif trend_from_highs_long_periods and rsi_trend_from_highs and trend_from_highs_long_periods == TrendDirection.HIGHER_HIGHS and rsi_trend_from_highs == TrendDirection.LOWER_HIGHS:
437
467
  return 'bearish_divergence'
438
- else:
439
- return 'no_divergence'
468
+ return 'no_divergence'
440
469
  pd_candles['rsi_divergence'] = pd_candles.apply(_rsi_divergence, axis=1)
441
470
 
442
471
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: siglab_py
3
- Version: 0.5.25
3
+ Version: 0.5.27
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=5isS_9WNoqpq9nC3ME5I5j7hJI9HAImij2bcgVVyWeY,275
2
+ siglab_py/constants.py,sha256=atSjvM0wv_f1wrzWHE0DcbUn6fqLmg-51BpfMJR1QB4,547
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=f-_trzvuH5NaiG-PzyiNqZ12w7J-ARi_-xcr78rFU4E,20510
@@ -22,18 +22,18 @@ siglab_py/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
22
  siglab_py/tests/integration/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
23
  siglab_py/tests/integration/market_data_util_tests.py,sha256=p-RWIJZLyj0lAdfi4QTIeAttCm_e8mEVWFKh4OWuogU,7189
24
24
  siglab_py/tests/unit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
- siglab_py/tests/unit/analytic_util_tests.py,sha256=aY6OvICgIrNz-Is4lPPlBtDBF0c8TC7NDPTwhzRJeVI,4237
25
+ siglab_py/tests/unit/analytic_util_tests.py,sha256=fstWJxgmm6xyn5xuVf45nVio1l_KU3kVhQLWxr4sufI,4264
26
26
  siglab_py/tests/unit/market_data_util_tests.py,sha256=A1y83itISmMJdn6wLpfwcr4tGola8wTf1D1xbelMvgw,2026
27
27
  siglab_py/tests/unit/trading_util_tests.py,sha256=9DZmTZlW55lPtNfTCukgDdiyBiMYv9R4mEFWJIJiTNg,3870
28
28
  siglab_py/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
29
- siglab_py/util/analytic_util.py,sha256=Zrpr94ceyVBNBAh-q2whb8PmVHMumU3R7jWOD3dQqkk,51727
29
+ siglab_py/util/analytic_util.py,sha256=4U471ANXvH-QEXYiwqCA6lLULyqKPnWMxTNsSWqEAik,54009
30
30
  siglab_py/util/aws_util.py,sha256=KGmjHrr1rpnnxr33nXHNzTul4tvyyxl9p6gpwNv0Ygc,2557
31
31
  siglab_py/util/market_data_util.py,sha256=mUXg4uaiX3b6_klgJWIEgnUQU4IUd6CwTOqKLiQWRlU,31307
32
32
  siglab_py/util/notification_util.py,sha256=vySgHjpHgwFDLW0tHSi_AGh9JBbPc25IUgvWxmjAeT8,2658
33
33
  siglab_py/util/retry_util.py,sha256=g-UU6pkPouWZZRZEqP99R2Z0lX5xzckYkzjwqqSDpVQ,922
34
34
  siglab_py/util/slack_notification_util.py,sha256=G27n-adbT3Q6oaHSMvu_Nom794rrda5PprSF-zvmzkM,1912
35
35
  siglab_py/util/trading_util.py,sha256=-TGNgJdy4HMDPgq31KQn_lRawFxuXnFU5NnLRb1XM5o,5757
36
- siglab_py-0.5.25.dist-info/METADATA,sha256=MK5IGmTLX1Iz9R7zWNDq16WCVxr2WecGgfHkSP6t3_w,829
37
- siglab_py-0.5.25.dist-info/WHEEL,sha256=lTU6B6eIfYoiQJTZNc-fyaR6BpL6ehTzU3xGYxn2n8k,91
38
- siglab_py-0.5.25.dist-info/top_level.txt,sha256=AbD4VR9OqmMOGlMJLkAVPGQMtUPIQv0t1BF5xmcLJSk,10
39
- siglab_py-0.5.25.dist-info/RECORD,,
36
+ siglab_py-0.5.27.dist-info/METADATA,sha256=Yn2_HCYJ4f0LyE14xyJMzn30_DZQ2XAVauT5Uy30Lrg,829
37
+ siglab_py-0.5.27.dist-info/WHEEL,sha256=lTU6B6eIfYoiQJTZNc-fyaR6BpL6ehTzU3xGYxn2n8k,91
38
+ siglab_py-0.5.27.dist-info/top_level.txt,sha256=AbD4VR9OqmMOGlMJLkAVPGQMtUPIQv0t1BF5xmcLJSk,10
39
+ siglab_py-0.5.27.dist-info/RECORD,,