Qubx 0.6.47__tar.gz → 0.6.48__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.47 → qubx-0.6.48}/PKG-INFO +3 -1
- {qubx-0.6.47 → qubx-0.6.48}/pyproject.toml +3 -1
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/__init__.py +7 -16
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/backtester/ome.py +72 -9
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/cli/commands.py +28 -2
- qubx-0.6.48/src/qubx/cli/tui.py +461 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/core/basics.py +2 -1
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/core/interfaces.py +8 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/data/readers.py +7 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/emitters/base.py +4 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/trackers/riskctrl.py +50 -1
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/utils/runner/configs.py +23 -21
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/utils/runner/runner.py +52 -25
- {qubx-0.6.47 → qubx-0.6.48}/LICENSE +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/README.md +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/build.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/_nb_magic.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/backtester/__init__.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/backtester/account.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/backtester/broker.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/backtester/data.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/backtester/management.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/backtester/optimization.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/backtester/runner.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/backtester/simulated_data.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/backtester/simulated_exchange.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/backtester/simulator.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/backtester/utils.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/cli/__init__.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/cli/deploy.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/cli/misc.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/cli/release.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/connectors/ccxt/__init__.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/connectors/ccxt/account.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/connectors/ccxt/broker.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/connectors/ccxt/data.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/connectors/ccxt/exceptions.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/connectors/ccxt/exchanges/__init__.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/connectors/ccxt/exchanges/binance/broker.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/connectors/ccxt/exchanges/binance/exchange.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex_account.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/connectors/ccxt/exchanges/kraken/kraken.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/connectors/ccxt/factory.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/connectors/ccxt/reader.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/connectors/ccxt/utils.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/connectors/tardis/data.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/connectors/tardis/utils.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/core/__init__.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/core/account.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/core/context.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/core/deque.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/core/errors.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/core/exceptions.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/core/helpers.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/core/initializer.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/core/loggers.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/core/lookups.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/core/metrics.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/core/mixins/__init__.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/core/mixins/market.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/core/mixins/processing.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/core/mixins/subscription.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/core/mixins/trading.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/core/mixins/universe.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/core/series.pxd +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/core/series.pyi +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/core/series.pyx +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/core/utils.pyi +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/core/utils.pyx +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/data/__init__.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/data/composite.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/data/helpers.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/data/hft.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/data/registry.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/data/tardis.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/emitters/__init__.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/emitters/composite.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/emitters/csv.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/emitters/prometheus.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/emitters/questdb.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/exporters/__init__.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/exporters/composite.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/exporters/formatters/__init__.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/exporters/formatters/base.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/exporters/formatters/incremental.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/exporters/formatters/slack.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/exporters/redis_streams.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/exporters/slack.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/features/__init__.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/features/core.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/features/orderbook.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/features/price.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/features/trades.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/features/utils.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/gathering/simplest.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/health/__init__.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/health/base.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/loggers/__init__.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/loggers/csv.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/loggers/factory.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/loggers/inmemory.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/loggers/mongo.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/math/__init__.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/math/stats.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/notifications/__init__.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/notifications/composite.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/notifications/slack.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/notifications/throttler.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/pandaz/__init__.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/pandaz/ta.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/pandaz/utils.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/resources/_build.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/resources/instruments/symbols-binance.cm.json +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/resources/instruments/symbols-binance.json +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/resources/instruments/symbols-binance.um.json +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/resources/instruments/symbols-bitfinex.f.json +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/resources/instruments/symbols-bitfinex.json +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/resources/instruments/symbols-kraken.f.json +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/resources/instruments/symbols-kraken.json +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/restarts/__init__.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/restarts/state_resolvers.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/restarts/time_finders.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/restorers/__init__.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/restorers/balance.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/restorers/factory.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/restorers/interfaces.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/restorers/position.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/restorers/signal.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/restorers/state.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/restorers/utils.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/ta/__init__.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/ta/indicators.pxd +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/ta/indicators.pyi +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/ta/indicators.pyx +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/trackers/__init__.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/trackers/advanced.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/trackers/composite.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/trackers/rebalancers.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/trackers/sizers.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/utils/__init__.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/utils/_pyxreloader.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/utils/charting/lookinglass.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/utils/charting/mpl_helpers.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/utils/collections.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/utils/marketdata/binance.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/utils/marketdata/ccxt.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/utils/marketdata/dukas.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/utils/misc.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/utils/ntp.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/utils/numbers_utils.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/utils/orderbook.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/utils/plotting/__init__.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/utils/plotting/dashboard.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/utils/plotting/data.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/utils/plotting/interfaces.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/utils/plotting/renderers/__init__.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/utils/plotting/renderers/plotly.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/utils/questdb.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/utils/runner/__init__.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/utils/runner/_jupyter_runner.pyt +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/utils/runner/accounts.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/utils/runner/factory.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/utils/time.py +0 -0
- {qubx-0.6.47 → qubx-0.6.48}/src/qubx/utils/version.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: Qubx
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.48
|
|
4
4
|
Summary: Qubx - Quantitative Trading Framework
|
|
5
5
|
Author: Dmitry Marienko
|
|
6
6
|
Author-email: dmitry.marienko@xlydian.com
|
|
@@ -42,12 +42,14 @@ Requires-Dist: pyyaml (>=6.0.2,<7.0.0)
|
|
|
42
42
|
Requires-Dist: qubx-bitfinex-api (>=3.0.7,<4.0.0)
|
|
43
43
|
Requires-Dist: questdb (>=2.0.3,<3.0.0)
|
|
44
44
|
Requires-Dist: redis (>=5.2.1,<6.0.0)
|
|
45
|
+
Requires-Dist: rich (>=13.9.4,<14.0.0)
|
|
45
46
|
Requires-Dist: scikit-learn (>=1.4.2,<2.0.0)
|
|
46
47
|
Requires-Dist: scipy (>=1.12.0,<2.0.0)
|
|
47
48
|
Requires-Dist: sortedcontainers (>=2.4.0,<3.0.0)
|
|
48
49
|
Requires-Dist: stackprinter (>=0.2.10,<0.3.0)
|
|
49
50
|
Requires-Dist: statsmodels (>=0.14.2,<0.15.0)
|
|
50
51
|
Requires-Dist: tabulate (>=0.9.0,<0.10.0)
|
|
52
|
+
Requires-Dist: textual (>=0.88.0,<0.89.0)
|
|
51
53
|
Requires-Dist: toml (>=0.10.2,<0.11.0)
|
|
52
54
|
Requires-Dist: tqdm
|
|
53
55
|
Requires-Dist: websockets (==15.0.1)
|
|
@@ -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.48"
|
|
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"
|
|
@@ -74,6 +74,8 @@ orjson = "^3.10.15"
|
|
|
74
74
|
aiohttp = "~3.10.11"
|
|
75
75
|
websockets = "15.0.1"
|
|
76
76
|
qubx-bitfinex-api = "^3.0.7"
|
|
77
|
+
textual = "^0.88.0"
|
|
78
|
+
rich = "^13.9.4"
|
|
77
79
|
|
|
78
80
|
[tool.ruff.lint]
|
|
79
81
|
extend-select = [ "I",]
|
|
@@ -65,7 +65,7 @@ class QubxLogConfig:
|
|
|
65
65
|
QubxLogConfig.setup_logger(level)
|
|
66
66
|
|
|
67
67
|
@staticmethod
|
|
68
|
-
def setup_logger(level: str | None = None, custom_formatter: Callable | None = None):
|
|
68
|
+
def setup_logger(level: str | None = None, custom_formatter: Callable | None = None, colorize: bool = True):
|
|
69
69
|
global logger
|
|
70
70
|
|
|
71
71
|
config = {
|
|
@@ -82,13 +82,13 @@ class QubxLogConfig:
|
|
|
82
82
|
logger.add(
|
|
83
83
|
sys.stdout,
|
|
84
84
|
format=custom_formatter or formatter,
|
|
85
|
-
colorize=
|
|
85
|
+
colorize=colorize,
|
|
86
86
|
level=level,
|
|
87
87
|
enqueue=True,
|
|
88
88
|
backtrace=True,
|
|
89
89
|
diagnose=True,
|
|
90
90
|
)
|
|
91
|
-
logger = logger.opt(colors=
|
|
91
|
+
logger = logger.opt(colors=colorize)
|
|
92
92
|
|
|
93
93
|
|
|
94
94
|
QubxLogConfig.setup_logger()
|
|
@@ -99,18 +99,7 @@ if runtime_env() in ["notebook", "shell"]:
|
|
|
99
99
|
from IPython.core.getipython import get_ipython
|
|
100
100
|
from IPython.core.magic import Magics, line_cell_magic, line_magic, magics_class
|
|
101
101
|
|
|
102
|
-
from qubx.utils.charting.
|
|
103
|
-
from qubx.utils.charting.mpl_helpers import ( # noqa: F401
|
|
104
|
-
ellips,
|
|
105
|
-
fig,
|
|
106
|
-
hline,
|
|
107
|
-
ohlc_plot,
|
|
108
|
-
plot_trends,
|
|
109
|
-
sbp,
|
|
110
|
-
set_mpl_theme,
|
|
111
|
-
vline,
|
|
112
|
-
)
|
|
113
|
-
from qubx.utils.misc import install_pyx_recompiler_for_dev
|
|
102
|
+
from qubx.utils.charting.mpl_helpers import set_mpl_theme
|
|
114
103
|
|
|
115
104
|
@magics_class
|
|
116
105
|
class QubxMagics(Magics):
|
|
@@ -140,6 +129,8 @@ if runtime_env() in ["notebook", "shell"]:
|
|
|
140
129
|
|
|
141
130
|
# setup cython dev hooks - only if 'dev' is passed as argument
|
|
142
131
|
if line and "dev" in args:
|
|
132
|
+
from qubx.utils.misc import install_pyx_recompiler_for_dev
|
|
133
|
+
|
|
143
134
|
install_pyx_recompiler_for_dev()
|
|
144
135
|
|
|
145
136
|
tpl_path = os.path.join(os.path.dirname(__file__), "_nb_magic.py")
|
|
@@ -159,7 +150,7 @@ if runtime_env() in ["notebook", "shell"]:
|
|
|
159
150
|
exec(_vscode_clr_trick, self.shell.user_ns)
|
|
160
151
|
|
|
161
152
|
elif "light" in line.lower():
|
|
162
|
-
|
|
153
|
+
set_mpl_theme("light")
|
|
163
154
|
|
|
164
155
|
def _get_manager(self):
|
|
165
156
|
if self.__manager is None:
|
|
@@ -9,12 +9,14 @@ from qubx.core.basics import (
|
|
|
9
9
|
OPTION_FILL_AT_SIGNAL_PRICE,
|
|
10
10
|
OPTION_SIGNAL_PRICE,
|
|
11
11
|
OPTION_SKIP_PRICE_CROSS_CONTROL,
|
|
12
|
+
OPTION_AVOID_STOP_ORDER_PRICE_VALIDATION,
|
|
12
13
|
Deal,
|
|
13
14
|
Instrument,
|
|
14
15
|
ITimeProvider,
|
|
15
16
|
Order,
|
|
16
17
|
OrderSide,
|
|
17
18
|
OrderType,
|
|
19
|
+
OrderStatus,
|
|
18
20
|
TransactionCostsCalculator,
|
|
19
21
|
dt_64,
|
|
20
22
|
)
|
|
@@ -37,14 +39,20 @@ class SimulatedExecutionReport:
|
|
|
37
39
|
class OrdersManagementEngine:
|
|
38
40
|
"""
|
|
39
41
|
Orders Management Engine (OME) is a simple implementation of a management of orders for simulation of a limit order book.
|
|
42
|
+
|
|
43
|
+
2025-06-02: Added support for deferred execution reports (mainly for stop orders). This handles following cases:
|
|
44
|
+
- It's possible to send stop loss order (STOP_MARKET) in on_execution_report() from custom PositionsTracker class
|
|
45
|
+
- This order may be executed immediately that can lead to calling of on_execution_report() again (when we are still in on_execution_report())
|
|
46
|
+
- To avoid this, it emulate stop orders execution (when condition is met) and add deferred execution report to the list
|
|
47
|
+
- Deferred executions then would be sent on next process_market_data() call
|
|
40
48
|
"""
|
|
41
49
|
|
|
42
50
|
instrument: Instrument
|
|
43
51
|
time_service: ITimeProvider
|
|
44
52
|
active_orders: dict[str, Order]
|
|
45
53
|
stop_orders: dict[str, Order]
|
|
46
|
-
asks: SortedDict[float, list[str]]
|
|
47
|
-
bids: SortedDict[float, list[str]]
|
|
54
|
+
asks: SortedDict # [float, list[str]]
|
|
55
|
+
bids: SortedDict # [float, list[str]]
|
|
48
56
|
bbo: Quote | None # - current best bid/ask order book
|
|
49
57
|
__prev_bbo: Quote | None # - previous best bid/ask order book
|
|
50
58
|
__order_id: int
|
|
@@ -53,6 +61,7 @@ class OrdersManagementEngine:
|
|
|
53
61
|
_tick_size: float
|
|
54
62
|
_last_update_time: dt_64
|
|
55
63
|
_last_data_update_time_ns: int
|
|
64
|
+
_deferred_exec_reports: list[SimulatedExecutionReport]
|
|
56
65
|
|
|
57
66
|
def __init__(
|
|
58
67
|
self,
|
|
@@ -76,6 +85,7 @@ class OrdersManagementEngine:
|
|
|
76
85
|
self._tick_size = instrument.tick_size
|
|
77
86
|
self._last_update_time = np.datetime64(0, "ns")
|
|
78
87
|
self._last_data_update_time_ns = 0
|
|
88
|
+
self._deferred_exec_reports = []
|
|
79
89
|
|
|
80
90
|
if not debug:
|
|
81
91
|
self._dbg = lambda message, **kwargs: None
|
|
@@ -94,6 +104,11 @@ class OrdersManagementEngine:
|
|
|
94
104
|
def get_open_orders(self) -> list[Order]:
|
|
95
105
|
return list(self.active_orders.values()) + list(self.stop_orders.values())
|
|
96
106
|
|
|
107
|
+
def __remove_pending_status(self, exec: SimulatedExecutionReport) -> SimulatedExecutionReport:
|
|
108
|
+
if exec.order.status == "PENDING":
|
|
109
|
+
exec.order.status = "CLOSED"
|
|
110
|
+
return exec
|
|
111
|
+
|
|
97
112
|
def process_market_data(self, mdata: Quote | OrderBook | Trade | TradeArray) -> list[SimulatedExecutionReport]:
|
|
98
113
|
"""
|
|
99
114
|
Processes the new market data (quote, trade or trades array) and simulates the execution of pending orders.
|
|
@@ -101,6 +116,11 @@ class OrdersManagementEngine:
|
|
|
101
116
|
timestamp = self.time_service.time()
|
|
102
117
|
_exec_report = []
|
|
103
118
|
|
|
119
|
+
# - process deferred exec reports: spit out deferred exec reports in first place
|
|
120
|
+
if self._deferred_exec_reports:
|
|
121
|
+
_exec_report = [self.__remove_pending_status(i) for i in self._deferred_exec_reports]
|
|
122
|
+
self._deferred_exec_reports.clear()
|
|
123
|
+
|
|
104
124
|
# - pass through data if it's older than previous update
|
|
105
125
|
if mdata.time < self._last_data_update_time_ns:
|
|
106
126
|
return _exec_report
|
|
@@ -191,7 +211,7 @@ class OrdersManagementEngine:
|
|
|
191
211
|
raise ExchangeError(f"Simulator is not ready for order management - no quote for {self.instrument.symbol}")
|
|
192
212
|
|
|
193
213
|
# - validate order parameters
|
|
194
|
-
self._validate_order(order_side, order_type, amount, price, time_in_force)
|
|
214
|
+
self._validate_order(order_side, order_type, amount, price, time_in_force, options)
|
|
195
215
|
|
|
196
216
|
timestamp = self.time_service.time()
|
|
197
217
|
order = Order(
|
|
@@ -262,7 +282,39 @@ class OrdersManagementEngine:
|
|
|
262
282
|
case "STOP_MARKET":
|
|
263
283
|
# - it processes stop orders separately without adding to orderbook (as on real exchanges)
|
|
264
284
|
order.status = "OPEN"
|
|
265
|
-
|
|
285
|
+
_stp_order = order
|
|
286
|
+
_emulate_price_exec = self._fill_stops_at_price or _stp_order.options.get(
|
|
287
|
+
OPTION_FILL_AT_SIGNAL_PRICE, False
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
if _stp_order.side == "BUY" and _c_ask >= _stp_order.price:
|
|
291
|
+
# _exec_price = _c_ask if not _emulate_price_exec else so.price
|
|
292
|
+
self._deferred_exec_reports.append(
|
|
293
|
+
self._execute_order(
|
|
294
|
+
timestamp,
|
|
295
|
+
_c_ask if not _emulate_price_exec else _stp_order.price,
|
|
296
|
+
order,
|
|
297
|
+
True,
|
|
298
|
+
"BBO: " + str(self.bbo),
|
|
299
|
+
"PENDING",
|
|
300
|
+
)
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
elif _stp_order.side == "SELL" and _c_bid <= _stp_order.price:
|
|
304
|
+
# _exec_price = _c_bid if not _emulate_price_exec else so.price
|
|
305
|
+
self._deferred_exec_reports.append(
|
|
306
|
+
self._execute_order(
|
|
307
|
+
timestamp,
|
|
308
|
+
_c_bid if not _emulate_price_exec else _stp_order.price,
|
|
309
|
+
order,
|
|
310
|
+
True,
|
|
311
|
+
"BBO: " + str(self.bbo),
|
|
312
|
+
"PENDING",
|
|
313
|
+
)
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
else:
|
|
317
|
+
self.stop_orders[order.id] = order
|
|
266
318
|
|
|
267
319
|
case "STOP_LIMIT":
|
|
268
320
|
# TODO: (OME) check trigger conditions in options etc
|
|
@@ -289,11 +341,17 @@ class OrdersManagementEngine:
|
|
|
289
341
|
return SimulatedExecutionReport(self.instrument, timestamp, order, None)
|
|
290
342
|
|
|
291
343
|
def _execute_order(
|
|
292
|
-
self,
|
|
344
|
+
self,
|
|
345
|
+
timestamp: dt_64,
|
|
346
|
+
exec_price: float,
|
|
347
|
+
order: Order,
|
|
348
|
+
taker: bool,
|
|
349
|
+
market_state: str,
|
|
350
|
+
status: OrderStatus = "CLOSED",
|
|
293
351
|
) -> SimulatedExecutionReport:
|
|
294
|
-
order.status =
|
|
352
|
+
order.status = status
|
|
295
353
|
self._dbg(
|
|
296
|
-
f"<red>{order.id}</red> {order.type} {order.side} {order.quantity} executed at {exec_price} ::: {market_state}"
|
|
354
|
+
f"<red>{order.id}</red> {order.type} {order.side} {order.quantity} executed at {exec_price} ::: {market_state} [{status}]"
|
|
297
355
|
)
|
|
298
356
|
return SimulatedExecutionReport(
|
|
299
357
|
self.instrument,
|
|
@@ -314,7 +372,7 @@ class OrdersManagementEngine:
|
|
|
314
372
|
)
|
|
315
373
|
|
|
316
374
|
def _validate_order(
|
|
317
|
-
self, order_side: str, order_type: str, amount: float, price: float | None, time_in_force: str
|
|
375
|
+
self, order_side: str, order_type: str, amount: float, price: float | None, time_in_force: str, options: dict
|
|
318
376
|
) -> None:
|
|
319
377
|
if order_side.upper() not in ["BUY", "SELL"]:
|
|
320
378
|
raise InvalidOrder("Invalid order side. Only BUY or SELL is allowed.")
|
|
@@ -333,7 +391,12 @@ class OrdersManagementEngine:
|
|
|
333
391
|
raise InvalidOrder("Invalid time in force. Only GTC, IOC, GTX are supported for now.")
|
|
334
392
|
|
|
335
393
|
if _ot.startswith("STOP"):
|
|
336
|
-
|
|
394
|
+
# - if the option is set, we don't check the current market price against the stop price
|
|
395
|
+
if options.get(OPTION_AVOID_STOP_ORDER_PRICE_VALIDATION, False):
|
|
396
|
+
return
|
|
397
|
+
|
|
398
|
+
assert self.bbo
|
|
399
|
+
assert price
|
|
337
400
|
c_ask, c_bid = self.bbo.ask, self.bbo.bid
|
|
338
401
|
if (order_side == "BUY" and c_ask >= price) or (order_side == "SELL" and c_bid <= price):
|
|
339
402
|
raise ExchangeError(
|
|
@@ -68,7 +68,8 @@ def main(debug: bool, debug_port: int, log_level: str):
|
|
|
68
68
|
@click.option(
|
|
69
69
|
"--restore", "-r", is_flag=True, default=False, help="Restore strategy state from previous run.", show_default=True
|
|
70
70
|
)
|
|
71
|
-
|
|
71
|
+
@click.option("--no-color", is_flag=True, default=False, help="Disable colored logging output.", show_default=True)
|
|
72
|
+
def run(config_file: Path, account_file: Path | None, paper: bool, jupyter: bool, restore: bool, no_color: bool):
|
|
72
73
|
"""
|
|
73
74
|
Starts the strategy with the given configuration file. If paper mode is enabled, account is not required.
|
|
74
75
|
|
|
@@ -87,7 +88,7 @@ def run(config_file: Path, account_file: Path | None, paper: bool, jupyter: bool
|
|
|
87
88
|
run_strategy_yaml_in_jupyter(config_file, account_file, paper, restore)
|
|
88
89
|
else:
|
|
89
90
|
logo()
|
|
90
|
-
run_strategy_yaml(config_file, account_file, paper=paper, restore=restore, blocking=True)
|
|
91
|
+
run_strategy_yaml(config_file, account_file, paper=paper, restore=restore, blocking=True, no_color=no_color)
|
|
91
92
|
|
|
92
93
|
|
|
93
94
|
@main.command()
|
|
@@ -249,5 +250,30 @@ def deploy(zip_file: str, output_dir: str | None, force: bool):
|
|
|
249
250
|
deploy_strategy(zip_file, output_dir, force)
|
|
250
251
|
|
|
251
252
|
|
|
253
|
+
@main.command()
|
|
254
|
+
@click.argument(
|
|
255
|
+
"results-path",
|
|
256
|
+
type=click.Path(exists=True, resolve_path=True),
|
|
257
|
+
default="results",
|
|
258
|
+
callback=lambda ctx, param, value: os.path.abspath(os.path.expanduser(value)),
|
|
259
|
+
)
|
|
260
|
+
def browse(results_path: str):
|
|
261
|
+
"""
|
|
262
|
+
Browse backtest results using an interactive TUI.
|
|
263
|
+
|
|
264
|
+
Opens a text-based user interface for exploring backtest results stored in ZIP files.
|
|
265
|
+
The browser provides:
|
|
266
|
+
- Tree view of results organized by strategy
|
|
267
|
+
- Table view with sortable metrics
|
|
268
|
+
- Equity chart view for comparing performance
|
|
269
|
+
|
|
270
|
+
Results are loaded from the specified directory containing .zip files
|
|
271
|
+
created by qubx simulate or result.to_file() methods.
|
|
272
|
+
"""
|
|
273
|
+
from .tui import run_backtest_browser
|
|
274
|
+
|
|
275
|
+
run_backtest_browser(results_path)
|
|
276
|
+
|
|
277
|
+
|
|
252
278
|
if __name__ == "__main__":
|
|
253
279
|
main()
|