siglab-py 0.5.69__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 +60 -9
- {siglab_py-0.5.69.dist-info → siglab_py-0.5.71.dist-info}/METADATA +1 -1
- {siglab_py-0.5.69.dist-info → siglab_py-0.5.71.dist-info}/RECORD +6 -6
- {siglab_py-0.5.69.dist-info → siglab_py-0.5.71.dist-info}/WHEEL +0 -0
- {siglab_py-0.5.69.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(
|
|
@@ -426,6 +431,45 @@ class YahooExchange:
|
|
|
426
431
|
|
|
427
432
|
return exchange_candles
|
|
428
433
|
|
|
434
|
+
def aggregate_candles(
|
|
435
|
+
interval : str,
|
|
436
|
+
pd_candles : pd.DataFrame
|
|
437
|
+
) -> pd.DataFrame:
|
|
438
|
+
if interval[-1]=='m':
|
|
439
|
+
# 'm' for pandas means months!
|
|
440
|
+
interval = interval.replace('m','min')
|
|
441
|
+
pd_candles.set_index('datetime', inplace=True)
|
|
442
|
+
pd_candles_aggregated = pd_candles.resample(interval).agg({
|
|
443
|
+
'exchange' : 'first',
|
|
444
|
+
'symbol' : 'first',
|
|
445
|
+
'timestamp_ms' : 'first',
|
|
446
|
+
|
|
447
|
+
'open': 'first',
|
|
448
|
+
'high': 'max',
|
|
449
|
+
'low': 'min',
|
|
450
|
+
'close': 'last',
|
|
451
|
+
'volume': 'sum',
|
|
452
|
+
|
|
453
|
+
'datetime_utc' : 'first',
|
|
454
|
+
'year' : 'first',
|
|
455
|
+
'month' : 'first',
|
|
456
|
+
'day' : 'first',
|
|
457
|
+
'hour' : 'first',
|
|
458
|
+
'minute' : 'first',
|
|
459
|
+
'dayofweek' : 'first',
|
|
460
|
+
'week_of_month' : 'first',
|
|
461
|
+
|
|
462
|
+
'apac_trading_hr' : 'first',
|
|
463
|
+
'emea_trading_hr' : 'first',
|
|
464
|
+
'amer_trading_hr' : 'first',
|
|
465
|
+
|
|
466
|
+
'pct_chg_on_close' : 'sum',
|
|
467
|
+
|
|
468
|
+
})
|
|
469
|
+
pd_candles.reset_index(inplace=True)
|
|
470
|
+
pd_candles_aggregated.reset_index(inplace=True)
|
|
471
|
+
return pd_candles_aggregated
|
|
472
|
+
|
|
429
473
|
def fetch_historical_price(
|
|
430
474
|
exchange,
|
|
431
475
|
normalized_symbol : str,
|
|
@@ -472,19 +516,21 @@ def fetch_candles(
|
|
|
472
516
|
validation_max_gaps : int = 10,
|
|
473
517
|
validation_max_end_date_intervals : int = 1
|
|
474
518
|
) -> Dict[str, Union[pd.DataFrame, None]]:
|
|
475
|
-
|
|
519
|
+
exchange_candles = { '' : None }
|
|
520
|
+
num_intervals = int(candle_size.replace(candle_size[-1],''))
|
|
521
|
+
|
|
476
522
|
if end_ts>datetime.now().timestamp():
|
|
477
523
|
end_ts = int(datetime.now().timestamp())
|
|
478
524
|
|
|
479
525
|
if type(exchange) is YahooExchange:
|
|
480
|
-
|
|
526
|
+
exchange_candles = exchange.fetch_candles(
|
|
481
527
|
start_ts=start_ts,
|
|
482
528
|
end_ts=end_ts,
|
|
483
529
|
symbols=normalized_symbols,
|
|
484
530
|
candle_size=candle_size
|
|
485
531
|
)
|
|
486
532
|
elif type(exchange) is NASDAQExchange:
|
|
487
|
-
|
|
533
|
+
exchange_candles = exchange.fetch_candles(
|
|
488
534
|
start_ts=start_ts,
|
|
489
535
|
end_ts=end_ts,
|
|
490
536
|
symbols=normalized_symbols,
|
|
@@ -501,9 +547,9 @@ def fetch_candles(
|
|
|
501
547
|
pd_candles = exchange_candles[symbol]
|
|
502
548
|
if not pd_candles is None:
|
|
503
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
|
|
504
|
-
|
|
550
|
+
|
|
505
551
|
elif issubclass(exchange.__class__, CcxtExchange):
|
|
506
|
-
|
|
552
|
+
exchange_candles = _fetch_candles_ccxt(
|
|
507
553
|
start_ts=start_ts,
|
|
508
554
|
end_ts=end_ts,
|
|
509
555
|
exchange=exchange,
|
|
@@ -511,7 +557,12 @@ def fetch_candles(
|
|
|
511
557
|
candle_size=candle_size,
|
|
512
558
|
num_candles_limit=num_candles_limit
|
|
513
559
|
)
|
|
514
|
-
|
|
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
|
|
515
566
|
|
|
516
567
|
'''
|
|
517
568
|
Find listing date https://gist.github.com/mr-easy/5185b1dcdd5f9f908ff196446f092e9b
|
|
@@ -563,7 +614,7 @@ def _fetch_candles_ccxt(
|
|
|
563
614
|
|
|
564
615
|
def _calc_increment(candle_size):
|
|
565
616
|
increment = 1
|
|
566
|
-
num_intervals = int(candle_size[
|
|
617
|
+
num_intervals = int(candle_size.replace(candle_size[-1],''))
|
|
567
618
|
interval_type = candle_size[-1]
|
|
568
619
|
if interval_type == "m":
|
|
569
620
|
increment = 60
|
|
@@ -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
|