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.

@@ -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
- timestamp_ms_gap = pd_candles.iloc[-1]['timestamp_ms_gap']
224
- assert(pd_candles[~pd_candles.timestamp_ms_gap.isna()][pd_candles.timestamp_ms_gap!=timestamp_ms_gap].shape[0]==0)
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
- return exchange.fetch_candles(
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
- return exchange.fetch_candles(
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
- return exchange_candles
550
+
505
551
  elif issubclass(exchange.__class__, CcxtExchange):
506
- return _fetch_candles_ccxt(
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
- return { '' : None }
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[0])
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: siglab_py
3
- Version: 0.5.69
3
+ Version: 0.5.71
4
4
  Summary: Market data fetches, TA calculations and generic order gateway.
5
5
  Author: r0bbarh00d
6
6
  Author-email: r0bbarh00d <r0bbarh00d@gmail.com>
@@ -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=p-RWIJZLyj0lAdfi4QTIeAttCm_e8mEVWFKh4OWuogU,7189
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=mUXg4uaiX3b6_klgJWIEgnUQU4IUd6CwTOqKLiQWRlU,31307
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.69.dist-info/METADATA,sha256=QaGfEu-83Dyrx9WYb2z4dQzJaziY40smJpgcA2HCZPI,829
39
- siglab_py-0.5.69.dist-info/WHEEL,sha256=lTU6B6eIfYoiQJTZNc-fyaR6BpL6ehTzU3xGYxn2n8k,91
40
- siglab_py-0.5.69.dist-info/top_level.txt,sha256=AbD4VR9OqmMOGlMJLkAVPGQMtUPIQv0t1BF5xmcLJSk,10
41
- siglab_py-0.5.69.dist-info/RECORD,,
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,,