Qubx 0.5.8__tar.gz → 0.6.1__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.5.8 → qubx-0.6.1}/PKG-INFO +7 -2
- {qubx-0.5.8 → qubx-0.6.1}/pyproject.toml +77 -69
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/__init__.py +50 -10
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/_nb_magic.py +1 -1
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/backtester/simulator.py +2 -1
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/backtester/utils.py +2 -1
- qubx-0.6.1/src/qubx/cli/commands.py +282 -0
- qubx-0.6.1/src/qubx/cli/deploy.py +232 -0
- qubx-0.6.1/src/qubx/cli/misc.py +381 -0
- qubx-0.6.1/src/qubx/cli/release.py +689 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/core/context.py +5 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/core/interfaces.py +46 -6
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/core/loggers.py +4 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/core/lookups.py +4 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/core/mixins/market.py +1 -1
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/core/mixins/processing.py +25 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/core/series.pyi +21 -4
- qubx-0.6.1/src/qubx/exporters/__init__.py +10 -0
- qubx-0.6.1/src/qubx/exporters/formatters/__init__.py +11 -0
- qubx-0.6.1/src/qubx/exporters/formatters/base.py +162 -0
- qubx-0.6.1/src/qubx/exporters/formatters/incremental.py +16 -0
- qubx-0.6.1/src/qubx/exporters/formatters/slack.py +183 -0
- qubx-0.6.1/src/qubx/exporters/redis_streams.py +177 -0
- qubx-0.6.1/src/qubx/exporters/slack.py +174 -0
- qubx-0.6.1/src/qubx/utils/__init__.py +16 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/utils/_pyxreloader.py +111 -58
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/utils/misc.py +0 -24
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/utils/orderbook.py +2 -1
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/utils/plotting/dashboard.py +2 -1
- qubx-0.6.1/src/qubx/utils/runner/__init__.py +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/utils/runner/accounts.py +3 -3
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/utils/runner/runner.py +2 -1
- qubx-0.6.1/src/qubx/utils/version.py +182 -0
- qubx-0.5.8/src/qubx/cli/commands.py +0 -67
- qubx-0.5.8/src/qubx/utils/__init__.py +0 -5
- qubx-0.5.8/src/qubx/utils/runner/__init__.py +0 -1
- {qubx-0.5.8 → qubx-0.6.1}/README.md +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/build.py +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/backtester/__init__.py +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/backtester/account.py +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/backtester/broker.py +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/backtester/data.py +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/backtester/management.py +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/backtester/ome.py +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/backtester/optimization.py +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/backtester/simulated_data.py +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/cli/__init__.py +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/connectors/ccxt/__init__.py +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/connectors/ccxt/account.py +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/connectors/ccxt/broker.py +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/connectors/ccxt/customizations.py +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/connectors/ccxt/data.py +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/connectors/ccxt/exceptions.py +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/connectors/ccxt/factory.py +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/connectors/ccxt/utils.py +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/core/__init__.py +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/core/account.py +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/core/basics.py +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/core/exceptions.py +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/core/helpers.py +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/core/metrics.py +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/core/mixins/__init__.py +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/core/mixins/subscription.py +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/core/mixins/trading.py +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/core/mixins/universe.py +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/core/series.pxd +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/core/series.pyx +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/core/utils.pyi +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/core/utils.pyx +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/data/__init__.py +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/data/helpers.py +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/data/readers.py +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/data/tardis.py +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/gathering/simplest.py +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/math/__init__.py +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/math/stats.py +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/pandaz/__init__.py +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/pandaz/ta.py +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/pandaz/utils.py +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/resources/instruments/symbols-binance.cm.json +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/resources/instruments/symbols-binance.json +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/resources/instruments/symbols-binance.um.json +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/resources/instruments/symbols-bitfinex.f.json +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/resources/instruments/symbols-bitfinex.json +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/resources/instruments/symbols-kraken.f.json +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/resources/instruments/symbols-kraken.json +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/ta/__init__.py +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/ta/indicators.pxd +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/ta/indicators.pyi +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/ta/indicators.pyx +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/trackers/__init__.py +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/trackers/abvanced.py +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/trackers/composite.py +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/trackers/rebalancers.py +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/trackers/riskctrl.py +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/trackers/sizers.py +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/utils/charting/lookinglass.py +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/utils/charting/mpl_helpers.py +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/utils/marketdata/binance.py +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/utils/marketdata/ccxt.py +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/utils/marketdata/dukas.py +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/utils/ntp.py +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/utils/numbers_utils.py +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/utils/plotting/__init__.py +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/utils/plotting/data.py +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/utils/plotting/interfaces.py +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/utils/plotting/renderers/__init__.py +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/utils/plotting/renderers/plotly.py +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/utils/runner/_jupyter_runner.pyt +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/utils/runner/configs.py +0 -0
- {qubx-0.5.8 → qubx-0.6.1}/src/qubx/utils/time.py +0 -0
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
2
|
Name: Qubx
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.1
|
|
4
4
|
Summary: Qubx - Quantitative Trading Framework
|
|
5
|
+
Home-page: https://github.com/xLydianSoftware/Qubx
|
|
5
6
|
Author: Dmitry Marienko
|
|
6
7
|
Author-email: dmitry.marienko@xlydian.com
|
|
7
8
|
Requires-Python: >=3.10,<4.0
|
|
@@ -15,7 +16,10 @@ Requires-Dist: croniter (>=2.0.5,<3.0.0)
|
|
|
15
16
|
Requires-Dist: cython (==3.0.8)
|
|
16
17
|
Requires-Dist: dash (>=2.18.2,<3.0.0)
|
|
17
18
|
Requires-Dist: dash-bootstrap-components (>=1.6.0,<2.0.0)
|
|
19
|
+
Requires-Dist: gitpython (>=3.1.44,<4.0.0)
|
|
18
20
|
Requires-Dist: importlib-metadata
|
|
21
|
+
Requires-Dist: ipywidgets (>=8.1.5,<9.0.0)
|
|
22
|
+
Requires-Dist: jupyter (>=1.1.1,<2.0.0)
|
|
19
23
|
Requires-Dist: jupyter-console (>=6.6.3,<7.0.0)
|
|
20
24
|
Requires-Dist: loguru (>=0.7.2,<0.8.0)
|
|
21
25
|
Requires-Dist: matplotlib (>=3.8.4,<4.0.0)
|
|
@@ -34,6 +38,7 @@ Requires-Dist: pymongo (>=4.6.1,<5.0.0)
|
|
|
34
38
|
Requires-Dist: python-binance (>=1.0.19,<2.0.0)
|
|
35
39
|
Requires-Dist: python-dotenv (>=1.0.0,<2.0.0)
|
|
36
40
|
Requires-Dist: pyyaml (>=6.0.2,<7.0.0)
|
|
41
|
+
Requires-Dist: redis (>=5.2.1,<6.0.0)
|
|
37
42
|
Requires-Dist: scikit-learn (>=1.4.2,<2.0.0)
|
|
38
43
|
Requires-Dist: scipy (>=1.12.0,<2.0.0)
|
|
39
44
|
Requires-Dist: sortedcontainers (>=2.4.0,<3.0.0)
|
|
@@ -1,109 +1,117 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = [ "poetry-core", "setuptools", "numpy>=1.26.3", "cython==3.0.8", "toml>=0.10.2",]
|
|
3
|
+
build-backend = "poetry.core.masonry.api"
|
|
4
|
+
|
|
1
5
|
[tool.poetry]
|
|
2
6
|
name = "Qubx"
|
|
3
|
-
version = "0.
|
|
7
|
+
version = "0.6.1"
|
|
4
8
|
description = "Qubx - Quantitative Trading Framework"
|
|
5
|
-
authors = [
|
|
6
|
-
"Dmitry Marienko <dmitry.marienko@xlydian.com>",
|
|
7
|
-
"Yuriy Arabskyy <yuriy.arabskyy@xlydian.com>",
|
|
8
|
-
]
|
|
9
|
+
authors = [ "Dmitry Marienko <dmitry.marienko@xlydian.com>", "Yuriy Arabskyy <yuriy.arabskyy@xlydian.com>",]
|
|
9
10
|
readme = "README.md"
|
|
10
|
-
packages = [{ include = "qubx", from = "src" }]
|
|
11
11
|
repository = "https://github.com/xLydianSoftware/Qubx"
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
]
|
|
12
|
+
[[tool.poetry.packages]]
|
|
13
|
+
include = "qubx"
|
|
14
|
+
from = "src"
|
|
15
|
+
|
|
16
|
+
[[tool.poetry.include]]
|
|
17
|
+
path = "src/**/*.so"
|
|
18
|
+
format = "wheel"
|
|
19
|
+
|
|
20
|
+
[[tool.poetry.include]]
|
|
21
|
+
path = "src/**/*.pyd"
|
|
22
|
+
format = "wheel"
|
|
23
|
+
|
|
24
|
+
[tool.ruff]
|
|
25
|
+
line-length = 120
|
|
26
|
+
|
|
27
|
+
[tool.poetry.build]
|
|
28
|
+
script = "build.py"
|
|
29
|
+
generate-setup-file = false
|
|
30
|
+
|
|
31
|
+
[tool.poetry.scripts]
|
|
32
|
+
qubx = "qubx.cli.commands:main"
|
|
17
33
|
|
|
18
34
|
[tool.poetry.dependencies]
|
|
19
35
|
python = ">=3.10,<4.0"
|
|
20
36
|
numpy = "^1.26.3"
|
|
37
|
+
pandas = "^2.2.2"
|
|
38
|
+
pyarrow = "^15.0.0"
|
|
39
|
+
scipy = "^1.12.0"
|
|
40
|
+
scikit-learn = "^1.4.2"
|
|
41
|
+
statsmodels = "^0.14.2"
|
|
42
|
+
numba = "^0.59.1"
|
|
43
|
+
sortedcontainers = "^2.4.0"
|
|
21
44
|
ntplib = "^0.4.0"
|
|
45
|
+
python-binance = "^1.0.19"
|
|
46
|
+
ccxt = "^4.2.68"
|
|
47
|
+
pymongo = "^4.6.1"
|
|
48
|
+
redis = "^5.2.1"
|
|
49
|
+
psycopg = "^3.1.18"
|
|
50
|
+
psycopg-binary = "^3.1.19"
|
|
51
|
+
psycopg-pool = "^3.2.2"
|
|
52
|
+
matplotlib = "^3.8.4"
|
|
53
|
+
plotly = "^5.22.0"
|
|
54
|
+
dash = "^2.18.2"
|
|
55
|
+
dash-bootstrap-components = "^1.6.0"
|
|
22
56
|
loguru = "^0.7.2"
|
|
23
57
|
tqdm = "*"
|
|
24
58
|
importlib-metadata = "*"
|
|
25
59
|
stackprinter = "^0.2.10"
|
|
26
|
-
pymongo = "^4.6.1"
|
|
27
60
|
pydantic = "^2.9.2"
|
|
28
61
|
python-dotenv = "^1.0.0"
|
|
29
|
-
python-binance = "^1.0.19"
|
|
30
|
-
pyarrow = "^15.0.0"
|
|
31
|
-
scipy = "^1.12.0"
|
|
32
62
|
cython = "3.0.8"
|
|
33
|
-
ccxt = "^4.2.68"
|
|
34
63
|
croniter = "^2.0.5"
|
|
35
|
-
psycopg = "^3.1.18"
|
|
36
|
-
pandas = "^2.2.2"
|
|
37
|
-
statsmodels = "^0.14.2"
|
|
38
|
-
matplotlib = "^3.8.4"
|
|
39
|
-
numba = "^0.59.1"
|
|
40
|
-
scikit-learn = "^1.4.2"
|
|
41
|
-
plotly = "^5.22.0"
|
|
42
|
-
psycopg-binary = "^3.1.19"
|
|
43
|
-
psycopg-pool = "^3.2.2"
|
|
44
|
-
sortedcontainers = "^2.4.0"
|
|
45
64
|
msgspec = "^0.18.6"
|
|
46
65
|
pyyaml = "^6.0.2"
|
|
47
|
-
dash = "^2.18.2"
|
|
48
|
-
dash-bootstrap-components = "^1.6.0"
|
|
49
66
|
tabulate = "^0.9.0"
|
|
50
|
-
jupyter-console = "^6.6.3"
|
|
51
67
|
toml = "^0.10.2"
|
|
68
|
+
gitpython = "^3.1.44"
|
|
69
|
+
jupyter = "^1.1.1"
|
|
70
|
+
jupyter-console = "^6.6.3"
|
|
71
|
+
ipywidgets = "^8.1.5"
|
|
72
|
+
|
|
73
|
+
[tool.ruff.lint]
|
|
74
|
+
extend-select = [ "I",]
|
|
75
|
+
ignore = [ "E731", "E722", "E741",]
|
|
76
|
+
|
|
77
|
+
[tool.pytest.ini_options]
|
|
78
|
+
asyncio_mode = "auto"
|
|
79
|
+
asyncio_default_fixture_loop_scope = "function"
|
|
80
|
+
pythonpath = [ "src",]
|
|
81
|
+
markers = [ "integration: mark test as requiring external services like Redis", "e2e: mark test as requiring external exchange connections and API credentials",]
|
|
82
|
+
addopts = "--disable-warnings"
|
|
83
|
+
filterwarnings = [ "ignore:.*Jupyter is migrating.*:DeprecationWarning",]
|
|
84
|
+
|
|
85
|
+
[tool.ruff.lint.extend-per-file-ignores]
|
|
86
|
+
"*.ipynb" = [ "F405", "F401", "E701", "E402", "F403", "E401", "E702", "I001",]
|
|
52
87
|
|
|
53
88
|
[tool.poetry.group.dev.dependencies]
|
|
54
89
|
pre-commit = "^2.20.0"
|
|
55
90
|
rust-just = "^1.36.0"
|
|
56
91
|
twine = "^5.1.1"
|
|
57
|
-
|
|
58
|
-
#[project.optional-dependencies]
|
|
59
|
-
#numba = "^0.57.1"
|
|
60
92
|
ipykernel = "^6.29.4"
|
|
61
93
|
iprogress = "^0.4"
|
|
62
94
|
click = "^8.1.7"
|
|
63
95
|
ipywidgets = "^8.1.5"
|
|
64
96
|
ruff = "^0.9.7"
|
|
65
97
|
pytest-xdist = "^3.6.1"
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
[tool.poetry.build]
|
|
78
|
-
script = "build.py"
|
|
79
|
-
generate-setup-file = false
|
|
98
|
+
nbformat = "^5.10.4"
|
|
99
|
+
markdown = "3.7"
|
|
100
|
+
mkdocs = "1.6.1"
|
|
101
|
+
mkdocs-material = "9.6.5"
|
|
102
|
+
mdx-truly-sane-lists = "1.3"
|
|
103
|
+
pymdown-extensions = "10.14.3"
|
|
104
|
+
jinja2 = "3.1.5"
|
|
105
|
+
mike = "2.1.3"
|
|
106
|
+
mkdocs-jupyter = "0.25.1"
|
|
107
|
+
debugpy = "^1.8.12"
|
|
80
108
|
|
|
81
109
|
[tool.poetry.group.test.dependencies]
|
|
82
|
-
pytest = { extras = ["lazyfixture"], version = "^8.2.0" }
|
|
83
110
|
pytest-asyncio = "^0.24.0"
|
|
84
111
|
pytest-mock = "^3.12.0"
|
|
85
112
|
pytest-lazy-fixture = "^0.6.3"
|
|
86
113
|
pytest-cov = "^4.1.0"
|
|
87
114
|
|
|
88
|
-
[tool.pytest
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
pythonpath = ["src"]
|
|
92
|
-
markers = ["integration: mark a test as an integration test"]
|
|
93
|
-
addopts = "--disable-warnings"
|
|
94
|
-
filterwarnings = ["ignore:.*Jupyter is migrating.*:DeprecationWarning"]
|
|
95
|
-
|
|
96
|
-
[tool.ruff]
|
|
97
|
-
line-length = 120
|
|
98
|
-
lint.extend-select = ["I"]
|
|
99
|
-
lint.ignore = [
|
|
100
|
-
"E731",
|
|
101
|
-
"E722",
|
|
102
|
-
"E741",
|
|
103
|
-
] # Ignore lambda assignments, bare except, and ambiguous variable names
|
|
104
|
-
|
|
105
|
-
[tool.ruff.lint.extend-per-file-ignores]
|
|
106
|
-
"*.ipynb" = ["F405", "F401", "E701", "E402", "F403", "E401", "E702"]
|
|
107
|
-
|
|
108
|
-
[tool.poetry.scripts]
|
|
109
|
-
qubx = "qubx.cli.commands:main"
|
|
115
|
+
[tool.poetry.group.test.dependencies.pytest]
|
|
116
|
+
extras = [ "lazyfixture",]
|
|
117
|
+
version = "^8.2.0"
|
|
@@ -5,13 +5,33 @@ from typing import Callable
|
|
|
5
5
|
import stackprinter
|
|
6
6
|
from loguru import logger
|
|
7
7
|
|
|
8
|
-
from qubx.core.lookups import FeesLookup, GlobalLookup, InstrumentsLookup
|
|
9
|
-
from qubx.utils import runtime_env, set_mpl_theme
|
|
10
|
-
from qubx.utils.misc import install_pyx_recompiler_for_dev
|
|
11
|
-
|
|
12
8
|
# - TODO: import some main methods from packages
|
|
13
9
|
|
|
14
10
|
|
|
11
|
+
def runtime_env():
|
|
12
|
+
"""
|
|
13
|
+
Check what environment this script is being run under
|
|
14
|
+
:return: environment name, possible values:
|
|
15
|
+
- 'notebook' jupyter notebook
|
|
16
|
+
- 'shell' any interactive shell (ipython, PyCharm's console etc)
|
|
17
|
+
- 'python' standard python interpreter
|
|
18
|
+
- 'unknown' can't recognize environment
|
|
19
|
+
"""
|
|
20
|
+
try:
|
|
21
|
+
from IPython.core.getipython import get_ipython
|
|
22
|
+
|
|
23
|
+
shell = get_ipython().__class__.__name__
|
|
24
|
+
|
|
25
|
+
if shell == "ZMQInteractiveShell": # Jupyter notebook or qtconsole
|
|
26
|
+
return "notebook"
|
|
27
|
+
elif shell.endswith("TerminalInteractiveShell"): # Terminal running IPython
|
|
28
|
+
return "shell"
|
|
29
|
+
else:
|
|
30
|
+
return "unknown" # Other type (?)
|
|
31
|
+
except (NameError, ImportError):
|
|
32
|
+
return "python" # Probably standard Python interpreter
|
|
33
|
+
|
|
34
|
+
|
|
15
35
|
def formatter(record):
|
|
16
36
|
end = record["extra"].get("end", "\n")
|
|
17
37
|
fmt = "<lvl>{message}</lvl>%s" % end
|
|
@@ -47,6 +67,7 @@ class QubxLogConfig:
|
|
|
47
67
|
@staticmethod
|
|
48
68
|
def setup_logger(level: str | None = None, custom_formatter: Callable | None = None):
|
|
49
69
|
global logger
|
|
70
|
+
|
|
50
71
|
config = {
|
|
51
72
|
"handlers": [
|
|
52
73
|
{"sink": sys.stdout, "format": "{time} - {message}"},
|
|
@@ -55,23 +76,42 @@ class QubxLogConfig:
|
|
|
55
76
|
}
|
|
56
77
|
logger.configure(**config)
|
|
57
78
|
logger.remove(None)
|
|
79
|
+
|
|
58
80
|
level = level or QubxLogConfig.get_log_level()
|
|
59
|
-
|
|
81
|
+
# Add stdout handler with enqueue=True for thread/process safety
|
|
82
|
+
logger.add(
|
|
83
|
+
sys.stdout,
|
|
84
|
+
format=custom_formatter or formatter,
|
|
85
|
+
colorize=True,
|
|
86
|
+
level=level,
|
|
87
|
+
enqueue=True,
|
|
88
|
+
backtrace=True,
|
|
89
|
+
diagnose=True,
|
|
90
|
+
)
|
|
60
91
|
logger = logger.opt(colors=True)
|
|
61
92
|
|
|
62
93
|
|
|
63
94
|
QubxLogConfig.setup_logger()
|
|
64
95
|
|
|
65
96
|
|
|
66
|
-
# - global lookup helper
|
|
67
|
-
lookup = GlobalLookup(InstrumentsLookup(), FeesLookup())
|
|
68
|
-
|
|
69
|
-
|
|
70
97
|
# registering magic for jupyter notebook
|
|
71
98
|
if runtime_env() in ["notebook", "shell"]:
|
|
72
99
|
from IPython.core.getipython import get_ipython
|
|
73
100
|
from IPython.core.magic import Magics, line_cell_magic, line_magic, magics_class
|
|
74
101
|
|
|
102
|
+
from qubx.utils.charting.lookinglass import LookingGlass # noqa: F401
|
|
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
|
|
114
|
+
|
|
75
115
|
@magics_class
|
|
76
116
|
class QubxMagics(Magics):
|
|
77
117
|
# process data manager
|
|
@@ -119,7 +159,7 @@ if runtime_env() in ["notebook", "shell"]:
|
|
|
119
159
|
exec(_vscode_clr_trick, self.shell.user_ns)
|
|
120
160
|
|
|
121
161
|
elif "light" in line.lower():
|
|
122
|
-
|
|
162
|
+
sort: skip_mpl_theme("light")
|
|
123
163
|
|
|
124
164
|
def _get_manager(self):
|
|
125
165
|
if self.__manager is None:
|
|
@@ -4,13 +4,14 @@ import numpy as np
|
|
|
4
4
|
import pandas as pd
|
|
5
5
|
from joblib import delayed
|
|
6
6
|
|
|
7
|
-
from qubx import QubxLogConfig, logger
|
|
7
|
+
from qubx import QubxLogConfig, logger
|
|
8
8
|
from qubx.core.basics import SW, DataType
|
|
9
9
|
from qubx.core.context import StrategyContext
|
|
10
10
|
from qubx.core.exceptions import SimulationConfigError, SimulationError
|
|
11
11
|
from qubx.core.helpers import extract_parameters_from_object, full_qualified_class_name
|
|
12
12
|
from qubx.core.interfaces import IStrategy
|
|
13
13
|
from qubx.core.loggers import InMemoryLogsWriter, StrategyLogging
|
|
14
|
+
from qubx.core.lookups import lookup
|
|
14
15
|
from qubx.core.metrics import TradingSessionResult
|
|
15
16
|
from qubx.data.readers import DataReader
|
|
16
17
|
from qubx.pandaz.utils import _frame_to_str
|
|
@@ -6,7 +6,7 @@ import numpy as np
|
|
|
6
6
|
import pandas as pd
|
|
7
7
|
import stackprinter
|
|
8
8
|
|
|
9
|
-
from qubx import logger
|
|
9
|
+
from qubx import logger
|
|
10
10
|
from qubx.core.basics import (
|
|
11
11
|
CtrlChannel,
|
|
12
12
|
DataType,
|
|
@@ -20,6 +20,7 @@ from qubx.core.basics import (
|
|
|
20
20
|
from qubx.core.exceptions import SimulationConfigError, SimulationError
|
|
21
21
|
from qubx.core.helpers import BasicScheduler
|
|
22
22
|
from qubx.core.interfaces import IStrategy, IStrategyContext, PositionsTracker
|
|
23
|
+
from qubx.core.lookups import lookup
|
|
23
24
|
from qubx.core.series import OHLCV, Bar, Quote, Trade
|
|
24
25
|
from qubx.core.utils import time_delta_to_str
|
|
25
26
|
from qubx.data.helpers import InMemoryCachedReader, TimeGuardedWrapper
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
from qubx import QubxLogConfig, logger
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@click.group()
|
|
11
|
+
@click.option(
|
|
12
|
+
"--debug",
|
|
13
|
+
"-d",
|
|
14
|
+
is_flag=True,
|
|
15
|
+
help="Enable debug mode.",
|
|
16
|
+
)
|
|
17
|
+
@click.option(
|
|
18
|
+
"--debug-port",
|
|
19
|
+
"-p",
|
|
20
|
+
type=int,
|
|
21
|
+
help="Debug port.",
|
|
22
|
+
default=5678,
|
|
23
|
+
)
|
|
24
|
+
@click.option(
|
|
25
|
+
"--log-level",
|
|
26
|
+
"-l",
|
|
27
|
+
type=str,
|
|
28
|
+
help="Log level.",
|
|
29
|
+
default="INFO",
|
|
30
|
+
)
|
|
31
|
+
def main(debug: bool, debug_port: int, log_level: str):
|
|
32
|
+
"""
|
|
33
|
+
Qubx CLI.
|
|
34
|
+
"""
|
|
35
|
+
log_level = log_level.upper() if not debug else "DEBUG"
|
|
36
|
+
|
|
37
|
+
QubxLogConfig.set_log_level(log_level)
|
|
38
|
+
|
|
39
|
+
if debug:
|
|
40
|
+
os.environ["PYDEVD_DISABLE_FILE_VALIDATION"] = "1"
|
|
41
|
+
|
|
42
|
+
import debugpy
|
|
43
|
+
|
|
44
|
+
logger.info(f"Waiting for debugger to attach (port {debug_port})")
|
|
45
|
+
|
|
46
|
+
debugpy.listen(debug_port)
|
|
47
|
+
debugpy.wait_for_client()
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@main.command()
|
|
51
|
+
@click.argument("config-file", type=Path, required=True)
|
|
52
|
+
@click.option(
|
|
53
|
+
"--account-file",
|
|
54
|
+
"-a",
|
|
55
|
+
type=Path,
|
|
56
|
+
help="Account configuration file path.",
|
|
57
|
+
required=False,
|
|
58
|
+
)
|
|
59
|
+
@click.option("--paper", "-p", is_flag=True, default=False, help="Use paper trading mode.", show_default=True)
|
|
60
|
+
@click.option(
|
|
61
|
+
"--jupyter", "-j", is_flag=True, default=False, help="Run strategy in jupyter console.", show_default=True
|
|
62
|
+
)
|
|
63
|
+
def run(config_file: Path, account_file: Path | None, paper: bool, jupyter: bool):
|
|
64
|
+
"""
|
|
65
|
+
Starts the strategy with the given configuration file. If paper mode is enabled, account is not required.
|
|
66
|
+
|
|
67
|
+
Account configurations are searched in the following priority:\n
|
|
68
|
+
- If provided, the account file is searched first.\n
|
|
69
|
+
- If exists, accounts.toml located in the same folder with the config searched.\n
|
|
70
|
+
- If neither of the above are provided, the accounts.toml in the ~/qubx/accounts.toml path is searched.
|
|
71
|
+
"""
|
|
72
|
+
from qubx.utils.misc import add_project_to_system_path, logo
|
|
73
|
+
from qubx.utils.runner.runner import run_strategy_yaml, run_strategy_yaml_in_jupyter
|
|
74
|
+
|
|
75
|
+
add_project_to_system_path()
|
|
76
|
+
add_project_to_system_path(str(config_file.parent))
|
|
77
|
+
if jupyter:
|
|
78
|
+
run_strategy_yaml_in_jupyter(config_file, account_file, paper)
|
|
79
|
+
else:
|
|
80
|
+
logo()
|
|
81
|
+
run_strategy_yaml(config_file, account_file, paper, blocking=True)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@main.command()
|
|
85
|
+
@click.argument("config-file", type=Path, required=True)
|
|
86
|
+
@click.option(
|
|
87
|
+
"--start", "-s", default=None, type=str, help="Override simulation start date from config.", show_default=True
|
|
88
|
+
)
|
|
89
|
+
@click.option(
|
|
90
|
+
"--end", "-e", default=None, type=str, help="Override simulation end date from config.", show_default=True
|
|
91
|
+
)
|
|
92
|
+
@click.option(
|
|
93
|
+
"--output", "-o", default="results", type=str, help="Output directory for simulation results.", show_default=True
|
|
94
|
+
)
|
|
95
|
+
def simulate(config_file: Path, start: str | None, end: str | None, output: str | None):
|
|
96
|
+
"""
|
|
97
|
+
Simulates the strategy with the given configuration file.
|
|
98
|
+
"""
|
|
99
|
+
from qubx.utils.misc import add_project_to_system_path, logo
|
|
100
|
+
from qubx.utils.runner.runner import simulate_strategy
|
|
101
|
+
|
|
102
|
+
add_project_to_system_path()
|
|
103
|
+
add_project_to_system_path(str(config_file.parent))
|
|
104
|
+
logo()
|
|
105
|
+
simulate_strategy(config_file, output, start, end)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@main.command()
|
|
109
|
+
@click.argument(
|
|
110
|
+
"directory",
|
|
111
|
+
type=click.Path(exists=True, resolve_path=True),
|
|
112
|
+
default=".",
|
|
113
|
+
callback=lambda ctx, param, value: os.path.abspath(os.path.expanduser(value)),
|
|
114
|
+
)
|
|
115
|
+
def ls(directory: str):
|
|
116
|
+
"""
|
|
117
|
+
Lists all strategies in the given directory.
|
|
118
|
+
|
|
119
|
+
Strategies are identified by the inheritance from IStrategy interface.
|
|
120
|
+
"""
|
|
121
|
+
from .release import ls_strats
|
|
122
|
+
|
|
123
|
+
ls_strats(directory)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@main.command()
|
|
127
|
+
@click.argument(
|
|
128
|
+
"directory",
|
|
129
|
+
type=click.Path(exists=False),
|
|
130
|
+
default=".",
|
|
131
|
+
callback=lambda ctx, param, value: os.path.abspath(os.path.expanduser(value)),
|
|
132
|
+
)
|
|
133
|
+
@click.option(
|
|
134
|
+
"--strategy",
|
|
135
|
+
"-s",
|
|
136
|
+
type=click.STRING,
|
|
137
|
+
help="Strategy name to release (should match the strategy class name) or path to a config YAML file",
|
|
138
|
+
required=True,
|
|
139
|
+
)
|
|
140
|
+
@click.option(
|
|
141
|
+
"--output-dir",
|
|
142
|
+
"-o",
|
|
143
|
+
type=click.STRING,
|
|
144
|
+
help="Output directory to put zip file.",
|
|
145
|
+
default="releases",
|
|
146
|
+
show_default=True,
|
|
147
|
+
)
|
|
148
|
+
@click.option(
|
|
149
|
+
"--tag",
|
|
150
|
+
"-t",
|
|
151
|
+
type=click.STRING,
|
|
152
|
+
help="Additional tag for this release (e.g. 'v1.0.0')",
|
|
153
|
+
required=False,
|
|
154
|
+
)
|
|
155
|
+
@click.option(
|
|
156
|
+
"--message",
|
|
157
|
+
"-m",
|
|
158
|
+
type=click.STRING,
|
|
159
|
+
help="Release message (added to the info yaml file).",
|
|
160
|
+
required=False,
|
|
161
|
+
default=None,
|
|
162
|
+
show_default=True,
|
|
163
|
+
)
|
|
164
|
+
@click.option(
|
|
165
|
+
"--commit",
|
|
166
|
+
"-c",
|
|
167
|
+
is_flag=True,
|
|
168
|
+
default=False,
|
|
169
|
+
help="Commit changes and create tag in repo (default: False)",
|
|
170
|
+
show_default=True,
|
|
171
|
+
)
|
|
172
|
+
@click.option(
|
|
173
|
+
"--default-exchange",
|
|
174
|
+
type=click.STRING,
|
|
175
|
+
help="Default exchange to use in the generated config.",
|
|
176
|
+
default="BINANCE.UM",
|
|
177
|
+
show_default=True,
|
|
178
|
+
)
|
|
179
|
+
@click.option(
|
|
180
|
+
"--default-connector",
|
|
181
|
+
type=click.STRING,
|
|
182
|
+
help="Default connector to use in the generated config.",
|
|
183
|
+
default="ccxt",
|
|
184
|
+
show_default=True,
|
|
185
|
+
)
|
|
186
|
+
@click.option(
|
|
187
|
+
"--default-instruments",
|
|
188
|
+
type=click.STRING,
|
|
189
|
+
help="Default instruments to use in the generated config (comma-separated).",
|
|
190
|
+
default="BTCUSDT",
|
|
191
|
+
show_default=True,
|
|
192
|
+
)
|
|
193
|
+
def release(
|
|
194
|
+
directory: str,
|
|
195
|
+
strategy: str,
|
|
196
|
+
tag: str | None,
|
|
197
|
+
message: str | None,
|
|
198
|
+
commit: bool,
|
|
199
|
+
output_dir: str,
|
|
200
|
+
default_exchange: str,
|
|
201
|
+
default_connector: str,
|
|
202
|
+
default_instruments: str,
|
|
203
|
+
) -> None:
|
|
204
|
+
"""
|
|
205
|
+
Releases the strategy to a zip file.
|
|
206
|
+
|
|
207
|
+
The strategy can be specified in two ways:
|
|
208
|
+
1. As a strategy name (class name) - strategies are scanned in the given directory
|
|
209
|
+
2. As a path to a config YAML file containing the strategy configuration in StrategyConfig format
|
|
210
|
+
|
|
211
|
+
If a strategy name is provided, a default configuration will be generated with:
|
|
212
|
+
- The strategy parameters from the strategy class
|
|
213
|
+
- Default exchange, connector, and instruments from the command options
|
|
214
|
+
- Standard logging configuration
|
|
215
|
+
|
|
216
|
+
If a config file is provided, it must follow the StrategyConfig structure with:
|
|
217
|
+
- strategy: The strategy name or path
|
|
218
|
+
- parameters: Dictionary of strategy parameters
|
|
219
|
+
- exchanges: Dictionary of exchange configurations
|
|
220
|
+
- aux: Auxiliary configuration
|
|
221
|
+
- logging: Logging configuration
|
|
222
|
+
|
|
223
|
+
All of the dependencies are included in the zip file.
|
|
224
|
+
"""
|
|
225
|
+
from .release import release_strategy
|
|
226
|
+
|
|
227
|
+
# Parse default instruments
|
|
228
|
+
instruments = [instr.strip() for instr in default_instruments.split(",")]
|
|
229
|
+
|
|
230
|
+
release_strategy(
|
|
231
|
+
directory=directory,
|
|
232
|
+
strategy_name=strategy,
|
|
233
|
+
tag=tag,
|
|
234
|
+
message=message,
|
|
235
|
+
commit=commit,
|
|
236
|
+
output_dir=output_dir,
|
|
237
|
+
default_exchange=default_exchange,
|
|
238
|
+
default_connector=default_connector,
|
|
239
|
+
default_instruments=instruments,
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
@main.command()
|
|
244
|
+
@click.argument(
|
|
245
|
+
"zip-file",
|
|
246
|
+
type=click.Path(exists=True, resolve_path=True),
|
|
247
|
+
callback=lambda ctx, param, value: os.path.abspath(os.path.expanduser(value)),
|
|
248
|
+
)
|
|
249
|
+
@click.option(
|
|
250
|
+
"--output-dir",
|
|
251
|
+
"-o",
|
|
252
|
+
type=click.Path(exists=False),
|
|
253
|
+
help="Output directory to unpack the zip file. Defaults to the directory containing the zip file.",
|
|
254
|
+
default=None,
|
|
255
|
+
)
|
|
256
|
+
@click.option(
|
|
257
|
+
"--force",
|
|
258
|
+
"-f",
|
|
259
|
+
is_flag=True,
|
|
260
|
+
default=False,
|
|
261
|
+
help="Force overwrite if the output directory already exists.",
|
|
262
|
+
show_default=True,
|
|
263
|
+
)
|
|
264
|
+
def deploy(zip_file: str, output_dir: str | None, force: bool):
|
|
265
|
+
"""
|
|
266
|
+
Deploys a strategy from a zip file created by the release command.
|
|
267
|
+
|
|
268
|
+
This command:
|
|
269
|
+
1. Unpacks the zip file to the specified output directory
|
|
270
|
+
2. Creates a Poetry virtual environment in the .venv folder
|
|
271
|
+
3. Installs dependencies from the poetry.lock file
|
|
272
|
+
|
|
273
|
+
If no output directory is specified, the zip file is unpacked in the same directory
|
|
274
|
+
as the zip file, in a folder with the same name as the zip file (without the .zip extension).
|
|
275
|
+
"""
|
|
276
|
+
from .deploy import deploy_strategy
|
|
277
|
+
|
|
278
|
+
deploy_strategy(zip_file, output_dir, force)
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
if __name__ == "__main__":
|
|
282
|
+
main()
|