siglab-py 0.3.3__py3-none-any.whl → 0.3.6__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/market_data_providers/ccxt_candles_ta_to_csv.py +4 -4
- siglab_py/util/analytic_util.py +62 -29
- siglab_py/util/market_data_util.py +49 -1
- siglab_py/util/test_market_data_analytic_util.py +25 -13
- {siglab_py-0.3.3.dist-info → siglab_py-0.3.6.dist-info}/METADATA +1 -1
- {siglab_py-0.3.3.dist-info → siglab_py-0.3.6.dist-info}/RECORD +8 -8
- {siglab_py-0.3.3.dist-info → siglab_py-0.3.6.dist-info}/WHEEL +0 -0
- {siglab_py-0.3.3.dist-info → siglab_py-0.3.6.dist-info}/top_level.txt +0 -0
|
@@ -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
|
|
29
|
+
python ccxt_candles_ta_to_csv.py --exchange_name okx --symbol BTC/USDT:USDT --candle_size 1h --end_date "2025-04-22 0:0:0" --start_date "2024-01-01 0:0:0" --default_type linear --compute_ta Y --pypy_compatible N
|
|
30
30
|
|
|
31
31
|
(Remember: python -mpip install siglab_py)
|
|
32
32
|
|
|
@@ -60,8 +60,8 @@ If debugging from VSCode, launch.json:
|
|
|
60
60
|
"args" : [
|
|
61
61
|
"--exchange_name", "bybit",
|
|
62
62
|
"--symbol", "BTC/USDT:USDT",
|
|
63
|
-
"--end_date", "2025-
|
|
64
|
-
"--start_date", "2024-
|
|
63
|
+
"--end_date", "2025-04-22 0:0:0",
|
|
64
|
+
"--start_date", "2024-01-01 0:0:0",
|
|
65
65
|
"--default_type", "linear",
|
|
66
66
|
"--compute_ta", "Y",
|
|
67
67
|
"--pypy_compatible", "N"
|
|
@@ -83,7 +83,7 @@ param : Dict = {
|
|
|
83
83
|
'start_date' : start_date,
|
|
84
84
|
'end_date' : end_date,
|
|
85
85
|
'exchange_params' : {
|
|
86
|
-
'rateLimit' :
|
|
86
|
+
'rateLimit' : 300, # in ms
|
|
87
87
|
'options' : {
|
|
88
88
|
'defaultType' : "linear"
|
|
89
89
|
}
|
siglab_py/util/analytic_util.py
CHANGED
|
@@ -32,6 +32,25 @@ def estimate_fib_retracement(
|
|
|
32
32
|
|
|
33
33
|
return retracement_price
|
|
34
34
|
|
|
35
|
+
def calculate_slope(
|
|
36
|
+
pd_data : pd.DataFrame,
|
|
37
|
+
src_col_name : str,
|
|
38
|
+
slope_col_name : str,
|
|
39
|
+
sliding_window_how_many_candles : int
|
|
40
|
+
):
|
|
41
|
+
import statsmodels.api as sm # in-compatible with pypy
|
|
42
|
+
|
|
43
|
+
X = sm.add_constant(range(len(pd_data[src_col_name])))
|
|
44
|
+
rolling_slope = pd_data[src_col_name].rolling(window=sliding_window_how_many_candles).apply(lambda x: sm.OLS(x, X[:len(x)]).fit().params[1], raw=False)
|
|
45
|
+
pd_data[slope_col_name] = rolling_slope
|
|
46
|
+
max_abs_slope = pd_data[slope_col_name].abs().rolling(window=sliding_window_how_many_candles).max()
|
|
47
|
+
pd_data[f"normalized_{slope_col_name}"] = pd_data[slope_col_name] / max_abs_slope
|
|
48
|
+
normalized_slope_rolling = pd_data[f"normalized_{slope_col_name}"].rolling(window=sliding_window_how_many_candles)
|
|
49
|
+
pd_data[f"normalized_{slope_col_name}_min"] = normalized_slope_rolling.min()
|
|
50
|
+
pd_data[f"normalized_{slope_col_name}_max"] = normalized_slope_rolling.max()
|
|
51
|
+
pd_data[f"normalized_{slope_col_name}_idmin"] = normalized_slope_rolling.apply(lambda x : x.idxmin())
|
|
52
|
+
pd_data[f"normalized_{slope_col_name}_idmax"] = normalized_slope_rolling.apply(lambda x : x.idxmax())
|
|
53
|
+
|
|
35
54
|
'''
|
|
36
55
|
compute_candles_stats will calculate typical/basic technical indicators using in many trading strategies:
|
|
37
56
|
a. Basic SMA/EMAs (And slopes)
|
|
@@ -365,40 +384,54 @@ def compute_candles_stats(
|
|
|
365
384
|
pd_candles['macd_minus_signal'] = pd_candles['macd'] - pd_candles['signal']
|
|
366
385
|
|
|
367
386
|
if not pypy_compat:
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
pd_candles['normalized_close_short_slope'] = pd_candles['close_long_slope'] / (2 * max_abs_slope)
|
|
387
|
+
calculate_slope(
|
|
388
|
+
pd_data=pd_candles,
|
|
389
|
+
src_col_name='close',
|
|
390
|
+
slope_col_name='close_short_slope',
|
|
391
|
+
sliding_window_how_many_candles=int(sliding_window_how_many_candles/slow_fast_interval_ratio)
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
calculate_slope(
|
|
395
|
+
pd_data=pd_candles,
|
|
396
|
+
src_col_name='close',
|
|
397
|
+
slope_col_name='close_long_slope',
|
|
398
|
+
sliding_window_how_many_candles=int(sliding_window_how_many_candles)
|
|
399
|
+
)
|
|
382
400
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
401
|
+
calculate_slope(
|
|
402
|
+
pd_data=pd_candles,
|
|
403
|
+
src_col_name='ema_short_periods',
|
|
404
|
+
slope_col_name='ema_short_slope',
|
|
405
|
+
sliding_window_how_many_candles=int(sliding_window_how_many_candles/slow_fast_interval_ratio)
|
|
406
|
+
)
|
|
386
407
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
408
|
+
calculate_slope(
|
|
409
|
+
pd_data=pd_candles,
|
|
410
|
+
src_col_name='ema_long_periods',
|
|
411
|
+
slope_col_name='ema_long_slope',
|
|
412
|
+
sliding_window_how_many_candles=int(sliding_window_how_many_candles)
|
|
413
|
+
)
|
|
390
414
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
415
|
+
calculate_slope(
|
|
416
|
+
pd_data=pd_candles,
|
|
417
|
+
src_col_name='boillenger_upper',
|
|
418
|
+
slope_col_name='boillenger_upper_slope',
|
|
419
|
+
sliding_window_how_many_candles=int(sliding_window_how_many_candles)
|
|
420
|
+
)
|
|
394
421
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
422
|
+
calculate_slope(
|
|
423
|
+
pd_data=pd_candles,
|
|
424
|
+
src_col_name='boillenger_lower',
|
|
425
|
+
slope_col_name='boillenger_lower_slope',
|
|
426
|
+
sliding_window_how_many_candles=int(sliding_window_how_many_candles)
|
|
427
|
+
)
|
|
398
428
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
429
|
+
calculate_slope(
|
|
430
|
+
pd_data=pd_candles,
|
|
431
|
+
src_col_name='ema_rsi',
|
|
432
|
+
slope_col_name='ema_rsi_slope',
|
|
433
|
+
sliding_window_how_many_candles=int(sliding_window_how_many_candles)
|
|
434
|
+
)
|
|
402
435
|
|
|
403
436
|
pd_candles['regular_divergence'] = (
|
|
404
437
|
(pd_candles['ema_long_slope'] > 0) & (pd_candles['ema_rsi_slope'] < 0) |
|
|
@@ -626,4 +626,52 @@ def fetch_deribit_btc_option_expiries(
|
|
|
626
626
|
'index_price' : index_price,
|
|
627
627
|
'by_expiry' : sorted_expiry_data, # type: ignore Otherwise, Error: Type "dict[str, list[tuple[str, float]] | dict[str, Dict[Unknown, Unknown]]]" is not assignable to return type "Dict[str, Dict[str, float] | Dict[str, Dict[str, str | float]]]"
|
|
628
628
|
'by_expiry_and_strike' : expiry_data_breakdown_by_strike
|
|
629
|
-
}
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
def build_pair_candles(
|
|
632
|
+
pd_candles1 : pd.DataFrame,
|
|
633
|
+
pd_candles2 : pd.DataFrame
|
|
634
|
+
) -> pd.DataFrame:
|
|
635
|
+
min_timestamp_ms1 = int(pd_candles1.iloc[0]['timestamp_ms'])
|
|
636
|
+
max_timestamp_ms1 = int(pd_candles1.iloc[-1]['timestamp_ms'])
|
|
637
|
+
min_timestamp_ms2 = int(pd_candles2.iloc[0]['timestamp_ms'])
|
|
638
|
+
max_timestamp_ms2 = int(pd_candles2.iloc[-1]['timestamp_ms'])
|
|
639
|
+
|
|
640
|
+
pd_candles1 = pd_candles1[(pd_candles1.timestamp_ms>=min_timestamp_ms2) & (pd_candles1.timestamp_ms<=max_timestamp_ms2) & (~pd_candles1.timestamp_ms.isna()) ]
|
|
641
|
+
pd_candles2 = pd_candles2[(pd_candles2.timestamp_ms>=min_timestamp_ms1) & (pd_candles2.timestamp_ms<=max_timestamp_ms1) & (~pd_candles2.timestamp_ms.isna())]
|
|
642
|
+
assert(pd_candles1.shape[0]==pd_candles2.shape[0])
|
|
643
|
+
|
|
644
|
+
pd_candles1['timestamp_ms_gap'] = pd_candles1['timestamp_ms'] - pd_candles1['timestamp_ms'].shift(1)
|
|
645
|
+
timestamp_ms_gap = pd_candles1.iloc[-1]['timestamp_ms_gap']
|
|
646
|
+
|
|
647
|
+
assert(pd_candles1[~pd_candles1.timestamp_ms_gap.isna()][pd_candles1.timestamp_ms_gap!=timestamp_ms_gap].shape[0]==0)
|
|
648
|
+
pd_candles1.drop(columns=['timestamp_ms_gap'], inplace=True)
|
|
649
|
+
|
|
650
|
+
pd_candles2['timestamp_ms_gap'] = pd_candles2['timestamp_ms'] - pd_candles2['timestamp_ms'].shift(1)
|
|
651
|
+
timestamp_ms_gap = pd_candles2.iloc[-1]['timestamp_ms_gap']
|
|
652
|
+
assert(pd_candles2[~pd_candles2.timestamp_ms_gap.isna()][pd_candles2.timestamp_ms_gap!=timestamp_ms_gap].shape[0]==0)
|
|
653
|
+
pd_candles2.drop(columns=['timestamp_ms_gap'], inplace=True)
|
|
654
|
+
|
|
655
|
+
min_timestamp_ms1 = int(pd_candles1.iloc[0]['timestamp_ms'])
|
|
656
|
+
max_timestamp_ms1 = int(pd_candles1.iloc[-1]['timestamp_ms'])
|
|
657
|
+
min_timestamp_ms2 = int(pd_candles2.iloc[0]['timestamp_ms'])
|
|
658
|
+
max_timestamp_ms2 = int(pd_candles2.iloc[-1]['timestamp_ms'])
|
|
659
|
+
assert(min_timestamp_ms1==min_timestamp_ms2)
|
|
660
|
+
assert(max_timestamp_ms1==max_timestamp_ms2)
|
|
661
|
+
assert(pd_candles1.shape[0]==pd_candles2.shape[0])
|
|
662
|
+
|
|
663
|
+
if len([ col for col in pd_candles1.columns if col[-2:]=='_1' ]) == 0:
|
|
664
|
+
pd_candles1.columns = [str(col) + '_1' for col in pd_candles1.columns]
|
|
665
|
+
|
|
666
|
+
if len([ col for col in pd_candles2.columns if col[-2:]=='_2' ]) == 0:
|
|
667
|
+
pd_candles2.columns = [str(col) + '_2' for col in pd_candles2.columns]
|
|
668
|
+
|
|
669
|
+
pd_candles1.reset_index(drop=True, inplace=True)
|
|
670
|
+
pd_candles2.reset_index(drop=True, inplace=True)
|
|
671
|
+
pd_candles = pd.concat([pd_candles1, pd_candles2], axis=1)
|
|
672
|
+
pd_candles['timestamp_ms_gap'] = pd_candles['timestamp_ms_1'] - pd_candles['timestamp_ms_2']
|
|
673
|
+
assert(pd_candles[pd_candles.timestamp_ms_gap!=0].shape[0]==0)
|
|
674
|
+
|
|
675
|
+
return pd_candles
|
|
676
|
+
|
|
677
|
+
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
from datetime import datetime
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
import time
|
|
3
|
+
from typing import Union
|
|
4
4
|
import pandas as pd
|
|
5
|
-
import matplotlib.pyplot as plt
|
|
6
5
|
|
|
7
6
|
from ccxt.okx import okx
|
|
8
7
|
|
|
@@ -14,6 +13,10 @@ base_ccy : str = "BTC"
|
|
|
14
13
|
# ticker = "OL/USDT:USDT"
|
|
15
14
|
ticker = f"{base_ccy}/USDT:USDT"
|
|
16
15
|
|
|
16
|
+
reload_raw_candles : bool = True
|
|
17
|
+
raw_candles_file : str = f"{base_ccy}_raw_candles.csv"
|
|
18
|
+
candles_with_ta_file : str = f"{base_ccy}_candles_ta.csv"
|
|
19
|
+
|
|
17
20
|
param = {
|
|
18
21
|
'rateLimit' : 100, # In ms
|
|
19
22
|
'options' : {
|
|
@@ -23,7 +26,7 @@ param = {
|
|
|
23
26
|
exchange = okx(param) # type: ignore
|
|
24
27
|
|
|
25
28
|
start_date : datetime = datetime(2024,1,1)
|
|
26
|
-
end_date : datetime = datetime(2025,4,
|
|
29
|
+
end_date : datetime = datetime(2025,4,22)
|
|
27
30
|
candle_size : str = '1h'
|
|
28
31
|
ma_long_intervals : int = 24*30
|
|
29
32
|
ma_short_intervals : int = 24
|
|
@@ -33,15 +36,20 @@ pypy_compatible : bool = False
|
|
|
33
36
|
markets = exchange.load_markets()
|
|
34
37
|
assert(ticker in markets)
|
|
35
38
|
|
|
36
|
-
|
|
37
|
-
start_ts=int(start_date.timestamp()),
|
|
38
|
-
end_ts=int(end_date.timestamp()),
|
|
39
|
-
exchange=exchange,
|
|
40
|
-
normalized_symbols=[ ticker ],
|
|
41
|
-
candle_size=candle_size
|
|
42
|
-
)[ ticker ]
|
|
39
|
+
start = time.time()
|
|
43
40
|
|
|
44
|
-
|
|
41
|
+
if reload_raw_candles:
|
|
42
|
+
pd_candles: Union[pd.DataFrame, None] = fetch_candles(
|
|
43
|
+
start_ts=int(start_date.timestamp()),
|
|
44
|
+
end_ts=int(end_date.timestamp()),
|
|
45
|
+
exchange=exchange,
|
|
46
|
+
normalized_symbols=[ ticker ],
|
|
47
|
+
candle_size=candle_size
|
|
48
|
+
)[ ticker ]
|
|
49
|
+
|
|
50
|
+
pd_candles.to_csv(raw_candles_file) # type: ignore
|
|
51
|
+
else:
|
|
52
|
+
pd_candles = pd.read_csv(raw_candles_file) # type: ignore
|
|
45
53
|
|
|
46
54
|
compute_candles_stats(
|
|
47
55
|
pd_candles=pd_candles, # type: ignore
|
|
@@ -49,4 +57,8 @@ compute_candles_stats(
|
|
|
49
57
|
sliding_window_how_many_candles=ma_long_intervals,
|
|
50
58
|
slow_fast_interval_ratio=(ma_long_intervals/ma_short_intervals),
|
|
51
59
|
pypy_compat=pypy_compatible
|
|
52
|
-
)
|
|
60
|
+
)
|
|
61
|
+
pd_candles.to_csv(candles_with_ta_file) # type: ignore
|
|
62
|
+
|
|
63
|
+
compute_candles_stats_elapsed_ms = int((time.time() - start) *1000)
|
|
64
|
+
print(f"elapsed (ms): {compute_candles_stats_elapsed_ms}")
|
|
@@ -7,7 +7,7 @@ siglab_py/market_data_providers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm
|
|
|
7
7
|
siglab_py/market_data_providers/aggregated_orderbook_provider.py,sha256=FZRobEBNRzcNGlOG3u38OVhmOZYlkNm8dVvR-S7Ii2g,23342
|
|
8
8
|
siglab_py/market_data_providers/candles_provider.py,sha256=fqHJjlECsBiBlpgyywrc4gTgxiROPNzZM8KxQBB5cOg,14139
|
|
9
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=
|
|
10
|
+
siglab_py/market_data_providers/ccxt_candles_ta_to_csv.py,sha256=j17D62JvKBW-Oc-gIrAWVbF7cCaE70X-X_uKvbtkilo,11154
|
|
11
11
|
siglab_py/market_data_providers/deribit_options_expiry_provider.py,sha256=e9Ee8TmC8pXaid8-jouSLKIpuW6_JBBgwRTieI665yQ,8684
|
|
12
12
|
siglab_py/market_data_providers/futu_candles_ta_to_csv.py,sha256=S4GXaJ7AveEh-Cm9-VhENBdlj_1CfyBTrQO7acTqfUE,10226
|
|
13
13
|
siglab_py/market_data_providers/orderbooks_provider.py,sha256=olt-3LIkoyzQWfNNQRhJtKibLbkTutt_q_rCCTM7i1g,16216
|
|
@@ -25,15 +25,15 @@ siglab_py/tests/unit/analytic_util_tests.py,sha256=eeusM5zkQR2QyVhT7nqF0mwHVg7vl
|
|
|
25
25
|
siglab_py/tests/unit/market_data_util_tests.py,sha256=A1y83itISmMJdn6wLpfwcr4tGola8wTf1D1xbelMvgw,2026
|
|
26
26
|
siglab_py/tests/unit/trading_util_tests.py,sha256=tyefqOTQOoXSlemSDonqmdHp61-1nuXb0_6oeLlaNSM,2689
|
|
27
27
|
siglab_py/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
28
|
-
siglab_py/util/analytic_util.py,sha256=
|
|
28
|
+
siglab_py/util/analytic_util.py,sha256=BI0_nYgs4R9FogyEN3KYaFHigLLsV05IMF7b2-Twemo,45608
|
|
29
29
|
siglab_py/util/aws_util.py,sha256=KGmjHrr1rpnnxr33nXHNzTul4tvyyxl9p6gpwNv0Ygc,2557
|
|
30
|
-
siglab_py/util/market_data_util.py,sha256
|
|
30
|
+
siglab_py/util/market_data_util.py,sha256=OPoSB8WW9AulYxkTF79_mPQgmQ1aCSMUnYTTfarAtYw,28975
|
|
31
31
|
siglab_py/util/notification_util.py,sha256=vySgHjpHgwFDLW0tHSi_AGh9JBbPc25IUgvWxmjAeT8,2658
|
|
32
32
|
siglab_py/util/retry_util.py,sha256=mxYuRFZRZoaQQjENcwPmxhxixtd1TFvbxIdPx4RwfRc,743
|
|
33
33
|
siglab_py/util/slack_notification_util.py,sha256=G27n-adbT3Q6oaHSMvu_Nom794rrda5PprSF-zvmzkM,1912
|
|
34
|
-
siglab_py/util/test_market_data_analytic_util.py,sha256=
|
|
34
|
+
siglab_py/util/test_market_data_analytic_util.py,sha256=vWzPbJL8lgdC-oBoiLv2OVgfplFUdyWh95-J2PoUyIg,2152
|
|
35
35
|
siglab_py/util/trading_util.py,sha256=FmqsamuPhMjZUkz4lCyuE8MHFapXn6yNl8Isy7peQEs,3047
|
|
36
|
-
siglab_py-0.3.
|
|
37
|
-
siglab_py-0.3.
|
|
38
|
-
siglab_py-0.3.
|
|
39
|
-
siglab_py-0.3.
|
|
36
|
+
siglab_py-0.3.6.dist-info/METADATA,sha256=OaKPU3SBDpdvYtvH4Hu3KOPxp654IoKrLuatH48WLu4,979
|
|
37
|
+
siglab_py-0.3.6.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
|
|
38
|
+
siglab_py-0.3.6.dist-info/top_level.txt,sha256=AbD4VR9OqmMOGlMJLkAVPGQMtUPIQv0t1BF5xmcLJSk,10
|
|
39
|
+
siglab_py-0.3.6.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|