siglab-py 0.3.2__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.
- {siglab_py-0.3.2 → siglab_py-0.3.3}/PKG-INFO +1 -1
- {siglab_py-0.3.2 → siglab_py-0.3.3}/pyproject.toml +1 -1
- {siglab_py-0.3.2 → siglab_py-0.3.3}/setup.cfg +1 -1
- {siglab_py-0.3.2 → siglab_py-0.3.3}/siglab_py/ordergateway/client.py +8 -1
- {siglab_py-0.3.2 → siglab_py-0.3.3}/siglab_py/ordergateway/gateway.py +3 -97
- {siglab_py-0.3.2 → siglab_py-0.3.3}/siglab_py/tests/unit/analytic_util_tests.py +1 -0
- {siglab_py-0.3.2 → siglab_py-0.3.3}/siglab_py/util/analytic_util.py +8 -7
- {siglab_py-0.3.2 → siglab_py-0.3.3}/siglab_py/util/market_data_util.py +175 -3
- siglab_py-0.3.3/siglab_py/util/test_market_data_analytic_util.py +52 -0
- {siglab_py-0.3.2 → siglab_py-0.3.3}/siglab_py.egg-info/PKG-INFO +1 -1
- {siglab_py-0.3.2 → siglab_py-0.3.3}/siglab_py.egg-info/SOURCES.txt +1 -0
- {siglab_py-0.3.2 → siglab_py-0.3.3}/siglab_py/__init__.py +0 -0
- {siglab_py-0.3.2 → siglab_py-0.3.3}/siglab_py/constants.py +0 -0
- {siglab_py-0.3.2 → siglab_py-0.3.3}/siglab_py/exchanges/__init__.py +0 -0
- {siglab_py-0.3.2 → siglab_py-0.3.3}/siglab_py/exchanges/any_exchange.py +0 -0
- {siglab_py-0.3.2 → siglab_py-0.3.3}/siglab_py/exchanges/futubull.py +0 -0
- {siglab_py-0.3.2 → siglab_py-0.3.3}/siglab_py/market_data_providers/__init__.py +0 -0
- {siglab_py-0.3.2 → siglab_py-0.3.3}/siglab_py/market_data_providers/aggregated_orderbook_provider.py +0 -0
- {siglab_py-0.3.2 → siglab_py-0.3.3}/siglab_py/market_data_providers/candles_provider.py +0 -0
- {siglab_py-0.3.2 → siglab_py-0.3.3}/siglab_py/market_data_providers/candles_ta_provider.py +0 -0
- {siglab_py-0.3.2 → siglab_py-0.3.3}/siglab_py/market_data_providers/ccxt_candles_ta_to_csv.py +0 -0
- {siglab_py-0.3.2 → siglab_py-0.3.3}/siglab_py/market_data_providers/deribit_options_expiry_provider.py +0 -0
- {siglab_py-0.3.2 → siglab_py-0.3.3}/siglab_py/market_data_providers/futu_candles_ta_to_csv.py +0 -0
- {siglab_py-0.3.2 → siglab_py-0.3.3}/siglab_py/market_data_providers/orderbooks_provider.py +0 -0
- {siglab_py-0.3.2 → siglab_py-0.3.3}/siglab_py/market_data_providers/test_provider.py +0 -0
- {siglab_py-0.3.2 → siglab_py-0.3.3}/siglab_py/ordergateway/__init__.py +0 -0
- {siglab_py-0.3.2 → siglab_py-0.3.3}/siglab_py/ordergateway/encrypt_keys_util.py +0 -0
- {siglab_py-0.3.2 → siglab_py-0.3.3}/siglab_py/ordergateway/test_ordergateway.py +0 -0
- {siglab_py-0.3.2 → siglab_py-0.3.3}/siglab_py/tests/__init__.py +0 -0
- {siglab_py-0.3.2 → siglab_py-0.3.3}/siglab_py/tests/integration/__init__.py +0 -0
- {siglab_py-0.3.2 → siglab_py-0.3.3}/siglab_py/tests/integration/market_data_util_tests.py +0 -0
- {siglab_py-0.3.2 → siglab_py-0.3.3}/siglab_py/tests/unit/__init__.py +0 -0
- {siglab_py-0.3.2 → siglab_py-0.3.3}/siglab_py/tests/unit/market_data_util_tests.py +0 -0
- {siglab_py-0.3.2 → siglab_py-0.3.3}/siglab_py/tests/unit/trading_util_tests.py +0 -0
- {siglab_py-0.3.2 → siglab_py-0.3.3}/siglab_py/util/__init__.py +0 -0
- {siglab_py-0.3.2 → siglab_py-0.3.3}/siglab_py/util/aws_util.py +0 -0
- {siglab_py-0.3.2 → siglab_py-0.3.3}/siglab_py/util/notification_util.py +0 -0
- {siglab_py-0.3.2 → siglab_py-0.3.3}/siglab_py/util/retry_util.py +0 -0
- {siglab_py-0.3.2 → siglab_py-0.3.3}/siglab_py/util/slack_notification_util.py +0 -0
- {siglab_py-0.3.2 → siglab_py-0.3.3}/siglab_py/util/trading_util.py +0 -0
- {siglab_py-0.3.2 → siglab_py-0.3.3}/siglab_py.egg-info/dependency_links.txt +0 -0
- {siglab_py-0.3.2 → siglab_py-0.3.3}/siglab_py.egg-info/requires.txt +0 -0
- {siglab_py-0.3.2 → siglab_py-0.3.3}/siglab_py.egg-info/top_level.txt +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "siglab_py"
|
|
7
|
-
version = "0.3.
|
|
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,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
|
|
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',
|
|
@@ -102,9 +102,6 @@ def compute_candles_stats(
|
|
|
102
102
|
|
|
103
103
|
pd_candles['std_percent'] = pd_candles['std'] / pd_candles['ema_close'] * 100
|
|
104
104
|
|
|
105
|
-
pd_candles['normalized_close_short'] = (pd_candles['close'] - pd_candles['sma_short_periods']) / close_short_periods_rolling.std()
|
|
106
|
-
pd_candles['normalized_close_long'] = (pd_candles['close'] - pd_candles['sma_long_periods']) / pd_candles['std']
|
|
107
|
-
|
|
108
105
|
pd_candles['candle_height_percent'] = pd_candles['candle_height'] / pd_candles['ema_close'] * 100
|
|
109
106
|
pd_candles['candle_height_percent_rounded'] = pd_candles['candle_height_percent'].round().astype('Int64')
|
|
110
107
|
|
|
@@ -371,13 +368,17 @@ def compute_candles_stats(
|
|
|
371
368
|
import statsmodels.api as sm # in-compatible with pypy
|
|
372
369
|
|
|
373
370
|
# Slopes
|
|
374
|
-
X = sm.add_constant(range(len(pd_candles['
|
|
375
|
-
rolling_slope = pd_candles['
|
|
371
|
+
X = sm.add_constant(range(len(pd_candles['close'])))
|
|
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)
|
|
376
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)
|
|
377
376
|
|
|
378
|
-
X = sm.add_constant(range(len(pd_candles['
|
|
379
|
-
rolling_slope = pd_candles['
|
|
377
|
+
X = sm.add_constant(range(len(pd_candles['close'])))
|
|
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)
|
|
380
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)
|
|
381
382
|
|
|
382
383
|
X = sm.add_constant(range(len(pd_candles['ema_short_periods'])))
|
|
383
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 +
|
|
555
|
+
|
|
556
|
+
this_cutoff = record_ts + _calc_increment(candle_size)
|
|
385
557
|
else:
|
|
386
|
-
this_cutoff +=
|
|
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
|
+
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{siglab_py-0.3.2 → siglab_py-0.3.3}/siglab_py/market_data_providers/aggregated_orderbook_provider.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{siglab_py-0.3.2 → siglab_py-0.3.3}/siglab_py/market_data_providers/ccxt_candles_ta_to_csv.py
RENAMED
|
File without changes
|
|
File without changes
|
{siglab_py-0.3.2 → siglab_py-0.3.3}/siglab_py/market_data_providers/futu_candles_ta_to_csv.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|