Qubx 0.6.84__cp312-cp312-manylinux_2_39_x86_64.whl → 0.6.85__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/connectors/ccxt/account.py +1 -0
- qubx/connectors/ccxt/exchanges/hyperliquid/broker.py +17 -9
- qubx/connectors/ccxt/handlers/orderbook.py +8 -6
- qubx/connectors/ccxt/handlers/trade.py +116 -20
- qubx/core/helpers.py +9 -3
- qubx/core/mixins/subscription.py +7 -1
- qubx/core/series.cpython-312-x86_64-linux-gnu.so +0 -0
- qubx/core/series.pxd +3 -2
- qubx/core/series.pyi +3 -2
- qubx/core/series.pyx +30 -5
- qubx/core/utils.cpython-312-x86_64-linux-gnu.so +0 -0
- qubx/ta/indicators.cpython-312-x86_64-linux-gnu.so +0 -0
- {qubx-0.6.84.dist-info → qubx-0.6.85.dist-info}/METADATA +1 -1
- {qubx-0.6.84.dist-info → qubx-0.6.85.dist-info}/RECORD +17 -17
- {qubx-0.6.84.dist-info → qubx-0.6.85.dist-info}/WHEEL +0 -0
- {qubx-0.6.84.dist-info → qubx-0.6.85.dist-info}/entry_points.txt +0 -0
- {qubx-0.6.84.dist-info → qubx-0.6.85.dist-info}/licenses/LICENSE +0 -0
qubx/connectors/ccxt/account.py
CHANGED
|
@@ -130,6 +130,7 @@ class CcxtAccountProcessor(BasicAccountProcessor):
|
|
|
130
130
|
|
|
131
131
|
if not self.exchange_manager.exchange.isSandboxModeEnabled:
|
|
132
132
|
# - start polling tasks
|
|
133
|
+
self._loop.submit(self.exchange_manager.exchange.load_markets()).result()
|
|
133
134
|
self._polling_tasks["balance"] = self._loop.submit(
|
|
134
135
|
self._poller("balance", self._update_balance, self.balance_interval)
|
|
135
136
|
)
|
|
@@ -9,7 +9,7 @@ from qubx.core.exceptions import BadRequest
|
|
|
9
9
|
class HyperliquidCcxtBroker(CcxtBroker):
|
|
10
10
|
"""
|
|
11
11
|
HyperLiquid-specific broker that handles market order slippage requirements.
|
|
12
|
-
|
|
12
|
+
|
|
13
13
|
HyperLiquid requires a price even for market orders to calculate max slippage.
|
|
14
14
|
This broker automatically calculates slippage-protected prices for market orders.
|
|
15
15
|
"""
|
|
@@ -17,7 +17,7 @@ class HyperliquidCcxtBroker(CcxtBroker):
|
|
|
17
17
|
def __init__(
|
|
18
18
|
self,
|
|
19
19
|
*args,
|
|
20
|
-
market_order_slippage: float = 0.
|
|
20
|
+
market_order_slippage: float = 0.01, # 5% default slippage
|
|
21
21
|
**kwargs,
|
|
22
22
|
):
|
|
23
23
|
super().__init__(*args, **kwargs)
|
|
@@ -38,21 +38,29 @@ class HyperliquidCcxtBroker(CcxtBroker):
|
|
|
38
38
|
if order_type.lower() == "market" and price is None:
|
|
39
39
|
quote = self.data_provider.get_quote(instrument)
|
|
40
40
|
if quote is None:
|
|
41
|
-
logger.warning(
|
|
42
|
-
|
|
41
|
+
logger.warning(
|
|
42
|
+
f"[<y>{instrument.symbol}</y>] :: Quote is not available for market order slippage calculation."
|
|
43
|
+
)
|
|
44
|
+
raise BadRequest(
|
|
45
|
+
f"Quote is not available for market order slippage calculation for {instrument.symbol}"
|
|
46
|
+
)
|
|
43
47
|
|
|
44
48
|
# Get slippage from options or use default
|
|
45
49
|
slippage = options.get("slippage", self.market_order_slippage)
|
|
46
|
-
|
|
50
|
+
|
|
47
51
|
# Calculate slippage-protected price
|
|
48
52
|
if order_side.upper() == "BUY":
|
|
49
53
|
# For buy orders, add slippage to ask price to ensure execution
|
|
50
54
|
price = quote.ask * (1 + slippage)
|
|
51
|
-
logger.debug(
|
|
55
|
+
logger.debug(
|
|
56
|
+
f"[<y>{instrument.symbol}</y>] :: Market BUY order: using slippage-protected price {price:.6f} (ask: {quote.ask:.6f}, slippage: {slippage:.1%})"
|
|
57
|
+
)
|
|
52
58
|
else: # SELL
|
|
53
59
|
# For sell orders, subtract slippage from bid price to ensure execution
|
|
54
60
|
price = quote.bid * (1 - slippage)
|
|
55
|
-
logger.debug(
|
|
61
|
+
logger.debug(
|
|
62
|
+
f"[<y>{instrument.symbol}</y>] :: Market SELL order: using slippage-protected price {price:.6f} (bid: {quote.bid:.6f}, slippage: {slippage:.1%})"
|
|
63
|
+
)
|
|
56
64
|
|
|
57
65
|
# Call parent implementation with calculated price
|
|
58
66
|
payload = super()._prepare_order_payload(
|
|
@@ -64,6 +72,6 @@ class HyperliquidCcxtBroker(CcxtBroker):
|
|
|
64
72
|
if "slippage" in options:
|
|
65
73
|
# HyperLiquid accepts slippage as a percentage (e.g., 0.05 for 5%)
|
|
66
74
|
params["px"] = price # Explicit price for slippage calculation
|
|
67
|
-
|
|
75
|
+
|
|
68
76
|
payload["params"] = params
|
|
69
|
-
return payload
|
|
77
|
+
return payload
|
|
@@ -65,7 +65,7 @@ class OrderBookDataHandler(BaseDataTypeHandler):
|
|
|
65
65
|
|
|
66
66
|
# Notify all listeners
|
|
67
67
|
self._data_provider.notify_data_arrival(sub_type, dt_64(ob.time, "ns"))
|
|
68
|
-
|
|
68
|
+
|
|
69
69
|
channel.send((instrument, sub_type, ob, False))
|
|
70
70
|
return True
|
|
71
71
|
|
|
@@ -150,7 +150,7 @@ class OrderBookDataHandler(BaseDataTypeHandler):
|
|
|
150
150
|
) -> SubscriptionConfiguration:
|
|
151
151
|
"""
|
|
152
152
|
Prepare subscription configuration for individual instruments.
|
|
153
|
-
|
|
153
|
+
|
|
154
154
|
Creates separate subscriber functions for each instrument to enable independent
|
|
155
155
|
WebSocket streams without waiting for all instruments. This follows the same
|
|
156
156
|
pattern as the OHLC handler for proper individual stream management.
|
|
@@ -169,10 +169,10 @@ class OrderBookDataHandler(BaseDataTypeHandler):
|
|
|
169
169
|
try:
|
|
170
170
|
# Watch orderbook for single instrument
|
|
171
171
|
ccxt_ob = await self._exchange_manager.exchange.watch_order_book(symbol)
|
|
172
|
-
|
|
172
|
+
|
|
173
173
|
# Use private processing method to avoid duplication
|
|
174
174
|
self._process_orderbook(ccxt_ob, inst, sub_type, channel, depth, tick_size_pct)
|
|
175
|
-
|
|
175
|
+
|
|
176
176
|
except Exception as e:
|
|
177
177
|
logger.error(
|
|
178
178
|
f"<yellow>{exchange_id}</yellow> Error in individual orderbook subscription for {inst.symbol}: {e}"
|
|
@@ -186,13 +186,15 @@ class OrderBookDataHandler(BaseDataTypeHandler):
|
|
|
186
186
|
# Create individual unsubscriber if exchange supports it
|
|
187
187
|
un_watch_method = getattr(self._exchange_manager.exchange, "un_watch_order_book", None)
|
|
188
188
|
if un_watch_method is not None and callable(un_watch_method):
|
|
189
|
-
|
|
189
|
+
|
|
190
190
|
def create_individual_unsubscriber(symbol=ccxt_symbol, exchange_id=self._exchange_id):
|
|
191
191
|
async def individual_unsubscriber():
|
|
192
192
|
try:
|
|
193
193
|
await self._exchange_manager.exchange.un_watch_order_book(symbol)
|
|
194
194
|
except Exception as e:
|
|
195
|
-
logger.error(
|
|
195
|
+
logger.error(
|
|
196
|
+
f"<yellow>{exchange_id}</yellow> Error unsubscribing orderbook for {symbol}: {e}"
|
|
197
|
+
)
|
|
196
198
|
|
|
197
199
|
return individual_unsubscriber
|
|
198
200
|
|
|
@@ -27,6 +27,40 @@ class TradeDataHandler(BaseDataTypeHandler):
|
|
|
27
27
|
def data_type(self) -> str:
|
|
28
28
|
return "trade"
|
|
29
29
|
|
|
30
|
+
def _process_trade(self, trades: list, instrument: Instrument, sub_type: str, channel: CtrlChannel):
|
|
31
|
+
"""
|
|
32
|
+
Process trades with synthetic quote generation.
|
|
33
|
+
|
|
34
|
+
This method handles the common logic for processing trade data that's shared between
|
|
35
|
+
bulk and individual subscription approaches.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
trades: List of CCXT trade dictionaries
|
|
39
|
+
instrument: Instrument these trades belong to
|
|
40
|
+
sub_type: Subscription type string
|
|
41
|
+
channel: Control channel to send data through
|
|
42
|
+
"""
|
|
43
|
+
for trade in trades:
|
|
44
|
+
converted_trade = ccxt_convert_trade(trade)
|
|
45
|
+
|
|
46
|
+
# Notify all listeners
|
|
47
|
+
self._data_provider.notify_data_arrival(sub_type, dt_64(converted_trade.time, "ns"))
|
|
48
|
+
|
|
49
|
+
channel.send((instrument, sub_type, converted_trade, False))
|
|
50
|
+
|
|
51
|
+
# Generate synthetic quote if no quote/orderbook subscription exists
|
|
52
|
+
if len(trades) > 0 and not (
|
|
53
|
+
self._data_provider.has_subscription(instrument, DataType.ORDERBOOK)
|
|
54
|
+
or self._data_provider.has_subscription(instrument, DataType.QUOTE)
|
|
55
|
+
):
|
|
56
|
+
last_trade = trades[-1]
|
|
57
|
+
converted_trade = ccxt_convert_trade(last_trade)
|
|
58
|
+
_price = converted_trade.price
|
|
59
|
+
_time = converted_trade.time
|
|
60
|
+
_s2 = instrument.tick_size / 2.0
|
|
61
|
+
_bid, _ask = _price - _s2, _price + _s2
|
|
62
|
+
self._data_provider._last_quotes[instrument] = Quote(_time, _bid, _ask, 0.0, 0.0)
|
|
63
|
+
|
|
30
64
|
def prepare_subscription(
|
|
31
65
|
self, name: str, sub_type: str, channel: CtrlChannel, instruments: Set[Instrument], **params
|
|
32
66
|
) -> SubscriptionConfiguration:
|
|
@@ -42,6 +76,21 @@ class TradeDataHandler(BaseDataTypeHandler):
|
|
|
42
76
|
Returns:
|
|
43
77
|
SubscriptionConfiguration with subscriber and unsubscriber functions
|
|
44
78
|
"""
|
|
79
|
+
# Use exchange-specific approach based on capabilities
|
|
80
|
+
if self._exchange_manager.exchange.has.get("watchTradesForSymbols", False):
|
|
81
|
+
return self._prepare_subscription_for_instruments(name, sub_type, channel, instruments)
|
|
82
|
+
else:
|
|
83
|
+
# Fall back to individual instrument subscriptions
|
|
84
|
+
return self._prepare_subscription_for_individual_instruments(name, sub_type, channel, instruments)
|
|
85
|
+
|
|
86
|
+
def _prepare_subscription_for_instruments(
|
|
87
|
+
self,
|
|
88
|
+
name: str,
|
|
89
|
+
sub_type: str,
|
|
90
|
+
channel: CtrlChannel,
|
|
91
|
+
instruments: Set[Instrument],
|
|
92
|
+
) -> SubscriptionConfiguration:
|
|
93
|
+
"""Prepare subscription configuration for multiple instruments using bulk API."""
|
|
45
94
|
_instr_to_ccxt_symbol = {i: instrument_to_ccxt_symbol(i) for i in instruments}
|
|
46
95
|
_symbol_to_instrument = {_instr_to_ccxt_symbol[i]: i for i in instruments}
|
|
47
96
|
|
|
@@ -52,31 +101,13 @@ class TradeDataHandler(BaseDataTypeHandler):
|
|
|
52
101
|
exch_symbol = trades[0]["symbol"]
|
|
53
102
|
instrument = ccxt_find_instrument(exch_symbol, self._exchange_manager.exchange, _symbol_to_instrument)
|
|
54
103
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
# Notify all listeners
|
|
59
|
-
self._data_provider.notify_data_arrival(sub_type, dt_64(converted_trade.time, "ns"))
|
|
60
|
-
|
|
61
|
-
channel.send((instrument, sub_type, converted_trade, False))
|
|
62
|
-
|
|
63
|
-
if len(trades) > 0 and not (
|
|
64
|
-
self._data_provider.has_subscription(instrument, DataType.ORDERBOOK)
|
|
65
|
-
or self._data_provider.has_subscription(instrument, DataType.QUOTE)
|
|
66
|
-
):
|
|
67
|
-
last_trade = trades[-1]
|
|
68
|
-
converted_trade = ccxt_convert_trade(last_trade)
|
|
69
|
-
_price = converted_trade.price
|
|
70
|
-
_time = converted_trade.time
|
|
71
|
-
_s2 = instrument.tick_size / 2.0
|
|
72
|
-
_bid, _ask = _price - _s2, _price + _s2
|
|
73
|
-
self._data_provider._last_quotes[instrument] = Quote(_time, _bid, _ask, 0.0, 0.0)
|
|
104
|
+
# Use private processing method to avoid duplication
|
|
105
|
+
self._process_trade(trades, instrument, sub_type, channel)
|
|
74
106
|
|
|
75
107
|
async def un_watch_trades(instruments_batch: list[Instrument]):
|
|
76
108
|
symbols = [_instr_to_ccxt_symbol[i] for i in instruments_batch]
|
|
77
109
|
await self._exchange_manager.exchange.un_watch_trades_for_symbols(symbols)
|
|
78
110
|
|
|
79
|
-
# Return subscription configuration instead of calling _listen_to_stream directly
|
|
80
111
|
return SubscriptionConfiguration(
|
|
81
112
|
subscription_type=sub_type,
|
|
82
113
|
subscriber_func=create_market_type_batched_subscriber(watch_trades, instruments),
|
|
@@ -85,6 +116,71 @@ class TradeDataHandler(BaseDataTypeHandler):
|
|
|
85
116
|
requires_market_type_batching=True,
|
|
86
117
|
)
|
|
87
118
|
|
|
119
|
+
def _prepare_subscription_for_individual_instruments(
|
|
120
|
+
self,
|
|
121
|
+
name: str,
|
|
122
|
+
sub_type: str,
|
|
123
|
+
channel: CtrlChannel,
|
|
124
|
+
instruments: Set[Instrument],
|
|
125
|
+
) -> SubscriptionConfiguration:
|
|
126
|
+
"""
|
|
127
|
+
Prepare subscription configuration for individual instruments.
|
|
128
|
+
|
|
129
|
+
Creates separate subscriber functions for each instrument to enable independent
|
|
130
|
+
WebSocket streams without waiting for all instruments. This follows the same
|
|
131
|
+
pattern as the orderbook handler for proper individual stream management.
|
|
132
|
+
"""
|
|
133
|
+
_instr_to_ccxt_symbol = {i: instrument_to_ccxt_symbol(i) for i in instruments}
|
|
134
|
+
|
|
135
|
+
individual_subscribers = {}
|
|
136
|
+
individual_unsubscribers = {}
|
|
137
|
+
|
|
138
|
+
for instrument in instruments:
|
|
139
|
+
ccxt_symbol = _instr_to_ccxt_symbol[instrument]
|
|
140
|
+
|
|
141
|
+
# Create individual subscriber for this instrument using closure
|
|
142
|
+
def create_individual_subscriber(inst=instrument, symbol=ccxt_symbol, exchange_id=self._exchange_id):
|
|
143
|
+
async def individual_subscriber():
|
|
144
|
+
try:
|
|
145
|
+
# Watch trades for single instrument
|
|
146
|
+
trades = await self._exchange_manager.exchange.watch_trades(symbol)
|
|
147
|
+
|
|
148
|
+
# Use private processing method to avoid duplication
|
|
149
|
+
self._process_trade(trades, inst, sub_type, channel)
|
|
150
|
+
|
|
151
|
+
except Exception as e:
|
|
152
|
+
logger.error(
|
|
153
|
+
f"<yellow>{exchange_id}</yellow> Error in individual trade subscription for {inst.symbol}: {e}"
|
|
154
|
+
)
|
|
155
|
+
raise # Let connection manager handle retries
|
|
156
|
+
|
|
157
|
+
return individual_subscriber
|
|
158
|
+
|
|
159
|
+
individual_subscribers[instrument] = create_individual_subscriber()
|
|
160
|
+
|
|
161
|
+
# Create individual unsubscriber if exchange supports it
|
|
162
|
+
un_watch_method = getattr(self._exchange_manager.exchange, "un_watch_trades", None)
|
|
163
|
+
if un_watch_method is not None and callable(un_watch_method):
|
|
164
|
+
|
|
165
|
+
def create_individual_unsubscriber(symbol=ccxt_symbol, exchange_id=self._exchange_id):
|
|
166
|
+
async def individual_unsubscriber():
|
|
167
|
+
try:
|
|
168
|
+
await self._exchange_manager.exchange.un_watch_trades(symbol)
|
|
169
|
+
except Exception as e:
|
|
170
|
+
logger.error(f"<yellow>{exchange_id}</yellow> Error unsubscribing trades for {symbol}: {e}")
|
|
171
|
+
|
|
172
|
+
return individual_unsubscriber
|
|
173
|
+
|
|
174
|
+
individual_unsubscribers[instrument] = create_individual_unsubscriber()
|
|
175
|
+
|
|
176
|
+
return SubscriptionConfiguration(
|
|
177
|
+
subscription_type=sub_type,
|
|
178
|
+
instrument_subscribers=individual_subscribers,
|
|
179
|
+
instrument_unsubscribers=individual_unsubscribers if individual_unsubscribers else None,
|
|
180
|
+
stream_name=name,
|
|
181
|
+
requires_market_type_batching=False,
|
|
182
|
+
)
|
|
183
|
+
|
|
88
184
|
async def warmup(self, instruments: Set[Instrument], channel: CtrlChannel, warmup_period: str, **params) -> None:
|
|
89
185
|
"""
|
|
90
186
|
Fetch historical trade data for warmup during backtesting.
|
qubx/core/helpers.py
CHANGED
|
@@ -233,10 +233,16 @@ class CachedMarketDataHolder:
|
|
|
233
233
|
if series:
|
|
234
234
|
total_vol = trade.size
|
|
235
235
|
bought_vol = total_vol if trade.side == 1 else 0.0
|
|
236
|
+
volume_quote = trade.price * trade.size
|
|
237
|
+
bought_volume_quote = volume_quote if trade.side == 1 else 0.0
|
|
236
238
|
for ser in series.values():
|
|
237
|
-
if len(ser) > 0
|
|
238
|
-
|
|
239
|
-
|
|
239
|
+
if len(ser) > 0:
|
|
240
|
+
current_bar_start = floor_t64(np.datetime64(ser[0].time, 'ns'), np.timedelta64(ser.timeframe, 'ns'))
|
|
241
|
+
trade_bar_start = floor_t64(np.datetime64(trade.time, 'ns'), np.timedelta64(ser.timeframe, 'ns'))
|
|
242
|
+
if trade_bar_start < current_bar_start:
|
|
243
|
+
# Trade belongs to a previous bar - skip it
|
|
244
|
+
continue
|
|
245
|
+
ser.update(trade.time, trade.price, total_vol, bought_vol, volume_quote, bought_volume_quote, 1)
|
|
240
246
|
|
|
241
247
|
def finalize_ohlc_for_instruments(self, time: dt_64, instruments: list[Instrument]):
|
|
242
248
|
"""
|
qubx/core/mixins/subscription.py
CHANGED
|
@@ -235,10 +235,16 @@ class SubscriptionManager(ISubscriptionManager):
|
|
|
235
235
|
self._pending_warmups[(sub, instr)] = _warmup_period
|
|
236
236
|
|
|
237
237
|
# TODO: think about appropriate handling of timeouts
|
|
238
|
-
|
|
238
|
+
_warmup_configs = self._get_pending_warmups_for_exchange(_data_provider.exchange())
|
|
239
|
+
_data_provider.warmup(_warmup_configs)
|
|
239
240
|
|
|
240
241
|
self._pending_warmups.clear()
|
|
241
242
|
|
|
243
|
+
def _get_pending_warmups_for_exchange(self, exchange: str) -> dict[tuple[str, Instrument], str]:
|
|
244
|
+
return {
|
|
245
|
+
(sub, instr): period for (sub, instr), period in self._pending_warmups.items() if instr.exchange == exchange
|
|
246
|
+
}
|
|
247
|
+
|
|
242
248
|
def _get_data_provider(self, exchange: str) -> IDataProvider:
|
|
243
249
|
if exchange in self._exchange_to_data_provider:
|
|
244
250
|
return self._exchange_to_data_provider[exchange]
|
|
Binary file
|
qubx/core/series.pxd
CHANGED
|
@@ -63,7 +63,7 @@ cdef class Bar:
|
|
|
63
63
|
cdef public double bought_volume_quote # volume bought (in quote asset) if presented
|
|
64
64
|
cdef public int trade_count # number of trades in this bar
|
|
65
65
|
|
|
66
|
-
cpdef Bar update(Bar self, double price, double volume, double
|
|
66
|
+
cpdef Bar update(Bar self, double price, double volume, double bought_volume=*, double volume_quote=*, double bought_volume_quote=*, int trade_count=*)
|
|
67
67
|
|
|
68
68
|
cpdef dict to_dict(Bar self, unsigned short skip_time=*)
|
|
69
69
|
|
|
@@ -78,8 +78,9 @@ cdef class OHLCV(TimeSeries):
|
|
|
78
78
|
cdef public TimeSeries volume_quote
|
|
79
79
|
cdef public TimeSeries bvolume_quote
|
|
80
80
|
cdef public TimeSeries trade_count
|
|
81
|
+
cdef public dict columns
|
|
81
82
|
|
|
82
|
-
cpdef short update(OHLCV self, long long time, double price, double volume=*, double bvolume=*)
|
|
83
|
+
cpdef short update(OHLCV self, long long time, double price, double volume=*, double bvolume=*, double volume_quote=*, double bought_volume_quote=*, int trade_count=*)
|
|
83
84
|
|
|
84
85
|
cpdef short update_by_bar(
|
|
85
86
|
OHLCV self, long long time, double open, double high, double low, double close,
|
qubx/core/series.pyi
CHANGED
|
@@ -31,8 +31,8 @@ class Bar:
|
|
|
31
31
|
self,
|
|
32
32
|
price: float,
|
|
33
33
|
volume: float,
|
|
34
|
-
volume_quote: float = 0,
|
|
35
34
|
bought_volume: float = 0,
|
|
35
|
+
volume_quote: float = 0,
|
|
36
36
|
bought_volume_quote: float = 0,
|
|
37
37
|
trade_count: int = 0,
|
|
38
38
|
) -> "Bar": ...
|
|
@@ -127,7 +127,8 @@ class OHLCV(TimeSeries):
|
|
|
127
127
|
|
|
128
128
|
def __init__(self, name, timeframe, max_series_length: int | float = np.inf) -> None: ...
|
|
129
129
|
def __len__(self) -> int: ...
|
|
130
|
-
def update(self, time: int, price: float, volume: float = 0.0, bvolume: float = 0.0
|
|
130
|
+
def update(self, time: int, price: float, volume: float = 0.0, bvolume: float = 0.0,
|
|
131
|
+
volume_quote: float = 0.0, bought_volume_quote: float = 0.0, trade_count: int = 0) -> bool: ...
|
|
131
132
|
def update_by_bar(
|
|
132
133
|
self,
|
|
133
134
|
time: int,
|
qubx/core/series.pyx
CHANGED
|
@@ -773,7 +773,7 @@ cdef class Bar:
|
|
|
773
773
|
self.bought_volume += bought_volume
|
|
774
774
|
self.volume_quote += volume_quote
|
|
775
775
|
self.bought_volume_quote += bought_volume_quote
|
|
776
|
-
self.trade_count
|
|
776
|
+
self.trade_count += trade_count # Increment trade count
|
|
777
777
|
return self
|
|
778
778
|
|
|
779
779
|
cpdef dict to_dict(self, unsigned short skip_time=0):
|
|
@@ -1020,6 +1020,22 @@ cdef class OHLCV(TimeSeries):
|
|
|
1020
1020
|
self.volume_quote = TimeSeries('volume_quote', timeframe, max_series_length)
|
|
1021
1021
|
self.bvolume_quote = TimeSeries('bvolume_quote', timeframe, max_series_length)
|
|
1022
1022
|
self.trade_count = TimeSeries('trade_count', timeframe, max_series_length)
|
|
1023
|
+
self.columns = {
|
|
1024
|
+
"open": self.open,
|
|
1025
|
+
"high": self.high,
|
|
1026
|
+
"low": self.low,
|
|
1027
|
+
"close": self.close,
|
|
1028
|
+
"volume": self.volume,
|
|
1029
|
+
"bvolume": self.bvolume,
|
|
1030
|
+
"volume_quote": self.volume_quote,
|
|
1031
|
+
"bvolume_quote": self.bvolume_quote,
|
|
1032
|
+
"trade_count": self.trade_count,
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
def __getitem__(self, idx):
|
|
1036
|
+
if isinstance(idx, str):
|
|
1037
|
+
return self.columns[idx]
|
|
1038
|
+
return super().__getitem__(idx)
|
|
1023
1039
|
|
|
1024
1040
|
cpdef object append_data(self,
|
|
1025
1041
|
np.ndarray times,
|
|
@@ -1114,18 +1130,25 @@ cdef class OHLCV(TimeSeries):
|
|
|
1114
1130
|
self.trade_count._update_last_item(time, value.trade_count)
|
|
1115
1131
|
self._is_new_item = False
|
|
1116
1132
|
|
|
1117
|
-
cpdef short update(self, long long time, double price, double volume=0.0, double bvolume=0.0
|
|
1133
|
+
cpdef short update(self, long long time, double price, double volume=0.0, double bvolume=0.0,
|
|
1134
|
+
double volume_quote=0.0, double bought_volume_quote=0.0, int trade_count=0):
|
|
1118
1135
|
cdef Bar b
|
|
1119
1136
|
bar_start_time = floor_t64(time, self.timeframe)
|
|
1120
1137
|
|
|
1121
1138
|
if not self.times:
|
|
1122
|
-
self._add_new_item(bar_start_time, Bar(bar_start_time, price, price, price, price,
|
|
1139
|
+
self._add_new_item(bar_start_time, Bar(bar_start_time, price, price, price, price,
|
|
1140
|
+
volume=volume, bought_volume=bvolume,
|
|
1141
|
+
volume_quote=volume_quote, bought_volume_quote=bought_volume_quote,
|
|
1142
|
+
trade_count=trade_count))
|
|
1123
1143
|
|
|
1124
1144
|
# Here we disable first notification because first item may be incomplete
|
|
1125
1145
|
self._is_new_item = False
|
|
1126
1146
|
|
|
1127
1147
|
elif (_dt := time - self.times[0]) >= self.timeframe:
|
|
1128
|
-
b = Bar(bar_start_time, price, price, price, price,
|
|
1148
|
+
b = Bar(bar_start_time, price, price, price, price,
|
|
1149
|
+
volume=volume, bought_volume=bvolume,
|
|
1150
|
+
volume_quote=volume_quote, bought_volume_quote=bought_volume_quote,
|
|
1151
|
+
trade_count=trade_count)
|
|
1129
1152
|
|
|
1130
1153
|
# - add new item
|
|
1131
1154
|
self._add_new_item(bar_start_time, b)
|
|
@@ -1138,7 +1161,9 @@ cdef class OHLCV(TimeSeries):
|
|
|
1138
1161
|
if _dt < 0:
|
|
1139
1162
|
raise ValueError(f"Attempt to update past data at {time_to_str(time)} !")
|
|
1140
1163
|
|
|
1141
|
-
self._update_last_item(bar_start_time, self[0].update(price, volume, bvolume
|
|
1164
|
+
self._update_last_item(bar_start_time, self[0].update(price, volume, bvolume,
|
|
1165
|
+
volume_quote, bought_volume_quote,
|
|
1166
|
+
trade_count))
|
|
1142
1167
|
|
|
1143
1168
|
# - update indicators by new data
|
|
1144
1169
|
self._update_indicators(bar_start_time, self[0], False)
|
|
Binary file
|
|
Binary file
|
|
@@ -20,7 +20,7 @@ qubx/cli/misc.py,sha256=tP28QxLEzuP8R2xnt8g3JTs9Z7aYy4iVWY4g3VzKTsQ,14777
|
|
|
20
20
|
qubx/cli/release.py,sha256=Kz5aykF9FlAeUgXF59_Mu3vRFxa_qF9dq0ySqLlKKqo,41362
|
|
21
21
|
qubx/cli/tui.py,sha256=N15UiNEdnWOWYh8E9DNlQCDWdoyP6rMGMhEItogPW88,16491
|
|
22
22
|
qubx/connectors/ccxt/__init__.py,sha256=HEQ7lM9HS8sED_zfsAHrhFT7F9E7NFGAecwZwNr-TDE,65
|
|
23
|
-
qubx/connectors/ccxt/account.py,sha256=
|
|
23
|
+
qubx/connectors/ccxt/account.py,sha256=VyPQGlELm1lFXD4sf4zD8xaC9ZQpneB8gAVm-di4hhE,25396
|
|
24
24
|
qubx/connectors/ccxt/adapters/__init__.py,sha256=4qwWer4C9fTIZKUYmcgbMFEwFuYp34UKbb-AoQNvXjc,178
|
|
25
25
|
qubx/connectors/ccxt/adapters/polling_adapter.py,sha256=UrOAoIfgtoRw7gj6Bmk5lPs_UI8iOxr88DAfp5o5CF8,8962
|
|
26
26
|
qubx/connectors/ccxt/broker.py,sha256=I6I_BynhwHadKa62nBGmDoj5LhFiEzbUq-1ZDwE25aM,16512
|
|
@@ -35,7 +35,7 @@ qubx/connectors/ccxt/exchanges/binance/exchange.py,sha256=UrhATX_NiclNTSG6ePdk8k
|
|
|
35
35
|
qubx/connectors/ccxt/exchanges/bitfinex/bitfinex.py,sha256=Tq-OR06JBU2yADUQc7m80oUyyUQ-cckzc4GyVI_1ey0,10745
|
|
36
36
|
qubx/connectors/ccxt/exchanges/bitfinex/bitfinex_account.py,sha256=zrnA6GJiNddoM5JF-SlFFO4FpITDO5rGaU9ipshUvAY,1603
|
|
37
37
|
qubx/connectors/ccxt/exchanges/hyperliquid/__init__.py,sha256=j6OB7j_yKRgitQAeJX3jkTrJ7NVnbTKtoumMiH_9u4I,166
|
|
38
|
-
qubx/connectors/ccxt/exchanges/hyperliquid/broker.py,sha256=
|
|
38
|
+
qubx/connectors/ccxt/exchanges/hyperliquid/broker.py,sha256=yoht8KsiMS3F6rkyfwQ3prBn-OgnY6tCDwYJQbsq1t0,3130
|
|
39
39
|
qubx/connectors/ccxt/exchanges/hyperliquid/hyperliquid.py,sha256=w2uidBYpIUWoZsAeXiITdc77EIGN4cE3bXsGAb4JpaE,20353
|
|
40
40
|
qubx/connectors/ccxt/exchanges/kraken/kraken.py,sha256=ntxf41aPY0JqxT5jn-979fgQih2TvMbr_p9fvCJB9QE,414
|
|
41
41
|
qubx/connectors/ccxt/factory.py,sha256=hX5WODwwo49J3YjGulpDyq1SKq9VvWH3otzFw6avS5I,6259
|
|
@@ -46,9 +46,9 @@ qubx/connectors/ccxt/handlers/funding_rate.py,sha256=OLlocxRBEMxQ7LAtxZgQcdn3LIZ
|
|
|
46
46
|
qubx/connectors/ccxt/handlers/liquidation.py,sha256=0QbLeZwpdEgd30PwQoWXUBKG25rrNQ0OgPz8fPLRA6U,3915
|
|
47
47
|
qubx/connectors/ccxt/handlers/ohlc.py,sha256=668mHEuLb-sT-Rk0miNsvKTETfoo0XK9aAAqNa4uTCY,17706
|
|
48
48
|
qubx/connectors/ccxt/handlers/open_interest.py,sha256=K3gPI51zUtxvED3V-sfFmK1Lsa-vx7k-Q-UE_eLsoRM,9957
|
|
49
|
-
qubx/connectors/ccxt/handlers/orderbook.py,sha256=
|
|
49
|
+
qubx/connectors/ccxt/handlers/orderbook.py,sha256=67cJt1aW778lMch8abkzvkhRKKnfHOqJ6IDufx-NoZk,9129
|
|
50
50
|
qubx/connectors/ccxt/handlers/quote.py,sha256=JwQ8mXMpFMdFEpQTx3x_Xaj6VHZanC6_JI6tB9l_yDw,4464
|
|
51
|
-
qubx/connectors/ccxt/handlers/trade.py,sha256=
|
|
51
|
+
qubx/connectors/ccxt/handlers/trade.py,sha256=doBxWHvRQIhRbr5PqzQnoVMBarjWlezk75DPlu__JVg,8841
|
|
52
52
|
qubx/connectors/ccxt/reader.py,sha256=uUG1I_ejzTf0f4bCAHpLhBzTUqtNX-JvJGFA4bi7-WU,26602
|
|
53
53
|
qubx/connectors/ccxt/subscription_config.py,sha256=jbMZ_9US3nvrp6LCVmMXLQnAjXH0xIltzUSPqXJZvgs,3865
|
|
54
54
|
qubx/connectors/ccxt/subscription_manager.py,sha256=9ZfA6bR6YwtlZqVs6yymKPvYSvy5x3yBuHA_LDbiKgc,13285
|
|
@@ -64,7 +64,7 @@ qubx/core/context.py,sha256=Yq039nsKmf-IeyvcRdnMs_phVmehoCUqFvQuVnm-d4s,26074
|
|
|
64
64
|
qubx/core/deque.py,sha256=3PsmJ5LF76JpsK4Wp5LLogyE15rKn6EDCkNOOWT6EOk,6203
|
|
65
65
|
qubx/core/errors.py,sha256=LENtlgmVzxxUFNCsuy4PwyHYhkZkxuZQ2BPif8jaGmw,1411
|
|
66
66
|
qubx/core/exceptions.py,sha256=11wQC3nnNLsl80zBqbE6xiKCqm31kctqo6W_gdnZkg8,581
|
|
67
|
-
qubx/core/helpers.py,sha256=
|
|
67
|
+
qubx/core/helpers.py,sha256=xFcgXtNGvaz8SvLeHToePMx0tx85eX3H4ymxjz66ekg,22129
|
|
68
68
|
qubx/core/initializer.py,sha256=VVti9UJDJCg0125Mm09S6Tt1foHK4XOBL0mXgDmvXes,7724
|
|
69
69
|
qubx/core/interfaces.py,sha256=AwSS92BbfUXw92TLvB75Wn5bxYByHAAXie2GAC70vUQ,67824
|
|
70
70
|
qubx/core/loggers.py,sha256=eYijsR02S5u1Hv21vjIk_dOUwOMv0fiBDYwEmFhAoWk,14344
|
|
@@ -73,16 +73,16 @@ qubx/core/metrics.py,sha256=o3Bd25G2GwqxVlIpryScCrQISCYjsVWiIWAUUsW2siQ,79274
|
|
|
73
73
|
qubx/core/mixins/__init__.py,sha256=AMCLvfNuIb1kkQl3bhCj9jIOEl2eKcVPJeyLgrkB-rk,329
|
|
74
74
|
qubx/core/mixins/market.py,sha256=w9OEDfy0r9xnb4KdKA-PuFsCQugNo4pMLQivGFeyqGw,6356
|
|
75
75
|
qubx/core/mixins/processing.py,sha256=UqKwOzkDXvmcjvffrsR8vugq_XacwW9rcGM0TcBd9RM,44485
|
|
76
|
-
qubx/core/mixins/subscription.py,sha256=
|
|
76
|
+
qubx/core/mixins/subscription.py,sha256=2nUikNNPsMExS9yO38kNt7jk1vKE-RPm0b3h1bU6gjE,11397
|
|
77
77
|
qubx/core/mixins/trading.py,sha256=7KwxHiPWkXGnkHS4VLaxOZ7BHULkvvqPS8ooAG1NTxM,9477
|
|
78
78
|
qubx/core/mixins/universe.py,sha256=UBa3OIr2XvlK04O7YUG9c66CY8AZ5rQDSZov1rnUSjQ,10512
|
|
79
79
|
qubx/core/mixins/utils.py,sha256=P71cLuqKjId8989MwOL_BtvvCnnwOFMkZyB1SY-0Ork,147
|
|
80
|
-
qubx/core/series.cpython-312-x86_64-linux-gnu.so,sha256=
|
|
81
|
-
qubx/core/series.pxd,sha256=
|
|
82
|
-
qubx/core/series.pyi,sha256=
|
|
83
|
-
qubx/core/series.pyx,sha256=
|
|
80
|
+
qubx/core/series.cpython-312-x86_64-linux-gnu.so,sha256=OvwHlIEZOnDhfIH_uRteowsd3l6OMYM6cywPPS4vSkQ,1027944
|
|
81
|
+
qubx/core/series.pxd,sha256=4WpEexOBwZdKvqI81yR7wCnQh2rKgVZp9Y0ejr8B3E4,4841
|
|
82
|
+
qubx/core/series.pyi,sha256=30F48-oMp-XuySh5pyuoZcV20R4yODRpYBgLv7yxhjY,5534
|
|
83
|
+
qubx/core/series.pyx,sha256=9d3XjPAyI6qYLXuKXqZ3HrxBir1CRVste8GpGvgqYL4,52876
|
|
84
84
|
qubx/core/stale_data_detector.py,sha256=NHnnG9NkcivC93n8QMwJUzFVQv2ziUaN-fg76ppng_c,17118
|
|
85
|
-
qubx/core/utils.cpython-312-x86_64-linux-gnu.so,sha256=
|
|
85
|
+
qubx/core/utils.cpython-312-x86_64-linux-gnu.so,sha256=XhIYTnpumKGrmjGzWNiU9_IJMENmqJmJZhWVmt4YS58,86568
|
|
86
86
|
qubx/core/utils.pyi,sha256=a-wS13V2p_dM1CnGq40JVulmiAhixTwVwt0ah5By0Hc,348
|
|
87
87
|
qubx/core/utils.pyx,sha256=UR9achMR-LARsztd2eelFsDsFH3n0gXACIKoGNPI9X4,1766
|
|
88
88
|
qubx/data/__init__.py,sha256=BlyZ99esHLDmFA6ktNkuIce9RZO99TA1IMKWe94aI8M,599
|
|
@@ -156,7 +156,7 @@ qubx/restorers/signal.py,sha256=5nK5ji8AucyWrFBK9uW619YCI_vPRGFnuDu8JnG3B_Y,1451
|
|
|
156
156
|
qubx/restorers/state.py,sha256=I1VIN0ZcOjigc3WMHIYTNJeAAbN9YB21MDcMl04ZWmY,8018
|
|
157
157
|
qubx/restorers/utils.py,sha256=We2gfqwQKWziUYhuUnjb-xo-5tSlbuHWpPQn0CEMTn0,1155
|
|
158
158
|
qubx/ta/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
159
|
-
qubx/ta/indicators.cpython-312-x86_64-linux-gnu.so,sha256=
|
|
159
|
+
qubx/ta/indicators.cpython-312-x86_64-linux-gnu.so,sha256=4SOZrbMeBBi7ep88enwT7hZh-KwHlyL_T4muHfF41bw,804392
|
|
160
160
|
qubx/ta/indicators.pxd,sha256=r9mYcpDFxn3RW5lVJ497Fiq2-eqD4k7VX0-Q0xcftkk,4913
|
|
161
161
|
qubx/ta/indicators.pyi,sha256=T87VwJrBJK8EuqtoW1Dhjri05scH_Y6BXN9kex6G7mQ,2881
|
|
162
162
|
qubx/ta/indicators.pyx,sha256=jJMZ7D2kUspwiZITBnFuNPNvzqUm26EtEfSzBWSrLvQ,39286
|
|
@@ -211,8 +211,8 @@ qubx/utils/runner/factory.py,sha256=hmtUDYNFQwVQffHEfxgrlmKwOGLcFQ6uJIH_ZLscpIY,
|
|
|
211
211
|
qubx/utils/runner/runner.py,sha256=xc575tmIOWuI1UN1YTdBsW5iA3WdhaQzR0XawV1UcPo,34337
|
|
212
212
|
qubx/utils/time.py,sha256=xOWl_F6dOLFCmbB4xccLIx5yVt5HOH-I8ZcuowXjtBQ,11797
|
|
213
213
|
qubx/utils/version.py,sha256=e52fIHyxzCiIuH7svCF6pkHuDlqL64rklqz-2XjWons,5309
|
|
214
|
-
qubx-0.6.
|
|
215
|
-
qubx-0.6.
|
|
216
|
-
qubx-0.6.
|
|
217
|
-
qubx-0.6.
|
|
218
|
-
qubx-0.6.
|
|
214
|
+
qubx-0.6.85.dist-info/METADATA,sha256=ZOHrVXgrzG-6FeBQhqdnU41BZI1KUaCvdDAERuUw4EY,5909
|
|
215
|
+
qubx-0.6.85.dist-info/WHEEL,sha256=RA6gLSyyVpI0R7d3ofBrM1iY5kDUsPwh15AF0XpvgQo,110
|
|
216
|
+
qubx-0.6.85.dist-info/entry_points.txt,sha256=VqilDTe8mVuV9SbR-yVlZJBTjbkHIL2JBgXfQw076HY,47
|
|
217
|
+
qubx-0.6.85.dist-info/licenses/LICENSE,sha256=qwMHOSJ2TD0nx6VUJvFhu1ynJdBfNozRMt6tnSul-Ts,35140
|
|
218
|
+
qubx-0.6.85.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|