Qubx 0.6.62__tar.gz → 0.6.63__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 Qubx might be problematic. Click here for more details.
- {qubx-0.6.62 → qubx-0.6.63}/PKG-INFO +1 -1
- {qubx-0.6.62 → qubx-0.6.63}/pyproject.toml +1 -1
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/core/account.py +4 -2
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/core/interfaces.py +20 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/core/mixins/processing.py +13 -7
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/emitters/base.py +23 -2
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/emitters/composite.py +17 -2
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/emitters/csv.py +43 -1
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/emitters/prometheus.py +57 -2
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/emitters/questdb.py +131 -2
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/pandaz/ta.py +7 -17
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/trackers/riskctrl.py +3 -4
- {qubx-0.6.62 → qubx-0.6.63}/LICENSE +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/README.md +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/build.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/__init__.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/_nb_magic.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/backtester/__init__.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/backtester/account.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/backtester/broker.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/backtester/data.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/backtester/management.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/backtester/ome.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/backtester/optimization.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/backtester/runner.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/backtester/simulated_data.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/backtester/simulated_exchange.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/backtester/simulator.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/backtester/utils.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/cli/__init__.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/cli/commands.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/cli/deploy.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/cli/misc.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/cli/release.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/cli/tui.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/connectors/ccxt/__init__.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/connectors/ccxt/account.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/connectors/ccxt/broker.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/connectors/ccxt/data.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/connectors/ccxt/exceptions.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/connectors/ccxt/exchanges/__init__.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/connectors/ccxt/exchanges/binance/broker.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/connectors/ccxt/exchanges/binance/exchange.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex_account.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/connectors/ccxt/exchanges/kraken/kraken.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/connectors/ccxt/factory.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/connectors/ccxt/reader.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/connectors/ccxt/utils.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/connectors/tardis/data.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/connectors/tardis/utils.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/core/__init__.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/core/basics.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/core/context.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/core/deque.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/core/errors.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/core/exceptions.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/core/helpers.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/core/initializer.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/core/loggers.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/core/lookups.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/core/metrics.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/core/mixins/__init__.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/core/mixins/market.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/core/mixins/subscription.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/core/mixins/trading.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/core/mixins/universe.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/core/series.pxd +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/core/series.pyi +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/core/series.pyx +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/core/utils.pyi +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/core/utils.pyx +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/data/__init__.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/data/composite.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/data/helpers.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/data/hft.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/data/readers.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/data/registry.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/data/tardis.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/emitters/__init__.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/emitters/indicator.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/exporters/__init__.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/exporters/composite.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/exporters/formatters/__init__.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/exporters/formatters/base.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/exporters/formatters/incremental.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/exporters/formatters/slack.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/exporters/redis_streams.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/exporters/slack.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/features/__init__.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/features/core.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/features/orderbook.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/features/price.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/features/trades.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/features/utils.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/gathering/simplest.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/health/__init__.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/health/base.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/loggers/__init__.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/loggers/csv.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/loggers/factory.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/loggers/inmemory.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/loggers/mongo.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/math/__init__.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/math/stats.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/notifications/__init__.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/notifications/composite.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/notifications/slack.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/notifications/throttler.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/pandaz/__init__.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/pandaz/utils.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/resources/_build.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/resources/crypto-fees.ini +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/resources/instruments/symbols-binance-spot.json +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/resources/instruments/symbols-binance.cm-future.json +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/resources/instruments/symbols-binance.cm-perpetual.json +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/resources/instruments/symbols-binance.um-future.json +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/resources/instruments/symbols-binance.um-perpetual.json +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/resources/instruments/symbols-bitfinex.f-perpetual.json +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/resources/instruments/symbols-hyperliquid-spot.json +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/resources/instruments/symbols-hyperliquid.f-perpetual.json +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/resources/instruments/symbols-kraken-spot.json +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/resources/instruments/symbols-kraken.f-future.json +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/resources/instruments/symbols-kraken.f-perpetual.json +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/restarts/__init__.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/restarts/state_resolvers.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/restarts/time_finders.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/restorers/__init__.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/restorers/balance.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/restorers/factory.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/restorers/interfaces.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/restorers/position.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/restorers/signal.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/restorers/state.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/restorers/utils.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/ta/__init__.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/ta/indicators.pxd +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/ta/indicators.pyi +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/ta/indicators.pyx +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/trackers/__init__.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/trackers/advanced.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/trackers/composite.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/trackers/rebalancers.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/trackers/sizers.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/utils/__init__.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/utils/_pyxreloader.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/utils/charting/lookinglass.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/utils/charting/mpl_helpers.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/utils/collections.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/utils/marketdata/binance.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/utils/marketdata/ccxt.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/utils/marketdata/dukas.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/utils/misc.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/utils/ntp.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/utils/numbers_utils.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/utils/orderbook.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/utils/plotting/__init__.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/utils/plotting/dashboard.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/utils/plotting/data.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/utils/plotting/interfaces.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/utils/plotting/renderers/__init__.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/utils/plotting/renderers/plotly.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/utils/questdb.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/utils/runner/__init__.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/utils/runner/_jupyter_runner.pyt +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/utils/runner/accounts.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/utils/runner/configs.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/utils/runner/factory.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/utils/runner/runner.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/utils/time.py +0 -0
- {qubx-0.6.62 → qubx-0.6.63}/src/qubx/utils/version.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
|
|
|
4
4
|
|
|
5
5
|
[tool.poetry]
|
|
6
6
|
name = "Qubx"
|
|
7
|
-
version = "0.6.
|
|
7
|
+
version = "0.6.63"
|
|
8
8
|
description = "Qubx - Quantitative Trading Framework"
|
|
9
9
|
authors = [ "Dmitry Marienko <dmitry.marienko@xlydian.com>", "Yuriy Arabskyy <yuriy.arabskyy@xlydian.com>",]
|
|
10
10
|
readme = "README.md"
|
|
@@ -112,10 +112,12 @@ class BasicAccountProcessor(IAccountProcessor):
|
|
|
112
112
|
return {s: self.get_leverage(s) for s in self._positions.keys()}
|
|
113
113
|
|
|
114
114
|
def get_net_leverage(self, exchange: str | None = None) -> float:
|
|
115
|
-
|
|
115
|
+
leverages = self.get_leverages(exchange).values()
|
|
116
|
+
return sum(lev for lev in leverages if lev is not None and not np.isnan(lev))
|
|
116
117
|
|
|
117
118
|
def get_gross_leverage(self, exchange: str | None = None) -> float:
|
|
118
|
-
|
|
119
|
+
leverages = self.get_leverages(exchange).values()
|
|
120
|
+
return sum(abs(lev) for lev in leverages if lev is not None and not np.isnan(lev))
|
|
119
121
|
|
|
120
122
|
########################################################
|
|
121
123
|
# Margin information
|
|
@@ -1935,6 +1935,26 @@ class IMetricEmitter:
|
|
|
1935
1935
|
"""
|
|
1936
1936
|
pass
|
|
1937
1937
|
|
|
1938
|
+
def emit_signals(
|
|
1939
|
+
self,
|
|
1940
|
+
time: dt_64,
|
|
1941
|
+
signals: list[Signal],
|
|
1942
|
+
account: "IAccountViewer",
|
|
1943
|
+
target_positions: list["TargetPosition"] | None = None,
|
|
1944
|
+
) -> None:
|
|
1945
|
+
"""
|
|
1946
|
+
Emit signals to the monitoring system.
|
|
1947
|
+
|
|
1948
|
+
This method is called to emit trading signals for monitoring and analysis purposes.
|
|
1949
|
+
|
|
1950
|
+
Args:
|
|
1951
|
+
time: Timestamp when the signals were generated
|
|
1952
|
+
signals: List of signals to emit
|
|
1953
|
+
account: Account viewer to get account information like total capital, leverage, etc.
|
|
1954
|
+
target_positions: Optional list of target positions generated from the signals
|
|
1955
|
+
"""
|
|
1956
|
+
pass
|
|
1957
|
+
|
|
1938
1958
|
|
|
1939
1959
|
class IStrategyLifecycleNotifier:
|
|
1940
1960
|
"""Interface for notifying about strategy lifecycle events."""
|
|
@@ -334,13 +334,6 @@ class ProcessingManager(IProcessingManager):
|
|
|
334
334
|
self._instruments_in_init_stage.remove(instr)
|
|
335
335
|
logger.info(f"Switching tracker for <g>{instr}</g> back to defined position tracker")
|
|
336
336
|
|
|
337
|
-
# - log all signals
|
|
338
|
-
self._logging.save_signals(signals)
|
|
339
|
-
|
|
340
|
-
# - export signals if exporter is specified
|
|
341
|
-
if self._exporter is not None and signals:
|
|
342
|
-
self._exporter.export_signals(self._time_provider.time(), signals, self._account)
|
|
343
|
-
|
|
344
337
|
return _std_signals, _init_signals, _cancel_init_stage_instruments_tracker
|
|
345
338
|
|
|
346
339
|
def __process_signals(self, signals: list[Signal]):
|
|
@@ -372,6 +365,19 @@ class ProcessingManager(IProcessingManager):
|
|
|
372
365
|
self._context, self.__preprocess_and_log_target_positions(_targets_from_trackers)
|
|
373
366
|
)
|
|
374
367
|
|
|
368
|
+
# - log all signals and export signals if exporter is specified after processing because trackers can modify the signals
|
|
369
|
+
self._logging.save_signals(signals)
|
|
370
|
+
|
|
371
|
+
# - export signals if exporter is specified
|
|
372
|
+
if self._exporter is not None and signals:
|
|
373
|
+
self._exporter.export_signals(self._time_provider.time(), signals, self._account)
|
|
374
|
+
|
|
375
|
+
# - emit signals to metric emitters if available
|
|
376
|
+
if self._context.emitter is not None and signals:
|
|
377
|
+
self._context.emitter.emit_signals(
|
|
378
|
+
self._time_provider.time(), signals, self._account, _targets_from_trackers
|
|
379
|
+
)
|
|
380
|
+
|
|
375
381
|
def __invoke_on_fit(self) -> None:
|
|
376
382
|
with self._health_monitor("ctx.on_fit"):
|
|
377
383
|
try:
|
|
@@ -9,8 +9,8 @@ from typing import Dict, List, Optional, Set
|
|
|
9
9
|
import pandas as pd
|
|
10
10
|
|
|
11
11
|
from qubx import logger
|
|
12
|
-
from qubx.core.basics import Instrument, dt_64
|
|
13
|
-
from qubx.core.interfaces import IMetricEmitter, IStrategyContext, ITimeProvider
|
|
12
|
+
from qubx.core.basics import Instrument, Signal, TargetPosition, dt_64
|
|
13
|
+
from qubx.core.interfaces import IAccountViewer, IMetricEmitter, IStrategyContext, ITimeProvider
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
class BaseMetricEmitter(IMetricEmitter):
|
|
@@ -182,6 +182,27 @@ class BaseMetricEmitter(IMetricEmitter):
|
|
|
182
182
|
except Exception as e:
|
|
183
183
|
logger.error(f"[BaseMetricEmitter] Failed to emit strategy stats: {e}")
|
|
184
184
|
|
|
185
|
+
def emit_signals(
|
|
186
|
+
self,
|
|
187
|
+
time: dt_64,
|
|
188
|
+
signals: list["Signal"],
|
|
189
|
+
account: "IAccountViewer",
|
|
190
|
+
target_positions: list["TargetPosition"] | None = None,
|
|
191
|
+
) -> None:
|
|
192
|
+
"""
|
|
193
|
+
Emit signals to the monitoring system.
|
|
194
|
+
|
|
195
|
+
Base implementation does nothing - subclasses should override this method
|
|
196
|
+
to implement specific signal emission logic.
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
time: Timestamp when the signals were generated
|
|
200
|
+
signals: List of signals to emit
|
|
201
|
+
account: Account viewer to get account information like total capital, leverage, etc.
|
|
202
|
+
target_positions: Optional list of target positions generated from the signals
|
|
203
|
+
"""
|
|
204
|
+
pass
|
|
205
|
+
|
|
185
206
|
def notify(self, context: IStrategyContext) -> None:
|
|
186
207
|
"""
|
|
187
208
|
Notify the metric emitter of a time update.
|
|
@@ -7,8 +7,8 @@ This module provides a composite implementation of IMetricEmitter that delegates
|
|
|
7
7
|
from typing import Dict, List, Optional
|
|
8
8
|
|
|
9
9
|
from qubx import logger
|
|
10
|
-
from qubx.core.basics import dt_64
|
|
11
|
-
from qubx.core.interfaces import IMetricEmitter, IStrategyContext
|
|
10
|
+
from qubx.core.basics import Signal, dt_64
|
|
11
|
+
from qubx.core.interfaces import IAccountViewer, IMetricEmitter, IStrategyContext
|
|
12
12
|
from qubx.emitters.base import BaseMetricEmitter
|
|
13
13
|
|
|
14
14
|
|
|
@@ -70,6 +70,21 @@ class CompositeMetricEmitter(BaseMetricEmitter):
|
|
|
70
70
|
except Exception as e:
|
|
71
71
|
logger.error(f"Error emitting strategy stats to {emitter.__class__.__name__}: {e}")
|
|
72
72
|
|
|
73
|
+
def emit_signals(self, time: dt_64, signals: list["Signal"], account: "IAccountViewer") -> None:
|
|
74
|
+
"""
|
|
75
|
+
Emit signals to all configured emitters.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
time: Timestamp when the signals were generated
|
|
79
|
+
signals: List of signals to emit
|
|
80
|
+
account: Account viewer to get account information
|
|
81
|
+
"""
|
|
82
|
+
for emitter in self._emitters:
|
|
83
|
+
try:
|
|
84
|
+
emitter.emit_signals(time, signals, account)
|
|
85
|
+
except Exception as e:
|
|
86
|
+
logger.error(f"Error emitting signals to {emitter.__class__.__name__}: {e}")
|
|
87
|
+
|
|
73
88
|
def notify(self, context: IStrategyContext) -> None:
|
|
74
89
|
for emitter in self._emitters:
|
|
75
90
|
try:
|
|
@@ -8,7 +8,8 @@ import os
|
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
|
|
10
10
|
from qubx import logger
|
|
11
|
-
from qubx.core.basics import dt_64
|
|
11
|
+
from qubx.core.basics import Signal, dt_64
|
|
12
|
+
from qubx.core.interfaces import IAccountViewer
|
|
12
13
|
from qubx.emitters.base import BaseMetricEmitter
|
|
13
14
|
from qubx.utils.ntp import time_now
|
|
14
15
|
|
|
@@ -81,3 +82,44 @@ class CSVMetricEmitter(BaseMetricEmitter):
|
|
|
81
82
|
f.write(f"{str(current_timestamp)},{name},{value},{tags_str}\n")
|
|
82
83
|
except Exception as e:
|
|
83
84
|
logger.error(f"[CSVMetricEmitter] Failed to emit metric {name}: {e}")
|
|
85
|
+
|
|
86
|
+
def emit_signals(self, time: dt_64, signals: list[Signal], account: IAccountViewer) -> None:
|
|
87
|
+
"""
|
|
88
|
+
Emit signals to CSV file.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
time: Timestamp when the signals were generated
|
|
92
|
+
signals: List of signals to emit
|
|
93
|
+
account: Account viewer to get account information
|
|
94
|
+
"""
|
|
95
|
+
if not signals:
|
|
96
|
+
return
|
|
97
|
+
|
|
98
|
+
try:
|
|
99
|
+
# Create a signals-specific CSV file
|
|
100
|
+
signals_file_path = self._file_path.parent / f"signals_{self._file_path.stem}.csv"
|
|
101
|
+
|
|
102
|
+
# Check if file exists, if not create with headers
|
|
103
|
+
if not signals_file_path.exists():
|
|
104
|
+
with open(signals_file_path, "w") as f:
|
|
105
|
+
f.write(
|
|
106
|
+
"timestamp,symbol,exchange,signal,price,stop,take,reference_price,group,comment,is_service\n"
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# Write each signal to the CSV file
|
|
110
|
+
for signal in signals:
|
|
111
|
+
signal_time = str(signal.time) if hasattr(signal.time, "__str__") else str(time)
|
|
112
|
+
price = signal.price if signal.price is not None else ""
|
|
113
|
+
stop = signal.stop if signal.stop is not None else ""
|
|
114
|
+
take = signal.take if signal.take is not None else ""
|
|
115
|
+
ref_price = signal.reference_price if signal.reference_price is not None else ""
|
|
116
|
+
|
|
117
|
+
with open(signals_file_path, "a") as f:
|
|
118
|
+
f.write(
|
|
119
|
+
f"{signal_time},{signal.instrument.symbol},{signal.instrument.exchange},"
|
|
120
|
+
f"{signal.signal},{price},{stop},{take},{ref_price},"
|
|
121
|
+
f"{signal.group},{signal.comment},{signal.is_service}\n"
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
except Exception as e:
|
|
125
|
+
logger.error(f"[CSVMetricEmitter] Failed to emit signals: {e}")
|
|
@@ -9,8 +9,8 @@ from typing import Dict, List, Literal, Optional
|
|
|
9
9
|
from prometheus_client import REGISTRY, Counter, Gauge, Summary, push_to_gateway
|
|
10
10
|
|
|
11
11
|
from qubx import logger
|
|
12
|
-
from qubx.core.basics import dt_64
|
|
13
|
-
from qubx.core.interfaces import IStrategyContext
|
|
12
|
+
from qubx.core.basics import Signal, dt_64
|
|
13
|
+
from qubx.core.interfaces import IAccountViewer, IStrategyContext
|
|
14
14
|
from qubx.emitters.base import BaseMetricEmitter
|
|
15
15
|
|
|
16
16
|
# Define metric types
|
|
@@ -220,3 +220,58 @@ class PrometheusMetricEmitter(BaseMetricEmitter):
|
|
|
220
220
|
)
|
|
221
221
|
except Exception as e:
|
|
222
222
|
logger.error(f"[PrometheusMetricEmitter] Failed to push metrics to gateway: {e}")
|
|
223
|
+
|
|
224
|
+
def emit_signals(self, time: dt_64, signals: list[Signal], account: IAccountViewer) -> None:
|
|
225
|
+
"""
|
|
226
|
+
Emit signals as Prometheus metrics.
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
time: Timestamp when the signals were generated
|
|
230
|
+
signals: List of signals to emit
|
|
231
|
+
account: Account viewer to get account information
|
|
232
|
+
"""
|
|
233
|
+
if not signals:
|
|
234
|
+
return
|
|
235
|
+
|
|
236
|
+
try:
|
|
237
|
+
for signal in signals:
|
|
238
|
+
# Create labels for the signal
|
|
239
|
+
labels = {
|
|
240
|
+
"symbol": signal.instrument.symbol,
|
|
241
|
+
"exchange": signal.instrument.exchange,
|
|
242
|
+
"group": signal.group if signal.group else "default",
|
|
243
|
+
"is_service": str(signal.is_service).lower(),
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
# Emit the signal value as a gauge
|
|
247
|
+
gauge = self._get_or_create_gauge("signal_value", labels)
|
|
248
|
+
gauge.labels(**labels).set(signal.signal)
|
|
249
|
+
|
|
250
|
+
# Emit price-related metrics if available
|
|
251
|
+
if signal.price is not None:
|
|
252
|
+
price_gauge = self._get_or_create_gauge("signal_price", labels)
|
|
253
|
+
price_gauge.labels(**labels).set(signal.price)
|
|
254
|
+
|
|
255
|
+
if signal.stop is not None:
|
|
256
|
+
stop_gauge = self._get_or_create_gauge("signal_stop", labels)
|
|
257
|
+
stop_gauge.labels(**labels).set(signal.stop)
|
|
258
|
+
|
|
259
|
+
if signal.take is not None:
|
|
260
|
+
take_gauge = self._get_or_create_gauge("signal_take", labels)
|
|
261
|
+
take_gauge.labels(**labels).set(signal.take)
|
|
262
|
+
|
|
263
|
+
if signal.reference_price is not None:
|
|
264
|
+
ref_price_gauge = self._get_or_create_gauge("signal_reference_price", labels)
|
|
265
|
+
ref_price_gauge.labels(**labels).set(signal.reference_price)
|
|
266
|
+
|
|
267
|
+
# Push to gateway if configured
|
|
268
|
+
if self._pushgateway_url:
|
|
269
|
+
try:
|
|
270
|
+
push_to_gateway(
|
|
271
|
+
self._pushgateway_url, job=f"{self._namespace}_{self._strategy_name}", registry=self._registry
|
|
272
|
+
)
|
|
273
|
+
except Exception as e:
|
|
274
|
+
logger.error(f"[PrometheusMetricEmitter] Failed to push signal metrics to gateway: {e}")
|
|
275
|
+
|
|
276
|
+
except Exception as e:
|
|
277
|
+
logger.error(f"[PrometheusMetricEmitter] Failed to emit signals: {e}")
|
|
@@ -11,9 +11,10 @@ import pandas as pd
|
|
|
11
11
|
from questdb.ingress import Sender
|
|
12
12
|
|
|
13
13
|
from qubx import logger
|
|
14
|
-
from qubx.core.basics import dt_64
|
|
15
|
-
from qubx.core.interfaces import IStrategyContext
|
|
14
|
+
from qubx.core.basics import Signal, TargetPosition, dt_64
|
|
15
|
+
from qubx.core.interfaces import IAccountViewer, IStrategyContext
|
|
16
16
|
from qubx.emitters.base import BaseMetricEmitter
|
|
17
|
+
from qubx.utils.questdb import QuestDBClient
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
class QuestDBMetricEmitter(BaseMetricEmitter):
|
|
@@ -28,6 +29,7 @@ class QuestDBMetricEmitter(BaseMetricEmitter):
|
|
|
28
29
|
host: str = "localhost",
|
|
29
30
|
port: int = 9000,
|
|
30
31
|
table_name: str = "qubx_metrics",
|
|
32
|
+
signals_table_name: str = "qubx_signals",
|
|
31
33
|
stats_to_emit: list[str] | None = None,
|
|
32
34
|
stats_interval: str = "1m",
|
|
33
35
|
flush_interval: str = "5s",
|
|
@@ -41,6 +43,7 @@ class QuestDBMetricEmitter(BaseMetricEmitter):
|
|
|
41
43
|
host: QuestDB server host
|
|
42
44
|
port: QuestDB server port
|
|
43
45
|
table_name: Name of the table to store metrics in
|
|
46
|
+
signals_table_name: Name of the table to store signals in
|
|
44
47
|
stats_to_emit: Optional list of specific stats to emit
|
|
45
48
|
stats_interval: Interval for emitting strategy stats (default: "1m")
|
|
46
49
|
tags: Dictionary of default tags/labels to include with all metrics
|
|
@@ -54,12 +57,16 @@ class QuestDBMetricEmitter(BaseMetricEmitter):
|
|
|
54
57
|
self._host = host
|
|
55
58
|
self._port = port
|
|
56
59
|
self._table_name = table_name
|
|
60
|
+
self._signals_table_name = signals_table_name
|
|
57
61
|
self._conn_str = f"http::addr={host}:{port};"
|
|
58
62
|
self._flush_interval = pd.Timedelta(flush_interval)
|
|
59
63
|
self._sender = self._try_get_sender()
|
|
60
64
|
self._last_flush = None
|
|
61
65
|
self._executor = ThreadPoolExecutor(max_workers=max_workers, thread_name_prefix="questdb_emitter")
|
|
62
66
|
|
|
67
|
+
# Create signals table if it doesn't exist
|
|
68
|
+
self._ensure_signals_table_exists()
|
|
69
|
+
|
|
63
70
|
def notify(self, context: IStrategyContext) -> None:
|
|
64
71
|
super().notify(context)
|
|
65
72
|
|
|
@@ -158,3 +165,125 @@ class QuestDBMetricEmitter(BaseMetricEmitter):
|
|
|
158
165
|
logger.error(f"[QuestDBMetricEmitter] Failed to connect to QuestDB: {e}")
|
|
159
166
|
_sender = None
|
|
160
167
|
return _sender
|
|
168
|
+
|
|
169
|
+
def _ensure_signals_table_exists(self) -> None:
|
|
170
|
+
"""Ensure the signals table exists with the correct schema."""
|
|
171
|
+
try:
|
|
172
|
+
# Use the PostgreSQL interface (port 8812) for DDL operations
|
|
173
|
+
client = QuestDBClient(host=self._host, port=8812)
|
|
174
|
+
|
|
175
|
+
create_table_sql = f"""
|
|
176
|
+
CREATE TABLE IF NOT EXISTS {self._signals_table_name} (
|
|
177
|
+
timestamp TIMESTAMP,
|
|
178
|
+
symbol SYMBOL,
|
|
179
|
+
exchange SYMBOL,
|
|
180
|
+
signal DOUBLE,
|
|
181
|
+
price DOUBLE,
|
|
182
|
+
stop DOUBLE,
|
|
183
|
+
take DOUBLE,
|
|
184
|
+
reference_price DOUBLE,
|
|
185
|
+
target_leverage DOUBLE,
|
|
186
|
+
group_name SYMBOL,
|
|
187
|
+
comment STRING,
|
|
188
|
+
is_service BOOLEAN
|
|
189
|
+
) TIMESTAMP(timestamp) PARTITION BY DAY;
|
|
190
|
+
"""
|
|
191
|
+
|
|
192
|
+
client.execute(create_table_sql)
|
|
193
|
+
logger.info(f"[QuestDBMetricEmitter] Ensured signals table '{self._signals_table_name}' exists")
|
|
194
|
+
except Exception as e:
|
|
195
|
+
logger.error(f"[QuestDBMetricEmitter] Failed to create signals table: {e}")
|
|
196
|
+
|
|
197
|
+
def emit_signals(
|
|
198
|
+
self,
|
|
199
|
+
time: dt_64,
|
|
200
|
+
signals: list[Signal],
|
|
201
|
+
account: IAccountViewer,
|
|
202
|
+
target_positions: list[TargetPosition] | None = None,
|
|
203
|
+
) -> None:
|
|
204
|
+
"""
|
|
205
|
+
Emit signals to QuestDB.
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
time: Timestamp when the signals were generated
|
|
209
|
+
signals: List of signals to emit
|
|
210
|
+
account: Account viewer to get account information
|
|
211
|
+
target_positions: Optional list of target positions generated from the signals
|
|
212
|
+
"""
|
|
213
|
+
if not signals or self._sender is None:
|
|
214
|
+
return
|
|
215
|
+
|
|
216
|
+
try:
|
|
217
|
+
# Submit the signals emission to the thread pool
|
|
218
|
+
self._executor.submit(self._emit_signals_to_questdb, time, signals, account, target_positions)
|
|
219
|
+
except Exception as e:
|
|
220
|
+
logger.error(f"[QuestDBMetricEmitter] Failed to queue signals emission: {e}")
|
|
221
|
+
|
|
222
|
+
def _emit_signals_to_questdb(
|
|
223
|
+
self,
|
|
224
|
+
time: dt_64,
|
|
225
|
+
signals: list[Signal],
|
|
226
|
+
account: IAccountViewer,
|
|
227
|
+
target_positions: list[TargetPosition] | None = None,
|
|
228
|
+
) -> None:
|
|
229
|
+
"""
|
|
230
|
+
Send signals to QuestDB in a background thread.
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
time: Timestamp when the signals were generated
|
|
234
|
+
signals: List of signals to emit
|
|
235
|
+
account: Account viewer to get account information
|
|
236
|
+
target_positions: Optional list of target positions generated from the signals
|
|
237
|
+
"""
|
|
238
|
+
if self._sender is None:
|
|
239
|
+
return
|
|
240
|
+
|
|
241
|
+
try:
|
|
242
|
+
# Get total capital for leverage calculations
|
|
243
|
+
total_capital = account.get_total_capital()
|
|
244
|
+
|
|
245
|
+
# Create a mapping of instruments to target positions for easier lookup
|
|
246
|
+
target_positions_map = {}
|
|
247
|
+
|
|
248
|
+
if target_positions:
|
|
249
|
+
for target in target_positions:
|
|
250
|
+
target_positions_map[target.instrument] = target
|
|
251
|
+
|
|
252
|
+
for signal in signals:
|
|
253
|
+
# Get target leverage for this instrument if available
|
|
254
|
+
target_leverage = None
|
|
255
|
+
if signal.instrument in target_positions_map:
|
|
256
|
+
target = target_positions_map[signal.instrument]
|
|
257
|
+
# Use signal.reference_price for notional value calculation
|
|
258
|
+
if signal.reference_price is not None and total_capital > 0:
|
|
259
|
+
notional_value = abs(target.target_position_size * signal.reference_price)
|
|
260
|
+
target_leverage = (notional_value / total_capital) * 100
|
|
261
|
+
|
|
262
|
+
# Use _merge_tags to get properly merged tags
|
|
263
|
+
merged_tags = self._merge_tags({}, signal.instrument)
|
|
264
|
+
|
|
265
|
+
symbols = {
|
|
266
|
+
"group_name": signal.group if signal.group else "",
|
|
267
|
+
}
|
|
268
|
+
symbols.update(merged_tags) # Add merged tags
|
|
269
|
+
|
|
270
|
+
columns = {
|
|
271
|
+
"signal": float(signal.signal),
|
|
272
|
+
"price": float(signal.price) if signal.price is not None else None,
|
|
273
|
+
"stop": float(signal.stop) if signal.stop is not None else None,
|
|
274
|
+
"take": float(signal.take) if signal.take is not None else None,
|
|
275
|
+
"reference_price": float(signal.reference_price) if signal.reference_price is not None else None,
|
|
276
|
+
"target_leverage": float(target_leverage) if target_leverage is not None else None,
|
|
277
|
+
"comment": signal.comment if signal.comment else "",
|
|
278
|
+
# "options": json.dumps(signal.options) if signal.options else "{}",
|
|
279
|
+
"is_service": bool(signal.is_service),
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
# Convert timestamp - signal.time is always dt_64, no need to check for string
|
|
283
|
+
dt_timestamp = self._convert_timestamp(time)
|
|
284
|
+
|
|
285
|
+
# Send the row to QuestDB
|
|
286
|
+
self._sender.row(self._signals_table_name, symbols=symbols, columns=columns, at=dt_timestamp)
|
|
287
|
+
|
|
288
|
+
except Exception as e:
|
|
289
|
+
logger.error(f"[QuestDBMetricEmitter] Failed to emit signals to QuestDB: {e}")
|
|
@@ -1611,28 +1611,18 @@ def choppiness(
|
|
|
1611
1611
|
match identification:
|
|
1612
1612
|
case "mid":
|
|
1613
1613
|
f0[(ci > lower) & (ci < upper)] = 0
|
|
1614
|
-
f0[
|
|
1615
|
-
f0[
|
|
1614
|
+
f0[ci > upper] = 1
|
|
1615
|
+
f0[ci < lower] = -1
|
|
1616
1616
|
case "strong":
|
|
1617
|
-
f0[
|
|
1618
|
-
f0[
|
|
1617
|
+
f0[ci > lower] = 1
|
|
1618
|
+
f0[ci < lower] = 0
|
|
1619
1619
|
case "weak":
|
|
1620
|
-
f0[
|
|
1621
|
-
f0[
|
|
1620
|
+
f0[ci > upper] = 1
|
|
1621
|
+
f0[ci < lower] = 0
|
|
1622
1622
|
case _:
|
|
1623
1623
|
raise ValueError(f"Invalid identification: {identification}")
|
|
1624
1624
|
|
|
1625
|
-
|
|
1626
|
-
# f0[ci >= upper] = True
|
|
1627
|
-
# f0[ci <= lower] = False
|
|
1628
|
-
# return f0.ffill().fillna(False)
|
|
1629
|
-
|
|
1630
|
-
# f0 = pd.Series(np.nan, ci.index, dtype=int)
|
|
1631
|
-
# f0[(ci > upper) & (ci.shift(1) <= upper)] = +1
|
|
1632
|
-
# f0[(ci < lower) & (ci.shift(1) >= lower)] = 0
|
|
1633
|
-
# return f0.ffill().fillna(0)
|
|
1634
|
-
|
|
1635
|
-
return f0.ffill().fillna(0) if not with_raw_indicator else ci
|
|
1625
|
+
return f0 if not with_raw_indicator else ci
|
|
1636
1626
|
|
|
1637
1627
|
|
|
1638
1628
|
@njit
|
|
@@ -84,15 +84,14 @@ class RiskController(PositionsTracker):
|
|
|
84
84
|
)
|
|
85
85
|
continue
|
|
86
86
|
|
|
87
|
-
# - calculate risk,
|
|
88
|
-
|
|
89
|
-
signal_with_risk = self._risk_calculator.calculate_risks(ctx, quote, s_copy)
|
|
87
|
+
# - calculate risk, we allow modifications of the original signal
|
|
88
|
+
signal_with_risk = self._risk_calculator.calculate_risks(ctx, quote, s)
|
|
90
89
|
if signal_with_risk is None:
|
|
91
90
|
continue
|
|
92
91
|
|
|
93
92
|
# - final step - calculate actual target position and check if tracker can approve it
|
|
94
93
|
target = self.get_position_sizer().calculate_target_positions(ctx, [signal_with_risk])[0]
|
|
95
|
-
if self.handle_new_target(ctx,
|
|
94
|
+
if self.handle_new_target(ctx, s, target):
|
|
96
95
|
targets.append(target)
|
|
97
96
|
|
|
98
97
|
return targets
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
{qubx-0.6.62 → qubx-0.6.63}/src/qubx/resources/instruments/symbols-binance.cm-perpetual.json
RENAMED
|
File without changes
|
|
File without changes
|
{qubx-0.6.62 → qubx-0.6.63}/src/qubx/resources/instruments/symbols-binance.um-perpetual.json
RENAMED
|
File without changes
|
{qubx-0.6.62 → qubx-0.6.63}/src/qubx/resources/instruments/symbols-bitfinex.f-perpetual.json
RENAMED
|
File without changes
|
|
File without changes
|
{qubx-0.6.62 → qubx-0.6.63}/src/qubx/resources/instruments/symbols-hyperliquid.f-perpetual.json
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
|
|
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
|
|
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
|