Qubx 0.5.7__cp312-cp312-manylinux_2_39_x86_64.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 Qubx might be problematic. Click here for more details.
- qubx/__init__.py +207 -0
- qubx/_nb_magic.py +100 -0
- qubx/backtester/__init__.py +5 -0
- qubx/backtester/account.py +145 -0
- qubx/backtester/broker.py +87 -0
- qubx/backtester/data.py +296 -0
- qubx/backtester/management.py +378 -0
- qubx/backtester/ome.py +296 -0
- qubx/backtester/optimization.py +201 -0
- qubx/backtester/simulated_data.py +558 -0
- qubx/backtester/simulator.py +362 -0
- qubx/backtester/utils.py +780 -0
- qubx/cli/__init__.py +0 -0
- qubx/cli/commands.py +67 -0
- qubx/connectors/ccxt/__init__.py +0 -0
- qubx/connectors/ccxt/account.py +495 -0
- qubx/connectors/ccxt/broker.py +132 -0
- qubx/connectors/ccxt/customizations.py +193 -0
- qubx/connectors/ccxt/data.py +612 -0
- qubx/connectors/ccxt/exceptions.py +17 -0
- qubx/connectors/ccxt/factory.py +93 -0
- qubx/connectors/ccxt/utils.py +307 -0
- qubx/core/__init__.py +0 -0
- qubx/core/account.py +251 -0
- qubx/core/basics.py +850 -0
- qubx/core/context.py +420 -0
- qubx/core/exceptions.py +38 -0
- qubx/core/helpers.py +480 -0
- qubx/core/interfaces.py +1150 -0
- qubx/core/loggers.py +514 -0
- qubx/core/lookups.py +475 -0
- qubx/core/metrics.py +1512 -0
- qubx/core/mixins/__init__.py +13 -0
- qubx/core/mixins/market.py +94 -0
- qubx/core/mixins/processing.py +428 -0
- qubx/core/mixins/subscription.py +203 -0
- qubx/core/mixins/trading.py +88 -0
- qubx/core/mixins/universe.py +270 -0
- qubx/core/series.cpython-312-x86_64-linux-gnu.so +0 -0
- qubx/core/series.pxd +125 -0
- qubx/core/series.pyi +118 -0
- qubx/core/series.pyx +988 -0
- qubx/core/utils.cpython-312-x86_64-linux-gnu.so +0 -0
- qubx/core/utils.pyi +6 -0
- qubx/core/utils.pyx +62 -0
- qubx/data/__init__.py +25 -0
- qubx/data/helpers.py +416 -0
- qubx/data/readers.py +1562 -0
- qubx/data/tardis.py +100 -0
- qubx/gathering/simplest.py +88 -0
- qubx/math/__init__.py +3 -0
- qubx/math/stats.py +129 -0
- qubx/pandaz/__init__.py +23 -0
- qubx/pandaz/ta.py +2757 -0
- qubx/pandaz/utils.py +638 -0
- qubx/resources/instruments/symbols-binance.cm.json +1 -0
- qubx/resources/instruments/symbols-binance.json +1 -0
- qubx/resources/instruments/symbols-binance.um.json +1 -0
- qubx/resources/instruments/symbols-bitfinex.f.json +1 -0
- qubx/resources/instruments/symbols-bitfinex.json +1 -0
- qubx/resources/instruments/symbols-kraken.f.json +1 -0
- qubx/resources/instruments/symbols-kraken.json +1 -0
- qubx/ta/__init__.py +0 -0
- qubx/ta/indicators.cpython-312-x86_64-linux-gnu.so +0 -0
- qubx/ta/indicators.pxd +149 -0
- qubx/ta/indicators.pyi +41 -0
- qubx/ta/indicators.pyx +787 -0
- qubx/trackers/__init__.py +3 -0
- qubx/trackers/abvanced.py +236 -0
- qubx/trackers/composite.py +146 -0
- qubx/trackers/rebalancers.py +129 -0
- qubx/trackers/riskctrl.py +641 -0
- qubx/trackers/sizers.py +235 -0
- qubx/utils/__init__.py +5 -0
- qubx/utils/_pyxreloader.py +281 -0
- qubx/utils/charting/lookinglass.py +1057 -0
- qubx/utils/charting/mpl_helpers.py +1183 -0
- qubx/utils/marketdata/binance.py +284 -0
- qubx/utils/marketdata/ccxt.py +90 -0
- qubx/utils/marketdata/dukas.py +130 -0
- qubx/utils/misc.py +541 -0
- qubx/utils/ntp.py +63 -0
- qubx/utils/numbers_utils.py +7 -0
- qubx/utils/orderbook.py +491 -0
- qubx/utils/plotting/__init__.py +0 -0
- qubx/utils/plotting/dashboard.py +150 -0
- qubx/utils/plotting/data.py +137 -0
- qubx/utils/plotting/interfaces.py +25 -0
- qubx/utils/plotting/renderers/__init__.py +0 -0
- qubx/utils/plotting/renderers/plotly.py +0 -0
- qubx/utils/runner/__init__.py +1 -0
- qubx/utils/runner/_jupyter_runner.pyt +60 -0
- qubx/utils/runner/accounts.py +88 -0
- qubx/utils/runner/configs.py +65 -0
- qubx/utils/runner/runner.py +470 -0
- qubx/utils/time.py +312 -0
- qubx-0.5.7.dist-info/METADATA +105 -0
- qubx-0.5.7.dist-info/RECORD +100 -0
- qubx-0.5.7.dist-info/WHEEL +4 -0
- qubx-0.5.7.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from typing import Any, Callable, Dict, List, Tuple
|
|
3
|
+
from os import unlink
|
|
4
|
+
import numpy as np
|
|
5
|
+
import pandas as pd
|
|
6
|
+
from os.path import exists, join, split, basename
|
|
7
|
+
from tqdm.notebook import tqdm
|
|
8
|
+
import requests
|
|
9
|
+
from collections import defaultdict
|
|
10
|
+
|
|
11
|
+
from qubx import logger
|
|
12
|
+
from qubx.utils.misc import makedirs, get_local_qubx_folder
|
|
13
|
+
from qubx.pandaz import generate_equal_date_ranges, srows
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
DEFALT_LOCAL_FILE_STORAGE = makedirs(get_local_qubx_folder(), 'data/import/binance_history/')
|
|
17
|
+
DEFALT_LOCAL_CSV_STORAGE = makedirs(get_local_qubx_folder(), 'data/binance/')
|
|
18
|
+
|
|
19
|
+
# _DEFAULT_MARKET_DATA_DB = 'md'
|
|
20
|
+
BINANCE_DATA_STORAGE = "https://s3-ap-northeast-1.amazonaws.com"
|
|
21
|
+
BINANCE_DATA_URL = "https://data.binance.vision/"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def get_binance_symbol_info_for_type(market_types: List[str]) -> Dict[str, Dict[str, Any]]:
|
|
25
|
+
"""
|
|
26
|
+
Get list of all symbols from binance for given list of market types:
|
|
27
|
+
possible types are: SPOT, FUTURES, COINSFUTURES
|
|
28
|
+
|
|
29
|
+
>>> get_binance_symbol_info_for_type('FUTURES')
|
|
30
|
+
|
|
31
|
+
:param market_type: SPOT, FUTURES (UM) or COINSFUTURES (CM)
|
|
32
|
+
"""
|
|
33
|
+
from binance.client import Client
|
|
34
|
+
|
|
35
|
+
client = Client()
|
|
36
|
+
infos = {}
|
|
37
|
+
for market_type in (market_types if not isinstance(market_types, str) else [market_types]):
|
|
38
|
+
if market_type in ['FUTURES', 'UM']:
|
|
39
|
+
infos['binance.um'] = client.futures_exchange_info()
|
|
40
|
+
|
|
41
|
+
elif market_type in ['COINSFUTURES', 'CM']:
|
|
42
|
+
infos['binance.cm'] = client.futures_coin_exchange_info()
|
|
43
|
+
|
|
44
|
+
elif market_type == 'SPOT':
|
|
45
|
+
infos['binance'] = client.get_exchange_info()
|
|
46
|
+
else:
|
|
47
|
+
raise ValueError("Only 'FUTURES | UM', 'COINSFUTURES | CM' or 'SPOT' are supported for market_type")
|
|
48
|
+
|
|
49
|
+
return infos
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def fetch_file(url, local_file_storage, chunk_size=1024*1024, progress_bar=True):
|
|
53
|
+
"""
|
|
54
|
+
Load file from url and store it to specified storage
|
|
55
|
+
"""
|
|
56
|
+
file = split(url)[-1]
|
|
57
|
+
|
|
58
|
+
# if dest location not exists create it
|
|
59
|
+
if not exists(local_file_storage):
|
|
60
|
+
makedirs(local_file_storage)
|
|
61
|
+
|
|
62
|
+
response = requests.get(url, stream=True)
|
|
63
|
+
if response.status_code != 200:
|
|
64
|
+
logger.warning(f"Error while fetching {url}: {response.status_code}")
|
|
65
|
+
return None
|
|
66
|
+
fpath = join(local_file_storage, file)
|
|
67
|
+
fpath_temp = join(local_file_storage, f"{file}_tmp")
|
|
68
|
+
with open(fpath_temp, "wb") as handle:
|
|
69
|
+
iters = response.iter_content(chunk_size=chunk_size)
|
|
70
|
+
for data in tqdm(iters) if progress_bar else iters:
|
|
71
|
+
handle.write(data)
|
|
72
|
+
os.rename(fpath_temp, fpath)
|
|
73
|
+
return fpath
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def get_trades_files(symbol: str, instr_type: str, instr_subtype: str):
|
|
77
|
+
"""
|
|
78
|
+
Get list of trades files for specified instrument from Binance datastorage
|
|
79
|
+
"""
|
|
80
|
+
if instr_type.lower() == 'spot':
|
|
81
|
+
instr_subtype = ''
|
|
82
|
+
filter_str = join("data", instr_type.lower(), instr_subtype.lower(), "monthly", "trades", symbol.upper())
|
|
83
|
+
pg = requests.get(f"{BINANCE_DATA_STORAGE}/data.binance.vision?prefix={filter_str}/")
|
|
84
|
+
info = pd.read_xml(pg.text)
|
|
85
|
+
return [k for k in info.Key.dropna() if k.endswith('.zip')]
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def load_trades_for(symbol, instr_type='futures', instr_subtype='um', local_file_storage=DEFALT_LOCAL_FILE_STORAGE):
|
|
89
|
+
"""
|
|
90
|
+
Load trades from Binance data storage
|
|
91
|
+
>>> load_trades_for('ETHUSDT', 'futures', 'um')
|
|
92
|
+
"""
|
|
93
|
+
local_file_storage = makedirs(local_file_storage)
|
|
94
|
+
|
|
95
|
+
f_list = get_trades_files(symbol, instr_type, instr_subtype)
|
|
96
|
+
for r_file in tqdm(f_list):
|
|
97
|
+
dest_dir = join(local_file_storage, symbol.upper())
|
|
98
|
+
dest_file = join(dest_dir, basename(r_file))
|
|
99
|
+
if not exists(dest_file):
|
|
100
|
+
fetch_file(join(BINANCE_DATA_URL, r_file), dest_dir)
|
|
101
|
+
else:
|
|
102
|
+
logger.info(f"{dest_file} already loaded, skipping ...")
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def parse_kl_file(fpath: str) -> pd.DataFrame:
|
|
106
|
+
_reader = lambda fp, hdr: pd.read_csv(fp, names = [
|
|
107
|
+
'open_time', 'open', 'high', 'low', 'close', 'volume', 'close_time',
|
|
108
|
+
'quote_volume', 'count', 'taker_buy_volume', 'taker_buy_quote_volume', 'ignore'
|
|
109
|
+
],
|
|
110
|
+
usecols=[
|
|
111
|
+
'open_time', 'open', 'high', 'low', 'close', 'volume', 'quote_volume',
|
|
112
|
+
'count', 'taker_buy_volume', 'taker_buy_quote_volume',
|
|
113
|
+
],
|
|
114
|
+
index_col='open_time', header=hdr
|
|
115
|
+
)
|
|
116
|
+
d = _reader(fpath, None)
|
|
117
|
+
|
|
118
|
+
# - if there is header
|
|
119
|
+
if isinstance(d.index[0], str):
|
|
120
|
+
d = d.iloc[1:, :].astype(float)
|
|
121
|
+
|
|
122
|
+
d.index = pd.to_datetime(d.index, unit='ms').rename('timestamp')
|
|
123
|
+
return d
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def load_binance_kl_history(symbol: str, start: str, stop: str = 'now',
|
|
127
|
+
instr_type='futures', instr_subtype='um',
|
|
128
|
+
timeframe='1m', temp_storage=DEFALT_LOCAL_FILE_STORAGE) -> pd.DataFrame:
|
|
129
|
+
"""
|
|
130
|
+
Loads binance 1m KLine history from AWS storage
|
|
131
|
+
|
|
132
|
+
Parameters
|
|
133
|
+
----------
|
|
134
|
+
symbol : str
|
|
135
|
+
The symbol to load data for
|
|
136
|
+
start : str
|
|
137
|
+
The start date in format %Y-%m-%d
|
|
138
|
+
stop : str, optional
|
|
139
|
+
The end date in format %Y-%m-%d, by default 'now'
|
|
140
|
+
instr_type : str, optional
|
|
141
|
+
The instrument type, one of 'futures', 'spot', by default 'futures'
|
|
142
|
+
instr_subtype : str, optional
|
|
143
|
+
The instrument subtype, one of 'um', 'cm', by default 'um'
|
|
144
|
+
timeframe : str, optional
|
|
145
|
+
The timeframe, one of '1m', '3m', '5m', '15m', '30m', '1h', '2h', '4h', '6h', '8h', '12h', '1d', '3d', '1w', '1M', by default '1m'
|
|
146
|
+
temp_storage : str, optional
|
|
147
|
+
The temporary storage location, by default 'DEFALT_LOCAL_FILE_STORAGE'
|
|
148
|
+
|
|
149
|
+
Returns
|
|
150
|
+
-------
|
|
151
|
+
pd.DataFrame
|
|
152
|
+
The loaded data
|
|
153
|
+
"""
|
|
154
|
+
start = pd.Timestamp(start)
|
|
155
|
+
stop = pd.Timestamp(stop)
|
|
156
|
+
if instr_type.lower() == 'futures':
|
|
157
|
+
subt = f"{instr_type}/{instr_subtype}"
|
|
158
|
+
else:
|
|
159
|
+
subt = 'spot'
|
|
160
|
+
temp_storage = join(temp_storage, subt)
|
|
161
|
+
|
|
162
|
+
def _loader(start: pd.Timestamp, stop: pd.Timestamp, f_units: str):
|
|
163
|
+
curr_date = pd.Timestamp('now').ceil('1d')
|
|
164
|
+
data = pd.DataFrame()
|
|
165
|
+
continue_from = None
|
|
166
|
+
for t, _ in generate_equal_date_ranges(start, stop, 1, f_units[0].upper()):
|
|
167
|
+
if f_units =='monthly':
|
|
168
|
+
dt = pd.Timestamp(t)
|
|
169
|
+
# stop when we got into current month
|
|
170
|
+
if dt.year == curr_date.year and dt.month == curr_date.month:
|
|
171
|
+
continue_from = t
|
|
172
|
+
break
|
|
173
|
+
dt = dt.strftime("%Y-%m")
|
|
174
|
+
else:
|
|
175
|
+
dt = pd.Timestamp(t).strftime("%Y-%m-%d")
|
|
176
|
+
|
|
177
|
+
fname = f"{symbol.upper()}-{timeframe}-{dt}.zip"
|
|
178
|
+
zf = join(temp_storage, fname)
|
|
179
|
+
|
|
180
|
+
if not exists(zf):
|
|
181
|
+
logger.info(f'[green]{(symbol)}[/green] {instr_subtype} {instr_type} loading data for [yellow]{dt}[/yellow] -> [red]{fname}[/red]')
|
|
182
|
+
u = f'{BINANCE_DATA_URL}data/{subt}/{f_units}/klines/{symbol.upper()}/{timeframe}/{fname}'
|
|
183
|
+
zf = fetch_file(u, temp_storage, progress_bar=False)
|
|
184
|
+
else:
|
|
185
|
+
logger.info(f'[green]{symbol}[/green] {instr_subtype} {instr_type} parsing data from [red]{fname}[/red]')
|
|
186
|
+
if zf and exists(zf):
|
|
187
|
+
try:
|
|
188
|
+
data = srows(data, parse_kl_file(zf), keep='last')
|
|
189
|
+
except Exception as err:
|
|
190
|
+
logger.warning(err)
|
|
191
|
+
unlink(zf)
|
|
192
|
+
return data, continue_from
|
|
193
|
+
|
|
194
|
+
# - load by months
|
|
195
|
+
data, cont_time = _loader(start, stop, 'monthly')
|
|
196
|
+
|
|
197
|
+
# - rest data load by days
|
|
198
|
+
if cont_time is not None:
|
|
199
|
+
data_cont, cont_time = _loader(cont_time, stop, 'daily')
|
|
200
|
+
data = srows(data, data_cont, keep='last')
|
|
201
|
+
|
|
202
|
+
return data
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def update_binance_data_storage(coins=[], quoted_in=['USDT'], market='futures', data_storage=DEFALT_LOCAL_CSV_STORAGE):
|
|
206
|
+
"""
|
|
207
|
+
Fetch data from the Binance data storage and save it as local csv files
|
|
208
|
+
TODO: csv is just temporary solution and we need to keep data in DB
|
|
209
|
+
"""
|
|
210
|
+
from binance.client import Client, HistoricalKlinesType
|
|
211
|
+
info = get_binance_symbol_info_for_type(['UM', 'CM', 'SPOT'])
|
|
212
|
+
|
|
213
|
+
if market.lower() == 'futures':
|
|
214
|
+
for sy in info['binance.um']['symbols']:
|
|
215
|
+
if sy['quoteAsset'] in quoted_in:
|
|
216
|
+
if coins and sy['baseAsset'] not in coins:
|
|
217
|
+
continue
|
|
218
|
+
symbol = sy['symbol']
|
|
219
|
+
start = pd.Timestamp(sy['onboardDate'], unit='ms')
|
|
220
|
+
data = load_binance_kl_history(symbol, start.strftime('%Y-%m-%d'), instr_type='futures', instr_subtype='um')
|
|
221
|
+
# - update in mongo db
|
|
222
|
+
if data is not None and not data.empty:
|
|
223
|
+
data.to_csv(join(data_storage, 'BINANCE.UM'))
|
|
224
|
+
# path = f'm1/BINANCEF:{symbol}'
|
|
225
|
+
# z_del(path, dbname=_DEFAULT_MARKET_DATA_DB)
|
|
226
|
+
# z_save(path, data, dbname=_DEFAULT_MARKET_DATA_DB)
|
|
227
|
+
del data
|
|
228
|
+
|
|
229
|
+
if market.lower() == 'spot':
|
|
230
|
+
client = Client()
|
|
231
|
+
for sy in info['binance']['symbols']:
|
|
232
|
+
if sy['quoteAsset'] in quoted_in:
|
|
233
|
+
if coins and sy['baseAsset'] not in coins:
|
|
234
|
+
continue
|
|
235
|
+
symbol = sy['symbol']
|
|
236
|
+
# - some dirty way to get historical data start for spot
|
|
237
|
+
d = client.get_historical_klines(
|
|
238
|
+
symbol, '1M', '2017-01-01', '2100-01-01', klines_type=HistoricalKlinesType.SPOT, limit=1000
|
|
239
|
+
)
|
|
240
|
+
start = pd.Timestamp(d[0][0], unit='ms')
|
|
241
|
+
data = load_binance_kl_history(symbol, start.strftime('%Y-%m-%d'), instr_type='spot', instr_subtype='-')
|
|
242
|
+
# - update in mongo db
|
|
243
|
+
if data is not None and not data.empty:
|
|
244
|
+
data.to_csv(join(data_storage, 'BINANCE'))
|
|
245
|
+
# path = f'm1/BINANCE:{symbol}'
|
|
246
|
+
# z_del(path, dbname=_DEFAULT_MARKET_DATA_DB)
|
|
247
|
+
# z_save(path, data, dbname=_DEFAULT_MARKET_DATA_DB)
|
|
248
|
+
del data
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def load_binance_markets_info() -> Tuple[pd.DataFrame, Dict[str, pd.DataFrame]]:
|
|
252
|
+
"""
|
|
253
|
+
Load binance market info using SPA (non-documented)
|
|
254
|
+
"""
|
|
255
|
+
resp = requests.get('https://www.binance.com/bapi/asset/v2/public/asset-service/product/get-products?includeEtf=false')
|
|
256
|
+
data = resp.json()
|
|
257
|
+
|
|
258
|
+
market_caps = {}
|
|
259
|
+
m_tags = defaultdict(list)
|
|
260
|
+
for r in data['data']:
|
|
261
|
+
symb = r['s']
|
|
262
|
+
market_caps[symb] = {
|
|
263
|
+
'Symbol': symb,
|
|
264
|
+
'MarketCap': float(r['cs']) * float(r['c']) / 1_000_000,
|
|
265
|
+
'Coin': r['b'],
|
|
266
|
+
'Quoted': r['q'],
|
|
267
|
+
'Name': r['an'],
|
|
268
|
+
'Tags': r['tags'],
|
|
269
|
+
}
|
|
270
|
+
for t in r['tags']:
|
|
271
|
+
m_tags[t].append({
|
|
272
|
+
'Symbol': symb,
|
|
273
|
+
'MarketCap': float(r['cs']) * float(r['c']) / 1_000_000,
|
|
274
|
+
'Coin': r['b'],
|
|
275
|
+
'Quoted': r['q'],
|
|
276
|
+
'Name': r['an']
|
|
277
|
+
})
|
|
278
|
+
mktcap = pd.DataFrame.from_dict(market_caps, orient='index').sort_values('MarketCap', ascending=False)
|
|
279
|
+
|
|
280
|
+
markets_tags = {}
|
|
281
|
+
for t, m in m_tags.items():
|
|
282
|
+
markets_tags[t.lower() if t else 'none'] = pd.DataFrame.from_records(m).sort_values('MarketCap', ascending=False)
|
|
283
|
+
|
|
284
|
+
return mktcap, markets_tags
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
import pandas as pd
|
|
4
|
+
|
|
5
|
+
from qubx.core.basics import AssetType, Instrument, MarketType
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def ccxt_build_qubx_exchange_name(ccxt_exchange: str, market_type: str | None = None, is_linear: bool = True) -> str:
|
|
9
|
+
"""
|
|
10
|
+
Build a Qubx exchange name from a ccxt exchange name and a market dictionary.
|
|
11
|
+
Parameters:
|
|
12
|
+
ccxt_exchange (str): The ccxt exchange name.
|
|
13
|
+
market (dict): The market dictionary.
|
|
14
|
+
Returns:
|
|
15
|
+
str: The Qubx exchange name.
|
|
16
|
+
"""
|
|
17
|
+
if ccxt_exchange == "BINANCE.PM":
|
|
18
|
+
if market_type in ["spot", "margin"]:
|
|
19
|
+
return "BINANCE"
|
|
20
|
+
elif market_type == "swap" and is_linear:
|
|
21
|
+
return "BINANCE.UM"
|
|
22
|
+
elif market_type == "swap" and not is_linear:
|
|
23
|
+
return "BINANCE.CM"
|
|
24
|
+
else:
|
|
25
|
+
return "BINANCE.UM"
|
|
26
|
+
else:
|
|
27
|
+
# for not just return the input exchange and extend later if needed
|
|
28
|
+
return ccxt_exchange
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def ccxt_symbol_to_instrument(ccxt_exchange_name: str, market: dict[str, Any]) -> Instrument:
|
|
32
|
+
exchange = ccxt_build_qubx_exchange_name(ccxt_exchange_name, market["type"], market.get("linear", True))
|
|
33
|
+
inner_info = market["info"]
|
|
34
|
+
maint_margin = 0.0
|
|
35
|
+
required_margin = 0.0
|
|
36
|
+
liquidation_fee = 0.0
|
|
37
|
+
if "marginLevels" in inner_info:
|
|
38
|
+
margins = inner_info["marginLevels"][0]
|
|
39
|
+
maint_margin = float(margins["maintenanceMargin"])
|
|
40
|
+
required_margin = float(margins["initialMargin"])
|
|
41
|
+
if "maintMarginPercent" in inner_info:
|
|
42
|
+
maint_margin = float(inner_info["maintMarginPercent"]) / 100
|
|
43
|
+
if "requiredMarginPercent" in inner_info:
|
|
44
|
+
required_margin = float(inner_info["requiredMarginPercent"]) / 100
|
|
45
|
+
if "liquidationFee" in inner_info:
|
|
46
|
+
liquidation_fee = float(inner_info["liquidationFee"])
|
|
47
|
+
|
|
48
|
+
# symbol = market["id"]
|
|
49
|
+
# let's use unified symbol format across all exchanges / types: BASEQUOTE
|
|
50
|
+
symbol = market["base"] + market["quote"]
|
|
51
|
+
|
|
52
|
+
# add some exchange specific formatting of symbol name
|
|
53
|
+
tick_size = float(market["precision"]["price"] or 0.0)
|
|
54
|
+
lot_size = float(market["precision"]["amount"] or 0.0)
|
|
55
|
+
min_size = float(market["limits"]["amount"]["min"] or 0.0)
|
|
56
|
+
min_notional = float(market["limits"]["cost"]["min"] or 0.0)
|
|
57
|
+
|
|
58
|
+
if exchange in ["BITFINEX", "BITFINEX.F"]:
|
|
59
|
+
if symbol.startswith("t"):
|
|
60
|
+
symbol = symbol[1:]
|
|
61
|
+
symbol = symbol.replace(":", "-")
|
|
62
|
+
tick_size = 10**-tick_size
|
|
63
|
+
lot_size = 10**-lot_size
|
|
64
|
+
min_size = 10**-min_size
|
|
65
|
+
min_notional = 10**-min_notional
|
|
66
|
+
|
|
67
|
+
return Instrument(
|
|
68
|
+
symbol=symbol,
|
|
69
|
+
asset_type=AssetType.CRYPTO,
|
|
70
|
+
market_type=MarketType[market["type"].upper()],
|
|
71
|
+
exchange=exchange,
|
|
72
|
+
base=market["base"],
|
|
73
|
+
quote=market["quote"],
|
|
74
|
+
settle=market["settle"],
|
|
75
|
+
exchange_symbol=market["id"],
|
|
76
|
+
tick_size=tick_size,
|
|
77
|
+
lot_size=lot_size,
|
|
78
|
+
min_size=min_size,
|
|
79
|
+
min_notional=min_notional,
|
|
80
|
+
initial_margin=required_margin,
|
|
81
|
+
maint_margin=maint_margin,
|
|
82
|
+
liquidation_fee=liquidation_fee,
|
|
83
|
+
contract_size=float(market.get("contractSize", 1.0) or 1.0),
|
|
84
|
+
onboard_date=pd.Timestamp(int(inner_info["onboardDate"]), unit="ms") if "onboardDate" in inner_info else None,
|
|
85
|
+
delivery_date=(
|
|
86
|
+
pd.Timestamp(int(market["expiryDatetime"]), unit="ms")
|
|
87
|
+
if "expiryDatetime" in inner_info
|
|
88
|
+
else pd.Timestamp("2100-01-01T00:00:00")
|
|
89
|
+
),
|
|
90
|
+
)
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
from qubx.core.basics import Instrument, AssetType, MarketType
|
|
2
|
+
|
|
3
|
+
SAMPLE_INSTRUMENTS = [
|
|
4
|
+
Instrument(
|
|
5
|
+
symbol="EURUSD",
|
|
6
|
+
asset_type=AssetType.FX,
|
|
7
|
+
market_type=MarketType.SPOT,
|
|
8
|
+
exchange="DUKAS",
|
|
9
|
+
base="EUR",
|
|
10
|
+
quote="USD",
|
|
11
|
+
settle="USD",
|
|
12
|
+
exchange_symbol="EURUSD",
|
|
13
|
+
tick_size=0.00001,
|
|
14
|
+
lot_size=1000,
|
|
15
|
+
min_size=1000,
|
|
16
|
+
min_notional=0.0,
|
|
17
|
+
initial_margin=0.0,
|
|
18
|
+
maint_margin=0.0,
|
|
19
|
+
liquidation_fee=0.0,
|
|
20
|
+
contract_size=1.0,
|
|
21
|
+
),
|
|
22
|
+
Instrument(
|
|
23
|
+
symbol="GBPUSD",
|
|
24
|
+
asset_type=AssetType.FX,
|
|
25
|
+
market_type=MarketType.SPOT,
|
|
26
|
+
exchange="DUKAS",
|
|
27
|
+
base="GBP",
|
|
28
|
+
quote="USD",
|
|
29
|
+
settle="USD",
|
|
30
|
+
exchange_symbol="GBPUSD",
|
|
31
|
+
tick_size=0.00001,
|
|
32
|
+
lot_size=1000,
|
|
33
|
+
min_size=1000,
|
|
34
|
+
min_notional=0.0,
|
|
35
|
+
initial_margin=0.0,
|
|
36
|
+
maint_margin=0.0,
|
|
37
|
+
liquidation_fee=0.0,
|
|
38
|
+
contract_size=1.0,
|
|
39
|
+
),
|
|
40
|
+
Instrument(
|
|
41
|
+
symbol="USDJPY",
|
|
42
|
+
asset_type=AssetType.FX,
|
|
43
|
+
market_type=MarketType.SPOT,
|
|
44
|
+
exchange="DUKAS",
|
|
45
|
+
base="USD",
|
|
46
|
+
quote="JPY",
|
|
47
|
+
settle="USD",
|
|
48
|
+
exchange_symbol="USDJPY",
|
|
49
|
+
tick_size=0.001,
|
|
50
|
+
lot_size=1000,
|
|
51
|
+
min_size=1000,
|
|
52
|
+
min_notional=0.0,
|
|
53
|
+
initial_margin=0.0,
|
|
54
|
+
maint_margin=0.0,
|
|
55
|
+
liquidation_fee=0.0,
|
|
56
|
+
contract_size=1.0,
|
|
57
|
+
),
|
|
58
|
+
Instrument(
|
|
59
|
+
symbol="USDCAD",
|
|
60
|
+
asset_type=AssetType.FX,
|
|
61
|
+
market_type=MarketType.SPOT,
|
|
62
|
+
exchange="DUKAS",
|
|
63
|
+
base="USD",
|
|
64
|
+
quote="CAD",
|
|
65
|
+
settle="USD",
|
|
66
|
+
exchange_symbol="USDCAD",
|
|
67
|
+
tick_size=0.00001,
|
|
68
|
+
lot_size=1000,
|
|
69
|
+
min_size=1000,
|
|
70
|
+
min_notional=0.0,
|
|
71
|
+
initial_margin=0.0,
|
|
72
|
+
maint_margin=0.0,
|
|
73
|
+
liquidation_fee=0.0,
|
|
74
|
+
contract_size=1.0,
|
|
75
|
+
),
|
|
76
|
+
Instrument(
|
|
77
|
+
symbol="AUDUSD",
|
|
78
|
+
asset_type=AssetType.FX,
|
|
79
|
+
market_type=MarketType.SPOT,
|
|
80
|
+
exchange="DUKAS",
|
|
81
|
+
base="AUD",
|
|
82
|
+
quote="USD",
|
|
83
|
+
settle="USD",
|
|
84
|
+
exchange_symbol="AUDUSD",
|
|
85
|
+
tick_size=0.00001,
|
|
86
|
+
lot_size=1000,
|
|
87
|
+
min_size=1000,
|
|
88
|
+
min_notional=0.0,
|
|
89
|
+
initial_margin=0.0,
|
|
90
|
+
maint_margin=0.0,
|
|
91
|
+
liquidation_fee=0.0,
|
|
92
|
+
contract_size=1.0,
|
|
93
|
+
),
|
|
94
|
+
Instrument(
|
|
95
|
+
symbol="USDPLN",
|
|
96
|
+
asset_type=AssetType.FX,
|
|
97
|
+
market_type=MarketType.SPOT,
|
|
98
|
+
exchange="DUKAS",
|
|
99
|
+
base="USD",
|
|
100
|
+
quote="PLN",
|
|
101
|
+
settle="USD",
|
|
102
|
+
exchange_symbol="USDPLN",
|
|
103
|
+
tick_size=0.00001,
|
|
104
|
+
lot_size=1000,
|
|
105
|
+
min_size=1000,
|
|
106
|
+
min_notional=0.0,
|
|
107
|
+
initial_margin=0.0,
|
|
108
|
+
maint_margin=0.0,
|
|
109
|
+
liquidation_fee=0.0,
|
|
110
|
+
contract_size=1.0,
|
|
111
|
+
),
|
|
112
|
+
Instrument(
|
|
113
|
+
symbol="EURGBP",
|
|
114
|
+
asset_type=AssetType.FX,
|
|
115
|
+
market_type=MarketType.SPOT,
|
|
116
|
+
exchange="DUKAS",
|
|
117
|
+
base="EUR",
|
|
118
|
+
quote="GBP",
|
|
119
|
+
settle="USD",
|
|
120
|
+
exchange_symbol="EURGBP",
|
|
121
|
+
tick_size=0.00001,
|
|
122
|
+
lot_size=1000,
|
|
123
|
+
min_size=1000,
|
|
124
|
+
min_notional=0.0,
|
|
125
|
+
initial_margin=0.0,
|
|
126
|
+
maint_margin=0.0,
|
|
127
|
+
liquidation_fee=0.0,
|
|
128
|
+
contract_size=1.0,
|
|
129
|
+
),
|
|
130
|
+
]
|