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,193 @@
|
|
|
1
|
+
from typing import Dict, List
|
|
2
|
+
|
|
3
|
+
import ccxt.pro as cxp
|
|
4
|
+
from ccxt.async_support.base.ws.cache import ArrayCache, ArrayCacheByTimestamp
|
|
5
|
+
from ccxt.async_support.base.ws.client import Client
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class BinanceQV(cxp.binance):
|
|
9
|
+
"""
|
|
10
|
+
Extended binance exchange to provide quote asset volumes support
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
def describe(self):
|
|
14
|
+
"""
|
|
15
|
+
Overriding watchTrades to use aggTrade instead of trade.
|
|
16
|
+
"""
|
|
17
|
+
return self.deep_extend(
|
|
18
|
+
super().describe(),
|
|
19
|
+
{
|
|
20
|
+
"options": {
|
|
21
|
+
"watchTrades": {
|
|
22
|
+
"name": "aggTrade",
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
def parse_ohlcv(self, ohlcv, market=None):
|
|
29
|
+
"""
|
|
30
|
+
[
|
|
31
|
+
1499040000000, // Kline open time 0
|
|
32
|
+
"0.01634790", // Open price 1
|
|
33
|
+
"0.80000000", // High price 2
|
|
34
|
+
"0.01575800", // Low price 3
|
|
35
|
+
"0.01577100", // Close price 4
|
|
36
|
+
"148976.11427815", // Volume 5
|
|
37
|
+
1499644799999, // Kline Close time 6
|
|
38
|
+
"2434.19055334", // Quote asset volume 7
|
|
39
|
+
308, // Number of trades 8
|
|
40
|
+
"1756.87402397", // Taker buy base asset volume 9
|
|
41
|
+
"28.46694368", // Taker buy quote asset volume 10
|
|
42
|
+
"0" // Unused field, ignore.
|
|
43
|
+
]
|
|
44
|
+
"""
|
|
45
|
+
return [
|
|
46
|
+
self.safe_integer(ohlcv, 0),
|
|
47
|
+
self.safe_number(ohlcv, 1),
|
|
48
|
+
self.safe_number(ohlcv, 2),
|
|
49
|
+
self.safe_number(ohlcv, 3),
|
|
50
|
+
self.safe_number(ohlcv, 4),
|
|
51
|
+
self.safe_number(ohlcv, 5),
|
|
52
|
+
self.safe_number(ohlcv, 7), # Quote asset volume
|
|
53
|
+
self.safe_number(ohlcv, 10), # Taker buy quote asset volume
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
def handle_ohlcv(self, client: Client, message):
|
|
57
|
+
event = self.safe_string(message, "e")
|
|
58
|
+
eventMap: dict = {
|
|
59
|
+
"indexPrice_kline": "indexPriceKline",
|
|
60
|
+
"markPrice_kline": "markPriceKline",
|
|
61
|
+
}
|
|
62
|
+
event = self.safe_string(eventMap, event, event)
|
|
63
|
+
kline = self.safe_value(message, "k")
|
|
64
|
+
marketId = self.safe_string_2(kline, "s", "ps")
|
|
65
|
+
if event == "indexPriceKline":
|
|
66
|
+
# indexPriceKline doesn't have the _PERP suffix
|
|
67
|
+
marketId = self.safe_string(message, "ps")
|
|
68
|
+
interval = self.safe_string(kline, "i")
|
|
69
|
+
# use a reverse lookup in a static map instead
|
|
70
|
+
unifiedTimeframe = self.find_timeframe(interval)
|
|
71
|
+
parsed = [
|
|
72
|
+
self.safe_integer(kline, "t"),
|
|
73
|
+
self.safe_float(kline, "o"),
|
|
74
|
+
self.safe_float(kline, "h"),
|
|
75
|
+
self.safe_float(kline, "l"),
|
|
76
|
+
self.safe_float(kline, "c"),
|
|
77
|
+
self.safe_float(kline, "v"),
|
|
78
|
+
# - additional fields
|
|
79
|
+
self.safe_float(kline, "q"), # - quote asset volume
|
|
80
|
+
self.safe_float(kline, "Q"), # - taker buy quote asset volume
|
|
81
|
+
# self.safe_integer(message, "E")
|
|
82
|
+
]
|
|
83
|
+
isSpot = (client.url.find("/stream") > -1) or (client.url.find("/testnet.binance") > -1)
|
|
84
|
+
marketType = "spot" if (isSpot) else "contract"
|
|
85
|
+
symbol = self.safe_symbol(marketId, None, None, marketType)
|
|
86
|
+
messageHash = "ohlcv::" + symbol + "::" + unifiedTimeframe
|
|
87
|
+
self.ohlcvs[symbol] = self.safe_value(self.ohlcvs, symbol, {})
|
|
88
|
+
stored = self.safe_value(self.ohlcvs[symbol], unifiedTimeframe)
|
|
89
|
+
if stored is None:
|
|
90
|
+
limit = self.safe_integer(self.options, "OHLCVLimit", 1000)
|
|
91
|
+
stored = ArrayCacheByTimestamp(limit)
|
|
92
|
+
self.ohlcvs[symbol][unifiedTimeframe] = stored
|
|
93
|
+
stored.append(parsed)
|
|
94
|
+
resolveData = [symbol, unifiedTimeframe, stored]
|
|
95
|
+
client.resolve(resolveData, messageHash)
|
|
96
|
+
|
|
97
|
+
def handle_trade(self, client: Client, message):
|
|
98
|
+
"""
|
|
99
|
+
There is a custom trade handler implementation, because Binance sends
|
|
100
|
+
some trades marked with "X" field, which is "MARKET" for market trades
|
|
101
|
+
and "INSURANCE_FUND" for insurance fund trades. We are interested only
|
|
102
|
+
in market trades, so we filter the rest out.
|
|
103
|
+
|
|
104
|
+
Update 07072024: Apparently insurance fund trades not aggregated so
|
|
105
|
+
we don't need to filter via "X" field, but let's keep it just in case.
|
|
106
|
+
"""
|
|
107
|
+
# the trade streams push raw trade information in real-time
|
|
108
|
+
# each trade has a unique buyer and seller
|
|
109
|
+
isSpot = (client.url.find("wss://stream.binance.com") > -1) or (client.url.find("/testnet.binance") > -1)
|
|
110
|
+
marketType = "spot" if (isSpot) else "contract"
|
|
111
|
+
marketId = self.safe_string(message, "s")
|
|
112
|
+
market = self.safe_market(marketId, None, None, marketType)
|
|
113
|
+
symbol = market["symbol"]
|
|
114
|
+
messageHash = "trade::" + symbol
|
|
115
|
+
executionType = self.safe_string(message, "X")
|
|
116
|
+
if executionType == "INSURANCE_FUND":
|
|
117
|
+
return
|
|
118
|
+
trade = self.parse_ws_trade(message, market)
|
|
119
|
+
tradesArray = self.safe_value(self.trades, symbol)
|
|
120
|
+
if tradesArray is None:
|
|
121
|
+
limit = self.safe_integer(self.options, "tradesLimit", 1000)
|
|
122
|
+
tradesArray = ArrayCache(limit)
|
|
123
|
+
tradesArray.append(trade)
|
|
124
|
+
self.trades[symbol] = tradesArray
|
|
125
|
+
client.resolve(tradesArray, messageHash)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class BinanceQVUSDM(cxp.binanceusdm, BinanceQV):
|
|
129
|
+
"""
|
|
130
|
+
The order of inheritance is important here, because we want
|
|
131
|
+
binanceusdm to take precedence over binanceqv. And this is how MRO is defined
|
|
132
|
+
in Python.
|
|
133
|
+
|
|
134
|
+
Describe method needs to be overriden, because of the way super is called in binanceusdm.
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
_funding_intervals: Dict[str, str]
|
|
138
|
+
|
|
139
|
+
def __init__(self, *args, **kwargs):
|
|
140
|
+
super().__init__(*args, **kwargs)
|
|
141
|
+
self._funding_intervals = {}
|
|
142
|
+
|
|
143
|
+
def describe(self):
|
|
144
|
+
"""
|
|
145
|
+
Overriding watchTrades to use aggTrade instead of trade.
|
|
146
|
+
"""
|
|
147
|
+
return self.deep_extend(
|
|
148
|
+
super().describe(),
|
|
149
|
+
{
|
|
150
|
+
"options": {
|
|
151
|
+
"watchTrades": {
|
|
152
|
+
"name": "aggTrade",
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
async def watch_funding_rates(self, symbols: List[str] | None = None):
|
|
159
|
+
await self.load_markets()
|
|
160
|
+
await self._update_funding_intervals()
|
|
161
|
+
mark_prices = await self.watch_mark_prices(symbols)
|
|
162
|
+
funding_rates = {}
|
|
163
|
+
for symbol, info in mark_prices.items():
|
|
164
|
+
interval = self._funding_intervals.get(symbol, "8h")
|
|
165
|
+
funding_rates[symbol] = {
|
|
166
|
+
"timestamp": info["timestamp"],
|
|
167
|
+
"interval": interval,
|
|
168
|
+
"fundingRate": float(info["info"]["r"]),
|
|
169
|
+
"nextFundingTime": info["info"]["T"],
|
|
170
|
+
"markPrice": info["markPrice"],
|
|
171
|
+
"indexPrice": info["indexPrice"],
|
|
172
|
+
}
|
|
173
|
+
return funding_rates
|
|
174
|
+
|
|
175
|
+
async def _update_funding_intervals(self):
|
|
176
|
+
if self._funding_intervals:
|
|
177
|
+
return
|
|
178
|
+
symbol_to_info = await self.fetch_funding_intervals()
|
|
179
|
+
self._funding_intervals = {str(s): str(info["interval"]) for s, info in symbol_to_info.items()}
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
class BinancePortfolioMargin(BinanceQVUSDM):
|
|
183
|
+
def describe(self):
|
|
184
|
+
return self.deep_extend(
|
|
185
|
+
super().describe(),
|
|
186
|
+
{
|
|
187
|
+
"options": {
|
|
188
|
+
"defaultType": "margin",
|
|
189
|
+
"portfolioMargin": True,
|
|
190
|
+
"defaultSubType": None,
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
)
|