siglab-py 0.3.1__tar.gz → 0.3.3__tar.gz

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.

Files changed (43) hide show
  1. {siglab_py-0.3.1 → siglab_py-0.3.3}/PKG-INFO +1 -1
  2. {siglab_py-0.3.1 → siglab_py-0.3.3}/pyproject.toml +1 -1
  3. {siglab_py-0.3.1 → siglab_py-0.3.3}/setup.cfg +1 -1
  4. {siglab_py-0.3.1 → siglab_py-0.3.3}/siglab_py/ordergateway/client.py +8 -1
  5. {siglab_py-0.3.1 → siglab_py-0.3.3}/siglab_py/ordergateway/gateway.py +3 -97
  6. {siglab_py-0.3.1 → siglab_py-0.3.3}/siglab_py/tests/unit/analytic_util_tests.py +1 -0
  7. {siglab_py-0.3.1 → siglab_py-0.3.3}/siglab_py/tests/unit/trading_util_tests.py +6 -6
  8. {siglab_py-0.3.1 → siglab_py-0.3.3}/siglab_py/util/analytic_util.py +5 -1
  9. {siglab_py-0.3.1 → siglab_py-0.3.3}/siglab_py/util/market_data_util.py +175 -3
  10. siglab_py-0.3.3/siglab_py/util/test_market_data_analytic_util.py +52 -0
  11. {siglab_py-0.3.1 → siglab_py-0.3.3}/siglab_py/util/trading_util.py +3 -3
  12. {siglab_py-0.3.1 → siglab_py-0.3.3}/siglab_py.egg-info/PKG-INFO +1 -1
  13. {siglab_py-0.3.1 → siglab_py-0.3.3}/siglab_py.egg-info/SOURCES.txt +1 -0
  14. {siglab_py-0.3.1 → siglab_py-0.3.3}/siglab_py/__init__.py +0 -0
  15. {siglab_py-0.3.1 → siglab_py-0.3.3}/siglab_py/constants.py +0 -0
  16. {siglab_py-0.3.1 → siglab_py-0.3.3}/siglab_py/exchanges/__init__.py +0 -0
  17. {siglab_py-0.3.1 → siglab_py-0.3.3}/siglab_py/exchanges/any_exchange.py +0 -0
  18. {siglab_py-0.3.1 → siglab_py-0.3.3}/siglab_py/exchanges/futubull.py +0 -0
  19. {siglab_py-0.3.1 → siglab_py-0.3.3}/siglab_py/market_data_providers/__init__.py +0 -0
  20. {siglab_py-0.3.1 → siglab_py-0.3.3}/siglab_py/market_data_providers/aggregated_orderbook_provider.py +0 -0
  21. {siglab_py-0.3.1 → siglab_py-0.3.3}/siglab_py/market_data_providers/candles_provider.py +0 -0
  22. {siglab_py-0.3.1 → siglab_py-0.3.3}/siglab_py/market_data_providers/candles_ta_provider.py +0 -0
  23. {siglab_py-0.3.1 → siglab_py-0.3.3}/siglab_py/market_data_providers/ccxt_candles_ta_to_csv.py +0 -0
  24. {siglab_py-0.3.1 → siglab_py-0.3.3}/siglab_py/market_data_providers/deribit_options_expiry_provider.py +0 -0
  25. {siglab_py-0.3.1 → siglab_py-0.3.3}/siglab_py/market_data_providers/futu_candles_ta_to_csv.py +0 -0
  26. {siglab_py-0.3.1 → siglab_py-0.3.3}/siglab_py/market_data_providers/orderbooks_provider.py +0 -0
  27. {siglab_py-0.3.1 → siglab_py-0.3.3}/siglab_py/market_data_providers/test_provider.py +0 -0
  28. {siglab_py-0.3.1 → siglab_py-0.3.3}/siglab_py/ordergateway/__init__.py +0 -0
  29. {siglab_py-0.3.1 → siglab_py-0.3.3}/siglab_py/ordergateway/encrypt_keys_util.py +0 -0
  30. {siglab_py-0.3.1 → siglab_py-0.3.3}/siglab_py/ordergateway/test_ordergateway.py +0 -0
  31. {siglab_py-0.3.1 → siglab_py-0.3.3}/siglab_py/tests/__init__.py +0 -0
  32. {siglab_py-0.3.1 → siglab_py-0.3.3}/siglab_py/tests/integration/__init__.py +0 -0
  33. {siglab_py-0.3.1 → siglab_py-0.3.3}/siglab_py/tests/integration/market_data_util_tests.py +0 -0
  34. {siglab_py-0.3.1 → siglab_py-0.3.3}/siglab_py/tests/unit/__init__.py +0 -0
  35. {siglab_py-0.3.1 → siglab_py-0.3.3}/siglab_py/tests/unit/market_data_util_tests.py +0 -0
  36. {siglab_py-0.3.1 → siglab_py-0.3.3}/siglab_py/util/__init__.py +0 -0
  37. {siglab_py-0.3.1 → siglab_py-0.3.3}/siglab_py/util/aws_util.py +0 -0
  38. {siglab_py-0.3.1 → siglab_py-0.3.3}/siglab_py/util/notification_util.py +0 -0
  39. {siglab_py-0.3.1 → siglab_py-0.3.3}/siglab_py/util/retry_util.py +0 -0
  40. {siglab_py-0.3.1 → siglab_py-0.3.3}/siglab_py/util/slack_notification_util.py +0 -0
  41. {siglab_py-0.3.1 → siglab_py-0.3.3}/siglab_py.egg-info/dependency_links.txt +0 -0
  42. {siglab_py-0.3.1 → siglab_py-0.3.3}/siglab_py.egg-info/requires.txt +0 -0
  43. {siglab_py-0.3.1 → siglab_py-0.3.3}/siglab_py.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: siglab_py
3
- Version: 0.3.1
3
+ Version: 0.3.3
4
4
  Summary: Market data fetches, TA calculations and generic order gateway.
5
5
  Author: r0bbarh00d
6
6
  Author-email: r0bbarh00d <r0bbarh00d@gmail.com>
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "siglab_py"
7
- version = "0.3.1"
7
+ version = "0.3.3"
8
8
  description = "Market data fetches, TA calculations and generic order gateway."
9
9
  authors = [{name = "r0bbarh00d", email = "r0bbarh00d@gmail.com"}]
10
10
  license = {text = "MIT"}
@@ -1,6 +1,6 @@
1
1
  [metadata]
2
2
  name = siglab_py
3
- version = 0.3.1
3
+ version = 0.3.3
4
4
  description = Market data fetches, TA calculations and generic order gateway.
5
5
  author = r0bbarh00d
6
6
  author_email = r0bbarh00d@gmail.com
@@ -1,6 +1,7 @@
1
1
 
2
2
  from typing import List, Dict, Any, Union
3
3
  import json
4
+ from datetime import datetime
4
5
  import time
5
6
  from redis import StrictRedis
6
7
 
@@ -49,6 +50,8 @@ class Order:
49
50
  self.dispatched_amount : float = 0 # This is amount in base ccy, with rounding + multiplier applied (So for contracts with multiplier!=1, this is no longer amount in base ccy.)
50
51
  self.dispatched_price : Union[None, float] = None
51
52
 
53
+ self.timestamp_ms = int(datetime.now().timestamp() * 1000)
54
+
52
55
  def to_dict(self) -> Dict[JSON_SERIALIZABLE_TYPES, JSON_SERIALIZABLE_TYPES]:
53
56
  return {
54
57
  "ticker" : self.ticker,
@@ -59,7 +62,8 @@ class Order:
59
62
  "reduce_only" : self.reduce_only,
60
63
  "fees_ccy" : self.fees_ccy,
61
64
  "dispatched_amount" : self.dispatched_amount,
62
- "dispatched_price" : self.dispatched_price
65
+ "dispatched_price" : self.dispatched_price,
66
+ "timestamp_ms" : self.timestamp_ms
63
67
  }
64
68
 
65
69
  '''
@@ -97,6 +101,8 @@ class DivisiblePosition(Order):
97
101
  self.dispatched_slices : List[Order] = []
98
102
  self.executions : Dict[str, Dict[str, Any]] = {}
99
103
 
104
+ self.timestamp_ms = int(datetime.now().timestamp() * 1000)
105
+
100
106
  def to_slices(self) -> List[Order]:
101
107
  slices : List[Order] = []
102
108
 
@@ -331,6 +337,7 @@ class DivisiblePosition(Order):
331
337
  rv['pos'] = self.pos
332
338
  rv['done'] = self.done
333
339
  rv['execution_err'] = self.execution_err
340
+ rv['timestamp_ms'] = self.timestamp_ms
334
341
  return rv
335
342
 
336
343
  def execute_positions(
@@ -24,6 +24,7 @@ from util.aws_util import AwsKmsUtil
24
24
  import ccxt.pro as ccxtpro
25
25
 
26
26
  from siglab_py.exchanges.any_exchange import AnyExchange
27
+ from util.market_data_util import async_instantiate_exchange
27
28
  from siglab_py.ordergateway.client import Order, DivisiblePosition
28
29
  from siglab_py.constants import LogLevel # type: ignore
29
30
  from util.notification_util import dispatch_notification
@@ -32,6 +33,7 @@ current_filename = os.path.basename(__file__)
32
33
 
33
34
  '''
34
35
  Usage:
36
+ set PYTHONPATH=%PYTHONPATH%;D:\dev\siglab\siglab_py
35
37
  python gateway.py --gateway_id hyperliquid_01 --default_type linear --rate_limit_ms 100
36
38
 
37
39
  --default_type defaults to linear
@@ -352,102 +354,6 @@ def init_redis_client() -> StrictRedis:
352
354
 
353
355
  return redis_client
354
356
 
355
- async def instantiate_exchange(
356
- gateway_id : str,
357
- api_key : str,
358
- secret : str,
359
- passphrase : str,
360
- default_type : str,
361
- rate_limit_ms : float = 100
362
- ) -> Union[AnyExchange, None]:
363
- exchange : Union[AnyExchange, None] = None
364
- exchange_name : str = gateway_id.split('_')[0]
365
- exchange_name =exchange_name.lower().strip()
366
-
367
- # Look at ccxt exchange.describe. under 'options' \ 'defaultType' (and 'defaultSubType') for what markets the exchange support.
368
- # https://docs.ccxt.com/en/latest/manual.html#instantiation
369
- exchange_params : Dict[str, Any]= {
370
- 'apiKey' : api_key,
371
- 'secret' : secret,
372
- 'enableRateLimit' : True,
373
- 'rateLimit' : rate_limit_ms,
374
- 'options' : {
375
- 'defaultType' : default_type
376
- }
377
- }
378
-
379
- if exchange_name=='binance':
380
- # spot, future, margin, delivery, option
381
- # https://github.com/ccxt/ccxt/blob/master/python/ccxt/binance.py#L1298
382
- exchange = ccxtpro.binance(exchange_params) # type: ignore
383
- elif exchange_name=='bybit':
384
- # spot, linear, inverse, futures
385
- # https://github.com/ccxt/ccxt/blob/master/python/ccxt/bybit.py#L1041
386
- exchange = ccxtpro.bybit(exchange_params) # type: ignore
387
- elif exchange_name=='okx':
388
- # 'funding', spot, margin, future, swap, option
389
- # https://github.com/ccxt/ccxt/blob/master/python/ccxt/okx.py#L1144
390
- exchange_params['password'] = passphrase
391
- exchange = ccxtpro.okx(exchange_params) # type: ignore
392
- elif exchange_name=='deribit':
393
- # spot, swap, future
394
- # https://github.com/ccxt/ccxt/blob/master/python/ccxt/deribit.py#L360
395
- exchange = ccxtpro.deribit(exchange_params) # type: ignore
396
- elif exchange_name=='hyperliquid':
397
- '''
398
- https://app.hyperliquid.xyz/API
399
-
400
- defaultType from ccxt: swap
401
- https://github.com/ccxt/ccxt/blob/master/python/ccxt/hyperliquid.py#L225
402
-
403
- How to integrate? You can skip first 6 min: https://www.youtube.com/watch?v=UuBr331wxr4&t=363s
404
-
405
- Example,
406
- API credentials created under "\ More \ API":
407
- Ledger Arbitrum Wallet Address: 0xAAAAA <-- This is your Ledger Arbitrum wallet address with which you connect to Hyperliquid.
408
- API Wallet Address 0xBBBBB <-- Generated
409
- privateKey 0xCCCCC
410
-
411
- Basic connection via CCXT:
412
- import asyncio
413
- import ccxt.pro as ccxtpro
414
-
415
- async def main():
416
- rate_limit_ms = 100
417
- exchange_params = {
418
- "walletAddress" : "0xAAAAA", # Ledger Arbitrum Wallet Address here! Not the generated address.
419
- "privateKey" : "0xCCCCC"
420
- }
421
- exchange = ccxtpro.hyperliquid(exchange_params)
422
- balances = await exchange.fetch_balance()
423
- print(balances)
424
-
425
- asyncio.run(main())
426
- '''
427
- exchange = ccxtpro.hyperliquid(
428
- {
429
- "walletAddress" : api_key,
430
- "privateKey" : secret,
431
- 'enableRateLimit' : True,
432
- 'rateLimit' : rate_limit_ms
433
- }
434
- ) # type: ignore
435
- else:
436
- raise ValueError(f"Unsupported exchange {exchange_name}, check gateway_id {gateway_id}.")
437
-
438
- await exchange.load_markets() # type: ignore
439
-
440
- '''
441
- Is this necessary? The added trouble is for example bybit.authenticate requires arg 'url'. binance doesn't. And fetch_balance already test credentials.
442
-
443
- try:
444
- await exchange.authenticate() # type: ignore
445
- except Exception as swallow_this_error:
446
- pass
447
- '''
448
-
449
- return exchange
450
-
451
357
  async def watch_orders_task(
452
358
  exchange : AnyExchange,
453
359
  executions : Dict[str, Dict[str, Any]]
@@ -931,7 +837,7 @@ async def main():
931
837
 
932
838
  redis_client : StrictRedis = init_redis_client()
933
839
 
934
- exchange : Union[AnyExchange, None] = await instantiate_exchange(
840
+ exchange : Union[AnyExchange, None] = await async_instantiate_exchange(
935
841
  gateway_id=param['gateway_id'],
936
842
  api_key=api_key,
937
843
  secret=secret,
@@ -50,6 +50,7 @@ class AnalyticUtilTests(unittest.TestCase):
50
50
  'is_green', 'pct_change_close',
51
51
  'sma_short_periods', 'sma_long_periods', 'ema_short_periods', 'ema_long_periods', 'ema_close',
52
52
  'std', 'std_percent',
53
+ 'normalized_close_short', 'normalized_close_long',
53
54
  'candle_height_percent', 'candle_height_percent_rounded',
54
55
  'log_return', 'interval_hist_vol', 'annualized_hist_vol',
55
56
  'chop_against_ema',
@@ -12,7 +12,7 @@ class TradingUtilTests(unittest.TestCase):
12
12
  tp_min_percent : float = 1.5
13
13
  tp_max_percent : float = 2.5
14
14
  sl_percent_trailing : float = 50 # Trailing stop loss in percent
15
- default_effective_tp_percent_trailing : float = 50
15
+ default_effective_tp_trailing_percent : float = 50
16
16
 
17
17
  pnl_percent_notional : float = 0.5 # Trade's current pnl in percent.
18
18
 
@@ -21,7 +21,7 @@ class TradingUtilTests(unittest.TestCase):
21
21
  tp_max_percent = tp_max_percent,
22
22
  sl_percent_trailing = sl_percent_trailing,
23
23
  pnl_percent_notional = pnl_percent_notional,
24
- default_effective_tp_percent_trailing = default_effective_tp_percent_trailing
24
+ default_effective_tp_trailing_percent = default_effective_tp_trailing_percent
25
25
  )
26
26
  assert(effective_tp_trailing_percent==50) # Generous trailing SL when trading starting out and pnl small.
27
27
 
@@ -29,7 +29,7 @@ class TradingUtilTests(unittest.TestCase):
29
29
  tp_min_percent : float = 1.5
30
30
  tp_max_percent : float = 2.5
31
31
  sl_percent_trailing : float = 50 # Trailing stop loss in percent
32
- default_effective_tp_percent_trailing : float = 50
32
+ default_effective_tp_trailing_percent : float = 50
33
33
 
34
34
  pnl_percent_notional : float = 2 # Trade's current pnl in percent.
35
35
 
@@ -38,7 +38,7 @@ class TradingUtilTests(unittest.TestCase):
38
38
  tp_max_percent = tp_max_percent,
39
39
  sl_percent_trailing = sl_percent_trailing,
40
40
  pnl_percent_notional = pnl_percent_notional,
41
- default_effective_tp_percent_trailing = default_effective_tp_percent_trailing
41
+ default_effective_tp_trailing_percent = default_effective_tp_trailing_percent
42
42
  )
43
43
  assert(effective_tp_trailing_percent==25) # Intermediate trailing SL
44
44
 
@@ -46,7 +46,7 @@ class TradingUtilTests(unittest.TestCase):
46
46
  tp_min_percent : float = 1.5
47
47
  tp_max_percent : float = 2.5
48
48
  sl_percent_trailing : float = 50 # Trailing stop loss in percent
49
- default_effective_tp_percent_trailing : float = 50
49
+ default_effective_tp_trailing_percent : float = 50
50
50
 
51
51
  pnl_percent_notional : float = 2.5 # Trade's current pnl in percent.
52
52
 
@@ -55,6 +55,6 @@ class TradingUtilTests(unittest.TestCase):
55
55
  tp_max_percent = tp_max_percent,
56
56
  sl_percent_trailing = sl_percent_trailing,
57
57
  pnl_percent_notional = pnl_percent_notional,
58
- default_effective_tp_percent_trailing = default_effective_tp_percent_trailing
58
+ default_effective_tp_trailing_percent = default_effective_tp_trailing_percent
59
59
  )
60
60
  assert(effective_tp_trailing_percent==0) # Most tight trailing SL
@@ -98,7 +98,7 @@ def compute_candles_stats(
98
98
  pd_candles['ema_short_periods'] = close_short_periods_ewm.mean()
99
99
  pd_candles['ema_long_periods'] = close_long_periods_ewm.mean()
100
100
  pd_candles['ema_close'] = pd_candles['ema_long_periods'] # Alias, shorter name
101
- pd_candles['std'] = pd_candles['close'].rolling(window=sliding_window_how_many_candles).std()
101
+ pd_candles['std'] = close_long_periods_rolling.std()
102
102
 
103
103
  pd_candles['std_percent'] = pd_candles['std'] / pd_candles['ema_close'] * 100
104
104
 
@@ -371,10 +371,14 @@ def compute_candles_stats(
371
371
  X = sm.add_constant(range(len(pd_candles['close'])))
372
372
  rolling_slope = pd_candles['close'].rolling(window=int(sliding_window_how_many_candles/slow_fast_interval_ratio)).apply(lambda x: sm.OLS(x, X[:len(x)]).fit().params[1], raw=False)
373
373
  pd_candles['close_short_slope'] = rolling_slope
374
+ max_abs_slope = pd_candles['close_short_slope'].abs().rolling(window=int(sliding_window_how_many_candles/slow_fast_interval_ratio)).max()
375
+ pd_candles['normalized_close_short_slope'] = pd_candles['close_short_slope'] / (2* max_abs_slope)
374
376
 
375
377
  X = sm.add_constant(range(len(pd_candles['close'])))
376
378
  rolling_slope = pd_candles['close'].rolling(window=sliding_window_how_many_candles).apply(lambda x: sm.OLS(x, X[:len(x)]).fit().params[1], raw=False)
377
379
  pd_candles['close_long_slope'] = rolling_slope
380
+ max_abs_slope = pd_candles['close_long_slope'].abs().rolling(window=sliding_window_how_many_candles).max()
381
+ pd_candles['normalized_close_short_slope'] = pd_candles['close_long_slope'] / (2 * max_abs_slope)
378
382
 
379
383
  X = sm.add_constant(range(len(pd_candles['ema_short_periods'])))
380
384
  rolling_slope = pd_candles['ema_short_periods'].rolling(window=int(sliding_window_how_many_candles/slow_fast_interval_ratio)).apply(lambda x: sm.OLS(x, X[:len(x)]).fit().params[1], raw=False)
@@ -1,3 +1,4 @@
1
+ import incremental
1
2
  import tzlocal
2
3
  from datetime import datetime, timezone
3
4
  from typing import List, Dict, Union, NoReturn, Any, Tuple
@@ -5,9 +6,12 @@ from pathlib import Path
5
6
  import math
6
7
  import pandas as pd
7
8
  import numpy as np
9
+ import asyncio
8
10
 
9
11
  from ccxt.base.exchange import Exchange as CcxtExchange
10
12
  from ccxt import deribit
13
+ import ccxt
14
+ import ccxt.pro as ccxtpro
11
15
 
12
16
  # https://www.analyticsvidhya.com/blog/2021/06/download-financial-dataset-using-yahoo-finance-in-python-a-complete-guide/
13
17
  from yahoofinancials import YahooFinancials
@@ -16,6 +20,155 @@ from yahoofinancials import YahooFinancials
16
20
  import yfinance as yf
17
21
 
18
22
  from siglab_py.exchanges.futubull import Futubull
23
+ from siglab_py.exchanges.any_exchange import AnyExchange
24
+
25
+ def instantiate_exchange(
26
+ exchange_name : str,
27
+ api_key : Union[str, None] = None,
28
+ secret : Union[str, None] = None,
29
+ passphrase : Union[str, None] = None,
30
+ default_type : Union[str, None] = 'spot',
31
+ rate_limit_ms : float = 100
32
+ ) -> Union[AnyExchange, None]:
33
+ exchange_name = exchange_name.lower().strip()
34
+
35
+ # Look at ccxt exchange.describe. under 'options' \ 'defaultType' (and 'defaultSubType') for what markets the exchange support.
36
+ # https://docs.ccxt.com/en/latest/manual.html#instantiation
37
+ exchange_params : Dict[str, Any]= {
38
+ 'apiKey' : api_key,
39
+ 'secret' : secret,
40
+ 'enableRateLimit' : True,
41
+ 'rateLimit' : rate_limit_ms,
42
+ 'options' : {
43
+ 'defaultType' : default_type
44
+ }
45
+ }
46
+ if api_key:
47
+ exchange_params['apiKey'] = api_key
48
+ if secret:
49
+ exchange_params['secret'] = secret
50
+ if passphrase:
51
+ exchange_params['passphrase'] = passphrase
52
+
53
+ if exchange_name=='binance':
54
+ exchange = ccxt.binance(exchange_params) # type: ignore
55
+ elif exchange_name=='bybit':
56
+ exchange = ccxt.bybit(exchange_params) # type: ignore
57
+ elif exchange_name=='okx':
58
+ exchange = ccxt.okx(exchange_params) # type: ignore
59
+ elif exchange_name=='deribit':
60
+ exchange = ccxt.deribit(exchange_params) # type: ignore
61
+ elif exchange_name=='hyperliquid':
62
+ exchange = ccxt.hyperliquid(
63
+ {
64
+ "walletAddress" : api_key, # type: ignore
65
+ "privateKey" : secret,
66
+ 'enableRateLimit' : True,
67
+ 'rateLimit' : rate_limit_ms
68
+ }
69
+ )
70
+ else:
71
+ raise ValueError(f"Unsupported exchange {exchange_name}.")
72
+
73
+ exchange.load_markets() # type: ignore
74
+
75
+ return exchange # type: ignore
76
+
77
+ async def async_instantiate_exchange(
78
+ gateway_id : str,
79
+ api_key : str,
80
+ secret : str,
81
+ passphrase : str,
82
+ default_type : str,
83
+ rate_limit_ms : float = 100
84
+ ) -> Union[AnyExchange, None]:
85
+ exchange : Union[AnyExchange, None] = None
86
+ exchange_name : str = gateway_id.split('_')[0]
87
+ exchange_name =exchange_name.lower().strip()
88
+
89
+ # Look at ccxt exchange.describe. under 'options' \ 'defaultType' (and 'defaultSubType') for what markets the exchange support.
90
+ # https://docs.ccxt.com/en/latest/manual.html#instantiation
91
+ exchange_params : Dict[str, Any]= {
92
+ 'apiKey' : api_key,
93
+ 'secret' : secret,
94
+ 'enableRateLimit' : True,
95
+ 'rateLimit' : rate_limit_ms,
96
+ 'options' : {
97
+ 'defaultType' : default_type
98
+ }
99
+ }
100
+
101
+ if exchange_name=='binance':
102
+ # spot, future, margin, delivery, option
103
+ # https://github.com/ccxt/ccxt/blob/master/python/ccxt/binance.py#L1298
104
+ exchange = ccxtpro.binance(exchange_params) # type: ignore
105
+ elif exchange_name=='bybit':
106
+ # spot, linear, inverse, futures
107
+ # https://github.com/ccxt/ccxt/blob/master/python/ccxt/bybit.py#L1041
108
+ exchange = ccxtpro.bybit(exchange_params) # type: ignore
109
+ elif exchange_name=='okx':
110
+ # 'funding', spot, margin, future, swap, option
111
+ # https://github.com/ccxt/ccxt/blob/master/python/ccxt/okx.py#L1144
112
+ exchange_params['password'] = passphrase
113
+ exchange = ccxtpro.okx(exchange_params) # type: ignore
114
+ elif exchange_name=='deribit':
115
+ # spot, swap, future
116
+ # https://github.com/ccxt/ccxt/blob/master/python/ccxt/deribit.py#L360
117
+ exchange = ccxtpro.deribit(exchange_params) # type: ignore
118
+ elif exchange_name=='hyperliquid':
119
+ '''
120
+ https://app.hyperliquid.xyz/API
121
+
122
+ defaultType from ccxt: swap
123
+ https://github.com/ccxt/ccxt/blob/master/python/ccxt/hyperliquid.py#L225
124
+
125
+ How to integrate? You can skip first 6 min: https://www.youtube.com/watch?v=UuBr331wxr4&t=363s
126
+
127
+ Example,
128
+ API credentials created under "\ More \ API":
129
+ Ledger Arbitrum Wallet Address: 0xAAAAA <-- This is your Ledger Arbitrum wallet address with which you connect to Hyperliquid.
130
+ API Wallet Address 0xBBBBB <-- Generated
131
+ privateKey 0xCCCCC
132
+
133
+ Basic connection via CCXT:
134
+ import asyncio
135
+ import ccxt.pro as ccxtpro
136
+
137
+ async def main():
138
+ rate_limit_ms = 100
139
+ exchange_params = {
140
+ "walletAddress" : "0xAAAAA", # Ledger Arbitrum Wallet Address here! Not the generated address.
141
+ "privateKey" : "0xCCCCC"
142
+ }
143
+ exchange = ccxtpro.hyperliquid(exchange_params)
144
+ balances = await exchange.fetch_balance()
145
+ print(balances)
146
+
147
+ asyncio.run(main())
148
+ '''
149
+ exchange = ccxtpro.hyperliquid(
150
+ {
151
+ "walletAddress" : api_key,
152
+ "privateKey" : secret,
153
+ 'enableRateLimit' : True,
154
+ 'rateLimit' : rate_limit_ms
155
+ }
156
+ ) # type: ignore
157
+ else:
158
+ raise ValueError(f"Unsupported exchange {exchange_name}, check gateway_id {gateway_id}.")
159
+
160
+ await exchange.load_markets() # type: ignore
161
+
162
+ '''
163
+ Is this necessary? The added trouble is for example bybit.authenticate requires arg 'url'. binance doesn't. And fetch_balance already test credentials.
164
+
165
+ try:
166
+ await exchange.authenticate() # type: ignore
167
+ except Exception as swallow_this_error:
168
+ pass
169
+ '''
170
+
171
+ return exchange
19
172
 
20
173
  def timestamp_to_datetime_cols(pd_candles : pd.DataFrame):
21
174
  def _fix_timestamp_ms(x):
@@ -61,6 +214,11 @@ def timestamp_to_datetime_cols(pd_candles : pd.DataFrame):
61
214
  lambda x: "AMER" in timestamp_to_active_trading_regions(int(x/1000))
62
215
  )
63
216
 
217
+ pd_candles['timestamp_ms_gap'] = pd_candles['timestamp_ms'] - pd_candles['timestamp_ms'].shift(1)
218
+ timestamp_ms_gap = pd_candles.iloc[-1]['timestamp_ms_gap']
219
+ assert(pd_candles[~pd_candles.timestamp_ms_gap.isna()][pd_candles.timestamp_ms_gap!=timestamp_ms_gap].shape[0]==0)
220
+ pd_candles.drop(columns=['timestamp_ms_gap'], inplace=True)
221
+
64
222
  def timestamp_to_active_trading_regions(
65
223
  timestamp_ms : int
66
224
  ) -> List[str]:
@@ -368,6 +526,20 @@ def _fetch_candles_ccxt(
368
526
 
369
527
  return candles
370
528
 
529
+ def _calc_increment(candle_size):
530
+ increment = 1
531
+ num_intervals = int(candle_size[0])
532
+ interval_type = candle_size[-1]
533
+ if interval_type == "m":
534
+ increment = 60
535
+ elif interval_type == "h":
536
+ increment = 60*60
537
+ elif interval_type == "d":
538
+ increment = 60*60*24
539
+ else:
540
+ raise ValueError(f"Invalid candle_size {candle_size}")
541
+ return num_intervals * increment
542
+
371
543
  all_candles = []
372
544
  params = {}
373
545
  this_cutoff = start_ts
@@ -380,10 +552,10 @@ def _fetch_candles_ccxt(
380
552
  record_ts_str : str = str(record_ts)
381
553
  if len(record_ts_str)==13:
382
554
  record_ts = int(int(record_ts_str)/1000) # Convert from milli-seconds to seconds
383
-
384
- this_cutoff = record_ts + 1
555
+
556
+ this_cutoff = record_ts + _calc_increment(candle_size)
385
557
  else:
386
- this_cutoff += 1
558
+ this_cutoff += _calc_increment(candle_size)
387
559
 
388
560
  columns = ['exchange', 'symbol', 'timestamp_ms', 'open', 'high', 'low', 'close', 'volume']
389
561
  pd_all_candles = pd.DataFrame([ [ exchange.name, ticker, x[0], x[1], x[2], x[3], x[4], x[5] ] for x in all_candles], columns=columns)
@@ -0,0 +1,52 @@
1
+ from datetime import datetime
2
+ from typing import Dict, Union
3
+
4
+ import pandas as pd
5
+ import matplotlib.pyplot as plt
6
+
7
+ from ccxt.okx import okx
8
+
9
+ from market_data_util import fetch_candles
10
+ from analytic_util import compute_candles_stats
11
+
12
+ base_ccy : str = "BTC"
13
+ # ticker = "GRIFFAIN/USDT:USDT"
14
+ # ticker = "OL/USDT:USDT"
15
+ ticker = f"{base_ccy}/USDT:USDT"
16
+
17
+ param = {
18
+ 'rateLimit' : 100, # In ms
19
+ 'options' : {
20
+ 'defaultType': 'swap', # Should test linear instead
21
+ }
22
+ }
23
+ exchange = okx(param) # type: ignore
24
+
25
+ start_date : datetime = datetime(2024,1,1)
26
+ end_date : datetime = datetime(2025,4,20)
27
+ candle_size : str = '1h'
28
+ ma_long_intervals : int = 24*30
29
+ ma_short_intervals : int = 24
30
+ boillenger_std_multiples : int = 2
31
+ pypy_compatible : bool = False
32
+
33
+ markets = exchange.load_markets()
34
+ assert(ticker in markets)
35
+
36
+ pd_candles: Union[pd.DataFrame, None] = fetch_candles(
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 ]
43
+
44
+ pd_candles.to_csv(f"{base_ccy}_raw_candles.csv") # type: ignore
45
+
46
+ compute_candles_stats(
47
+ pd_candles=pd_candles, # type: ignore
48
+ boillenger_std_multiples=boillenger_std_multiples,
49
+ sliding_window_how_many_candles=ma_long_intervals,
50
+ slow_fast_interval_ratio=(ma_long_intervals/ma_short_intervals),
51
+ pypy_compat=pypy_compatible
52
+ )
@@ -11,11 +11,11 @@ Examples,
11
11
  min TP = 1.5% <-- min TP
12
12
  max TP = 2.5% <-- max TP
13
13
 
14
- slope = (0-50)/(2.5-1.5) = -50
14
+ slope = (0-50)/(2.5-1.5) = -50/+1 = -50
15
15
  effective_tp_trailing_percent = slope * (pnl_percent_notional - 1.5%) + sl_percent_trailing
16
16
 
17
- Case 1. pnl_percent_notional = 0.5% (Trade starting off, only +50bps pnl. i.e. min TP)
18
- effective_tp_trailing_percent = slope * (pnl_percent_notional - 0.5%) + sl_percent_trailing
17
+ Case 1. pnl_percent_notional = 1.5% (Trade starting off, only +50bps pnl. i.e. min TP)
18
+ effective_tp_trailing_percent = slope * (pnl_percent_notional - 1.5%) + sl_percent_trailing
19
19
  = -50 * (1.5-1.5) + 50%
20
20
  = 0 + 50
21
21
  = 50% (Most loose)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: siglab-py
3
- Version: 0.3.1
3
+ Version: 0.3.3
4
4
  Summary: Market data fetches, TA calculations and generic order gateway.
5
5
  Author: r0bbarh00d
6
6
  Author-email: r0bbarh00d <r0bbarh00d@gmail.com>
@@ -38,4 +38,5 @@ siglab_py/util/market_data_util.py
38
38
  siglab_py/util/notification_util.py
39
39
  siglab_py/util/retry_util.py
40
40
  siglab_py/util/slack_notification_util.py
41
+ siglab_py/util/test_market_data_analytic_util.py
41
42
  siglab_py/util/trading_util.py