siglab-py 0.1.0__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/__init__.py +0 -0
- siglab_py/constants.py +3 -0
- siglab_py/exchanges/__init__.py +0 -0
- siglab_py/exchanges/any_exchange.py +20 -0
- siglab_py/market_data_providers/__init__.py +0 -0
- siglab_py/market_data_providers/aggregated_orderbook_provider.py +451 -0
- siglab_py/market_data_providers/candles_provider.py +342 -0
- siglab_py/market_data_providers/candles_ta_provider.py +263 -0
- siglab_py/market_data_providers/deribit_options_expiry_provider.py +197 -0
- siglab_py/market_data_providers/orderbooks_provider.py +359 -0
- siglab_py/market_data_providers/test_provider.py +70 -0
- siglab_py/ordergateway/__init__.py +0 -0
- siglab_py/ordergateway/client.py +137 -0
- siglab_py/ordergateway/encrypt_keys_util.py +43 -0
- siglab_py/ordergateway/gateway.py +658 -0
- siglab_py/ordergateway/test_ordergateway.py +140 -0
- siglab_py/tests/__init__.py +0 -0
- siglab_py/tests/integration/__init__.py +0 -0
- siglab_py/tests/integration/market_data_util_tests.py +123 -0
- siglab_py/tests/unit/__init__.py +0 -0
- siglab_py/util/__init__.py +0 -0
- siglab_py/util/analytic_util.py +792 -0
- siglab_py/util/aws_util.py +47 -0
- siglab_py/util/market_data_util.py +385 -0
- siglab_py/util/retry_util.py +15 -0
- siglab_py-0.1.0.dist-info/METADATA +36 -0
- siglab_py-0.1.0.dist-info/RECORD +29 -0
- siglab_py-0.1.0.dist-info/WHEEL +5 -0
- siglab_py-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
import argparse
|
|
3
|
+
import time
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from typing import Any, Dict, List, Union
|
|
6
|
+
import logging
|
|
7
|
+
import json
|
|
8
|
+
import pandas as pd
|
|
9
|
+
import numpy as np
|
|
10
|
+
from redis import StrictRedis
|
|
11
|
+
|
|
12
|
+
from ordergateway.client import DivisiblePosition
|
|
13
|
+
|
|
14
|
+
'''
|
|
15
|
+
set PYTHONPATH=%PYTHONPATH%;D:\dev\siglab\siglab_py
|
|
16
|
+
python test_ordergateway.py --gateway_id bybit_01
|
|
17
|
+
'''
|
|
18
|
+
|
|
19
|
+
param : Dict[str, str] = {
|
|
20
|
+
'gateway_id' : '---'
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
def parse_args():
|
|
24
|
+
parser = argparse.ArgumentParser() # type: ignore
|
|
25
|
+
parser.add_argument("--gateway_id", help="gateway_id: Where are you sending your order?", default=None)
|
|
26
|
+
|
|
27
|
+
args = parser.parse_args()
|
|
28
|
+
param['gateway_id'] = args.gateway_id
|
|
29
|
+
|
|
30
|
+
def init_redis_client() -> StrictRedis:
|
|
31
|
+
redis_client : StrictRedis = StrictRedis(
|
|
32
|
+
host = 'localhost',
|
|
33
|
+
port = 6379,
|
|
34
|
+
db = 0,
|
|
35
|
+
ssl = False
|
|
36
|
+
)
|
|
37
|
+
try:
|
|
38
|
+
redis_client.keys()
|
|
39
|
+
except ConnectionError as redis_conn_error:
|
|
40
|
+
err_msg = f"Failed to connect to redis: {'localhost'}, port: {6379}"
|
|
41
|
+
raise ConnectionError(err_msg)
|
|
42
|
+
|
|
43
|
+
return redis_client
|
|
44
|
+
|
|
45
|
+
def execute_positions(
|
|
46
|
+
redis_client : StrictRedis,
|
|
47
|
+
positions : List[DivisiblePosition],
|
|
48
|
+
ordergateway_pending_orders_topic : str):
|
|
49
|
+
# https://redis.io/commands/publish/
|
|
50
|
+
_positions = [ position.to_dict() for position in positions ]
|
|
51
|
+
redis_client.set(name=ordergateway_pending_orders_topic, value=json.dumps(_positions).encode('utf-8'), ex=60*15)
|
|
52
|
+
|
|
53
|
+
print(f"{ordergateway_pending_orders_topic}: Orders sent {_positions}.")
|
|
54
|
+
|
|
55
|
+
if __name__ == '__main__':
|
|
56
|
+
parse_args()
|
|
57
|
+
|
|
58
|
+
gateway_id : str = param['gateway_id']
|
|
59
|
+
ordergateway_pending_orders_topic = 'ordergateway_pending_orders_$GATEWAY_ID$'
|
|
60
|
+
ordergateway_pending_orders_topic = ordergateway_pending_orders_topic.replace("$GATEWAY_ID$", gateway_id)
|
|
61
|
+
|
|
62
|
+
ordergateway_executions_topic = "ordergateway_executions_$GATEWAY_ID$"
|
|
63
|
+
ordergateway_executions_topic = ordergateway_executions_topic.replace("$GATEWAY_ID$", gateway_id)
|
|
64
|
+
|
|
65
|
+
redis_client : StrictRedis = init_redis_client()
|
|
66
|
+
|
|
67
|
+
# Example, enter into a pair position long SUSHI, short DYDX
|
|
68
|
+
positions_1 : List[DivisiblePosition] = [
|
|
69
|
+
DivisiblePosition(
|
|
70
|
+
ticker = 'SUSHI/USDT:USDT',
|
|
71
|
+
side = 'sell',
|
|
72
|
+
amount = 10,
|
|
73
|
+
leg_room_bps = 5,
|
|
74
|
+
order_type = 'limit',
|
|
75
|
+
slices=5,
|
|
76
|
+
wait_fill_threshold_ms=15000
|
|
77
|
+
),
|
|
78
|
+
DivisiblePosition(
|
|
79
|
+
ticker = 'DYDX/USDT:USDT',
|
|
80
|
+
side = 'buy',
|
|
81
|
+
amount = 10,
|
|
82
|
+
leg_room_bps = 5,
|
|
83
|
+
order_type = 'limit',
|
|
84
|
+
slices=5,
|
|
85
|
+
wait_fill_threshold_ms=15000
|
|
86
|
+
)
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
positions_2 : List[DivisiblePosition] = [
|
|
90
|
+
DivisiblePosition(
|
|
91
|
+
ticker = 'SUSHI/USDT:USDT',
|
|
92
|
+
side = 'buy',
|
|
93
|
+
amount = 10,
|
|
94
|
+
leg_room_bps = 5,
|
|
95
|
+
order_type = 'limit',
|
|
96
|
+
slices=5,
|
|
97
|
+
wait_fill_threshold_ms=15000
|
|
98
|
+
),
|
|
99
|
+
]
|
|
100
|
+
|
|
101
|
+
positions_3 : List[DivisiblePosition] = [
|
|
102
|
+
DivisiblePosition(
|
|
103
|
+
ticker = 'BTC/USDT:USDT',
|
|
104
|
+
side = 'sell',
|
|
105
|
+
amount = 0.01,
|
|
106
|
+
leg_room_bps = 5,
|
|
107
|
+
order_type = 'limit',
|
|
108
|
+
slices=1,
|
|
109
|
+
wait_fill_threshold_ms=60000
|
|
110
|
+
)
|
|
111
|
+
]
|
|
112
|
+
execute_positions(
|
|
113
|
+
redis_client=redis_client,
|
|
114
|
+
positions=positions_2,
|
|
115
|
+
ordergateway_pending_orders_topic=ordergateway_pending_orders_topic)
|
|
116
|
+
|
|
117
|
+
# Wait for fills
|
|
118
|
+
fills_received : bool = False
|
|
119
|
+
while not fills_received:
|
|
120
|
+
try:
|
|
121
|
+
keys = redis_client.keys()
|
|
122
|
+
for key in keys:
|
|
123
|
+
s_key : str = key.decode("utf-8")
|
|
124
|
+
if s_key==ordergateway_executions_topic:
|
|
125
|
+
message = redis_client.get(key)
|
|
126
|
+
if message:
|
|
127
|
+
message = message.decode('utf-8')
|
|
128
|
+
positions = json.loads(message)
|
|
129
|
+
|
|
130
|
+
for position in positions:
|
|
131
|
+
print(f"{position['ticker']} {position['side']} amount: {position['amount']} leg_room_bps: {position['leg_room_bps']} slices: {position['slices']}, filled_amount: {position['filled_amount']}, average_cost: {position['average_cost']}, # executions: {len(position['executions'])}")
|
|
132
|
+
fills_received = True
|
|
133
|
+
break
|
|
134
|
+
|
|
135
|
+
except Exception as loop_err:
|
|
136
|
+
print(f"Oops {loop_err}")
|
|
137
|
+
finally:
|
|
138
|
+
time.sleep(1)
|
|
139
|
+
|
|
140
|
+
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from typing import Union
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from util.market_data_util import *
|
|
7
|
+
|
|
8
|
+
from ccxt.binance import binance
|
|
9
|
+
from ccxt.bybit import bybit
|
|
10
|
+
from ccxt.okx import okx
|
|
11
|
+
from ccxt.deribit import deribit
|
|
12
|
+
from ccxt.base.exchange import Exchange
|
|
13
|
+
|
|
14
|
+
# @unittest.skip("Skip all integration tests.")
|
|
15
|
+
class MarketDataGizmoTests(unittest.TestCase):
|
|
16
|
+
|
|
17
|
+
def test_fetch_candles_yahoo(self):
|
|
18
|
+
start_date : datetime = datetime(2024, 1,1)
|
|
19
|
+
end_date : datetime = datetime(2024,12,31)
|
|
20
|
+
|
|
21
|
+
exchange = YahooExchange()
|
|
22
|
+
normalized_symbols = [ 'AAPL' ]
|
|
23
|
+
pd_candles: Union[pd.DataFrame, None] = fetch_candles(
|
|
24
|
+
start_ts=start_date.timestamp(),
|
|
25
|
+
end_ts=end_date.timestamp(),
|
|
26
|
+
exchange=exchange,
|
|
27
|
+
normalized_symbols=normalized_symbols,
|
|
28
|
+
candle_size='1h'
|
|
29
|
+
)[normalized_symbols[0]]
|
|
30
|
+
|
|
31
|
+
assert pd_candles is not None
|
|
32
|
+
|
|
33
|
+
if pd_candles is not None:
|
|
34
|
+
assert len(pd_candles) > 0, "No candles returned."
|
|
35
|
+
expected_columns = {'exchange', 'symbol', 'timestamp_ms', 'open', 'high', 'low', 'close', 'volume', 'datetime_utc', 'datetime', 'year', 'month', 'day', 'hour', 'minute'}
|
|
36
|
+
assert set(pd_candles.columns) >= expected_columns, "Missing expected columns."
|
|
37
|
+
assert pd_candles['timestamp_ms'].notna().all(), "timestamp_ms column contains NaN values."
|
|
38
|
+
assert pd_candles['timestamp_ms'].is_monotonic_increasing, "Timestamps are not in ascending order."
|
|
39
|
+
|
|
40
|
+
def test_fetch_candles_nasdaq(self):
|
|
41
|
+
start_date : datetime = datetime(2023,1,1)
|
|
42
|
+
end_date : datetime = datetime(2023,12,31)
|
|
43
|
+
|
|
44
|
+
'''
|
|
45
|
+
Folder structure:
|
|
46
|
+
\ siglab
|
|
47
|
+
\ siglab_py <-- python project root
|
|
48
|
+
\ sigab_py
|
|
49
|
+
__init__.py
|
|
50
|
+
\ util
|
|
51
|
+
__init__.py
|
|
52
|
+
market_data_util.py
|
|
53
|
+
\ tests
|
|
54
|
+
\ integration
|
|
55
|
+
__init__.py
|
|
56
|
+
market_data_util_tests.py <-- Tests here
|
|
57
|
+
|
|
58
|
+
\ siglab_rs <-- Rust project root
|
|
59
|
+
\ data <-- Data files here!
|
|
60
|
+
'''
|
|
61
|
+
data_dir : Union[str, None] = str(Path(__file__).resolve().parents[3] / "data/nasdaq")
|
|
62
|
+
exchange : NASDAQExchange = NASDAQExchange(data_dir = data_dir)
|
|
63
|
+
|
|
64
|
+
# CSV from NASDAQ: https://www.nasdaq.com/market-activity/quotes/historical
|
|
65
|
+
normalized_symbols = [ 'AAPL' ]
|
|
66
|
+
# normalized_symbols = [ 'BABA', 'SPY', 'VOO', 'IVV', 'AAPL', 'AMZN', 'GOOG_classC', 'META', 'MSFT', 'MSTR', 'TSLA' ]
|
|
67
|
+
|
|
68
|
+
pd_candles: Union[pd.DataFrame, None] = fetch_candles(
|
|
69
|
+
start_ts=start_date.timestamp(),
|
|
70
|
+
end_ts=end_date.timestamp(),
|
|
71
|
+
exchange=exchange,
|
|
72
|
+
normalized_symbols=normalized_symbols,
|
|
73
|
+
candle_size='1h'
|
|
74
|
+
)[normalized_symbols[0]]
|
|
75
|
+
|
|
76
|
+
assert pd_candles is not None
|
|
77
|
+
|
|
78
|
+
if pd_candles is not None:
|
|
79
|
+
assert len(pd_candles) > 0, "No candles returned."
|
|
80
|
+
expected_columns = {'exchange', 'symbol', 'timestamp_ms', 'open', 'high', 'low', 'close', 'volume', 'datetime_utc', 'datetime', 'year', 'month', 'day', 'hour', 'minute'}
|
|
81
|
+
assert set(pd_candles.columns) >= expected_columns, "Missing expected columns."
|
|
82
|
+
assert pd_candles['timestamp_ms'].notna().all(), "timestamp_ms column contains NaN values."
|
|
83
|
+
assert pd_candles['timestamp_ms'].is_monotonic_increasing, "Timestamps are not in ascending order."
|
|
84
|
+
|
|
85
|
+
def test_fetch_candles_ccxt(self):
|
|
86
|
+
start_date : datetime = datetime(2024,1,1)
|
|
87
|
+
end_date : datetime = datetime(2024,12,31)
|
|
88
|
+
|
|
89
|
+
param = {
|
|
90
|
+
'apiKey' : None,
|
|
91
|
+
'secret' : None,
|
|
92
|
+
'password' : None, # Other exchanges dont require this! This is saved in exchange.password!
|
|
93
|
+
'subaccount' : None,
|
|
94
|
+
'rateLimit' : 100, # In ms
|
|
95
|
+
'options' : {
|
|
96
|
+
'defaultType': 'swap', # Should test linear instead
|
|
97
|
+
'leg_room_bps' : 5,
|
|
98
|
+
'trade_fee_bps' : 3,
|
|
99
|
+
|
|
100
|
+
'list_ts_field' : 'listTime' # list_ts_field: Response field in exchange.markets[symbol] to indiate timestamp of symbol's listing date in ms. For bybit, markets['launchTime'] is list date. For okx, it's markets['listTime'].
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
exchange : Exchange = okx(param)
|
|
105
|
+
normalized_symbols = [ 'BTC/USDT:USDT' ]
|
|
106
|
+
pd_candles: Union[pd.DataFrame, None] = fetch_candles(
|
|
107
|
+
start_ts=start_date.timestamp(),
|
|
108
|
+
end_ts=end_date.timestamp(),
|
|
109
|
+
exchange=exchange,
|
|
110
|
+
normalized_symbols=normalized_symbols,
|
|
111
|
+
candle_size='1h'
|
|
112
|
+
)[normalized_symbols[0]]
|
|
113
|
+
|
|
114
|
+
assert pd_candles is not None
|
|
115
|
+
|
|
116
|
+
if pd_candles is not None:
|
|
117
|
+
assert len(pd_candles) > 0, "No candles returned."
|
|
118
|
+
expected_columns = {'exchange', 'symbol', 'timestamp_ms', 'open', 'high', 'low', 'close', 'volume', 'datetime_utc', 'datetime', 'year', 'month', 'day', 'hour', 'minute'}
|
|
119
|
+
assert set(pd_candles.columns) >= expected_columns, "Missing expected columns."
|
|
120
|
+
assert pd_candles['timestamp_ms'].notna().all(), "timestamp_ms column contains NaN values."
|
|
121
|
+
assert pd_candles['timestamp_ms'].is_monotonic_increasing, "Timestamps are not in ascending order."
|
|
122
|
+
|
|
123
|
+
|
|
File without changes
|
|
File without changes
|