siglab-py 0.5.70__py3-none-any.whl → 0.5.71__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/tests/integration/market_data_util_tests.py +34 -0
- siglab_py/util/market_data_util.py +18 -14
- {siglab_py-0.5.70.dist-info → siglab_py-0.5.71.dist-info}/METADATA +1 -1
- {siglab_py-0.5.70.dist-info → siglab_py-0.5.71.dist-info}/RECORD +6 -6
- {siglab_py-0.5.70.dist-info → siglab_py-0.5.71.dist-info}/WHEEL +0 -0
- {siglab_py-0.5.70.dist-info → siglab_py-0.5.71.dist-info}/top_level.txt +0 -0
|
@@ -118,6 +118,40 @@ class MarketDataUtilTests(unittest.TestCase):
|
|
|
118
118
|
assert set(pd_candles.columns) >= expected_columns, "Missing expected columns."
|
|
119
119
|
assert pd_candles['timestamp_ms'].notna().all(), "timestamp_ms column contains NaN values."
|
|
120
120
|
assert pd_candles['timestamp_ms'].is_monotonic_increasing, "Timestamps are not in ascending order."
|
|
121
|
+
|
|
122
|
+
def test_aggregate_candles(self):
|
|
123
|
+
end_date : datetime = datetime.today()
|
|
124
|
+
start_date : datetime = end_date + timedelta(hours=-8)
|
|
125
|
+
|
|
126
|
+
param = {
|
|
127
|
+
'apiKey' : None,
|
|
128
|
+
'secret' : None,
|
|
129
|
+
'password' : None,
|
|
130
|
+
'subaccount' : None,
|
|
131
|
+
'rateLimit' : 100, # In ms
|
|
132
|
+
'options' : {
|
|
133
|
+
'defaultType': 'swap' }
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
exchange : Exchange = okx(param) # type: ignore
|
|
137
|
+
normalized_symbols = [ 'BTC/USDT:USDT' ]
|
|
138
|
+
pd_candles: Union[pd.DataFrame, None] = fetch_candles(
|
|
139
|
+
start_ts=start_date.timestamp(),
|
|
140
|
+
end_ts=end_date.timestamp(),
|
|
141
|
+
exchange=exchange,
|
|
142
|
+
normalized_symbols=normalized_symbols,
|
|
143
|
+
candle_size='15m' # <---- aggregate 1m into 15m candles
|
|
144
|
+
)[normalized_symbols[0]]
|
|
145
|
+
|
|
146
|
+
assert pd_candles is not None
|
|
147
|
+
pd_candles['timestamp_ms_gap'] = pd_candles['timestamp_ms'].diff()
|
|
148
|
+
timestamp_ms_gap_median = pd_candles['timestamp_ms_gap'].median()
|
|
149
|
+
NUM_MS_IN_1HR = 60*60*1000
|
|
150
|
+
expected_15m_gap_ms = NUM_MS_IN_1HR/4
|
|
151
|
+
assert(timestamp_ms_gap_median==expected_15m_gap_ms)
|
|
152
|
+
total_num_rows = pd_candles.shape[0]
|
|
153
|
+
num_rows_with_15min_gaps = pd_candles[pd_candles.timestamp_ms_gap!=timestamp_ms_gap_median].shape[0]
|
|
154
|
+
assert(num_rows_with_15min_gaps/total_num_rows <= 0.4) # Why not 100% match? minute bars may have gaps (Also depends on what ticker)
|
|
121
155
|
|
|
122
156
|
def test_fetch_candles_futubull(self):
|
|
123
157
|
# You need Futu OpenD running and you need entitlements
|
|
@@ -220,8 +220,13 @@ def timestamp_to_datetime_cols(pd_candles : pd.DataFrame):
|
|
|
220
220
|
)
|
|
221
221
|
|
|
222
222
|
pd_candles['timestamp_ms_gap'] = pd_candles['timestamp_ms'] - pd_candles['timestamp_ms'].shift(1)
|
|
223
|
-
|
|
224
|
-
|
|
223
|
+
|
|
224
|
+
# Depending on asset, minutes bar may have gaps
|
|
225
|
+
timestamp_ms_gap_median = pd_candles['timestamp_ms_gap'].median()
|
|
226
|
+
NUM_MS_IN_1HR = 60*60*1000
|
|
227
|
+
if timestamp_ms_gap_median>=NUM_MS_IN_1HR:
|
|
228
|
+
num_rows_with_expected_gap = pd_candles[~pd_candles.timestamp_ms_gap.isna()][pd_candles.timestamp_ms_gap==timestamp_ms_gap_median].shape[0]
|
|
229
|
+
assert(num_rows_with_expected_gap/pd_candles.shape[0]>0.9)
|
|
225
230
|
pd_candles.drop(columns=['timestamp_ms_gap'], inplace=True)
|
|
226
231
|
|
|
227
232
|
def timestamp_to_active_trading_regions(
|
|
@@ -511,20 +516,21 @@ def fetch_candles(
|
|
|
511
516
|
validation_max_gaps : int = 10,
|
|
512
517
|
validation_max_end_date_intervals : int = 1
|
|
513
518
|
) -> Dict[str, Union[pd.DataFrame, None]]:
|
|
519
|
+
exchange_candles = { '' : None }
|
|
514
520
|
num_intervals = int(candle_size.replace(candle_size[-1],''))
|
|
515
521
|
|
|
516
522
|
if end_ts>datetime.now().timestamp():
|
|
517
523
|
end_ts = int(datetime.now().timestamp())
|
|
518
524
|
|
|
519
525
|
if type(exchange) is YahooExchange:
|
|
520
|
-
|
|
526
|
+
exchange_candles = exchange.fetch_candles(
|
|
521
527
|
start_ts=start_ts,
|
|
522
528
|
end_ts=end_ts,
|
|
523
529
|
symbols=normalized_symbols,
|
|
524
530
|
candle_size=candle_size
|
|
525
531
|
)
|
|
526
532
|
elif type(exchange) is NASDAQExchange:
|
|
527
|
-
|
|
533
|
+
exchange_candles = exchange.fetch_candles(
|
|
528
534
|
start_ts=start_ts,
|
|
529
535
|
end_ts=end_ts,
|
|
530
536
|
symbols=normalized_symbols,
|
|
@@ -541,11 +547,9 @@ def fetch_candles(
|
|
|
541
547
|
pd_candles = exchange_candles[symbol]
|
|
542
548
|
if not pd_candles is None:
|
|
543
549
|
fix_column_types(pd_candles) # You don't want to do this from Futubull as you'd need import Futubull from there: Circular references
|
|
544
|
-
|
|
545
|
-
exchange_candles[symbol] = aggregate_candles(candle_size, pd_candles)
|
|
546
|
-
return exchange_candles
|
|
550
|
+
|
|
547
551
|
elif issubclass(exchange.__class__, CcxtExchange):
|
|
548
|
-
|
|
552
|
+
exchange_candles = _fetch_candles_ccxt(
|
|
549
553
|
start_ts=start_ts,
|
|
550
554
|
end_ts=end_ts,
|
|
551
555
|
exchange=exchange,
|
|
@@ -553,7 +557,12 @@ def fetch_candles(
|
|
|
553
557
|
candle_size=candle_size,
|
|
554
558
|
num_candles_limit=num_candles_limit
|
|
555
559
|
)
|
|
556
|
-
|
|
560
|
+
if num_intervals!=1:
|
|
561
|
+
for symbol in exchange_candles:
|
|
562
|
+
if not exchange_candles[symbol] is None:
|
|
563
|
+
exchange_candles[symbol] = aggregate_candles(candle_size, exchange_candles[symbol]) # type: ignore
|
|
564
|
+
|
|
565
|
+
return exchange_candles # type: ignore
|
|
557
566
|
|
|
558
567
|
'''
|
|
559
568
|
Find listing date https://gist.github.com/mr-easy/5185b1dcdd5f9f908ff196446f092e9b
|
|
@@ -589,8 +598,6 @@ def _fetch_candles_ccxt(
|
|
|
589
598
|
|
|
590
599
|
rsp = {}
|
|
591
600
|
|
|
592
|
-
num_intervals = int(candle_size.replace(candle_size[-1],''))
|
|
593
|
-
|
|
594
601
|
exchange.load_markets()
|
|
595
602
|
|
|
596
603
|
num_tickers = len(normalized_symbols)
|
|
@@ -653,9 +660,6 @@ def _fetch_candles_ccxt(
|
|
|
653
660
|
fix_column_types(pd_all_candles)
|
|
654
661
|
pd_all_candles['pct_chg_on_close'] = pd_all_candles['close'].pct_change()
|
|
655
662
|
|
|
656
|
-
if num_intervals!=1:
|
|
657
|
-
pd_all_candles = aggregate_candles(candle_size, pd_all_candles)
|
|
658
|
-
|
|
659
663
|
rsp[ticker] = pd_all_candles
|
|
660
664
|
|
|
661
665
|
i+=1
|
|
@@ -20,7 +20,7 @@ siglab_py/ordergateway/gateway.py,sha256=Z-BQ-Z9gXoNrKQHzRIy9R1mnCybf9QwWhHpqkSI
|
|
|
20
20
|
siglab_py/ordergateway/test_ordergateway.py,sha256=4PE2flp_soGcD3DrI7zJOzZndjkb6I5XaDrFNNq4Huo,4009
|
|
21
21
|
siglab_py/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
22
|
siglab_py/tests/integration/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
23
|
-
siglab_py/tests/integration/market_data_util_tests.py,sha256=
|
|
23
|
+
siglab_py/tests/integration/market_data_util_tests.py,sha256=XKO8CX9AF7xRjRvt4lb938v_s89d2IBLAXKfZDdUxdY,8705
|
|
24
24
|
siglab_py/tests/unit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
25
25
|
siglab_py/tests/unit/analytic_util_tests.py,sha256=wbasww0ZN9Kb-6e8V_q4o4hpstzVBvQ6Bop_0HZQUtw,5926
|
|
26
26
|
siglab_py/tests/unit/market_data_util_tests.py,sha256=A1y83itISmMJdn6wLpfwcr4tGola8wTf1D1xbelMvgw,2026
|
|
@@ -29,13 +29,13 @@ siglab_py/tests/unit/trading_util_tests.py,sha256=LiflZrduWXyLMbpSFQCaydA7jdJx3v
|
|
|
29
29
|
siglab_py/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
30
30
|
siglab_py/util/analytic_util.py,sha256=ywp-VI8UlmoYVej2SaJMrOyheFwyh9KVjsnfw55dpMU,63785
|
|
31
31
|
siglab_py/util/aws_util.py,sha256=KGmjHrr1rpnnxr33nXHNzTul4tvyyxl9p6gpwNv0Ygc,2557
|
|
32
|
-
siglab_py/util/market_data_util.py,sha256=
|
|
32
|
+
siglab_py/util/market_data_util.py,sha256=QPI7ZvtYzRllz6REBkQRouMwEG_iHPuDMsbt-4n5FSs,33054
|
|
33
33
|
siglab_py/util/notification_util.py,sha256=tNZMUkkjz4q1CKqcQq62oEmZgHgNIwz2Iw9J22V22Zw,2668
|
|
34
34
|
siglab_py/util/retry_util.py,sha256=g-UU6pkPouWZZRZEqP99R2Z0lX5xzckYkzjwqqSDpVQ,922
|
|
35
35
|
siglab_py/util/simple_math.py,sha256=F7vGj0O2Y9EAGcMFR6SN1tTjBWO_a7YZeiTzk3eHaVI,8518
|
|
36
36
|
siglab_py/util/slack_notification_util.py,sha256=G27n-adbT3Q6oaHSMvu_Nom794rrda5PprSF-zvmzkM,1912
|
|
37
37
|
siglab_py/util/trading_util.py,sha256=dlIOzoMGnddLSFODcJ61EBH1Aeruq4IT2MsxIdFkV9I,5252
|
|
38
|
-
siglab_py-0.5.
|
|
39
|
-
siglab_py-0.5.
|
|
40
|
-
siglab_py-0.5.
|
|
41
|
-
siglab_py-0.5.
|
|
38
|
+
siglab_py-0.5.71.dist-info/METADATA,sha256=6AfqxAvgCiB0Vob4DwZfjv2n9ALiHt2vqAlYAjHRoMU,829
|
|
39
|
+
siglab_py-0.5.71.dist-info/WHEEL,sha256=lTU6B6eIfYoiQJTZNc-fyaR6BpL6ehTzU3xGYxn2n8k,91
|
|
40
|
+
siglab_py-0.5.71.dist-info/top_level.txt,sha256=AbD4VR9OqmMOGlMJLkAVPGQMtUPIQv0t1BF5xmcLJSk,10
|
|
41
|
+
siglab_py-0.5.71.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|