Qubx 0.5.7__tar.gz → 0.6.0__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.

Files changed (104) hide show
  1. {qubx-0.5.7 → qubx-0.6.0}/PKG-INFO +4 -1
  2. {qubx-0.5.7 → qubx-0.6.0}/build.py +3 -3
  3. {qubx-0.5.7 → qubx-0.6.0}/pyproject.toml +59 -51
  4. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/__init__.py +54 -11
  5. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/_nb_magic.py +1 -1
  6. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/backtester/simulator.py +2 -1
  7. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/backtester/utils.py +2 -1
  8. qubx-0.6.0/src/qubx/cli/commands.py +282 -0
  9. qubx-0.6.0/src/qubx/cli/deploy.py +232 -0
  10. qubx-0.6.0/src/qubx/cli/misc.py +381 -0
  11. qubx-0.6.0/src/qubx/cli/release.py +681 -0
  12. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/core/loggers.py +4 -0
  13. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/core/lookups.py +4 -0
  14. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/core/mixins/market.py +1 -1
  15. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/core/series.pyi +21 -4
  16. qubx-0.6.0/src/qubx/utils/__init__.py +16 -0
  17. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/utils/_pyxreloader.py +111 -58
  18. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/utils/misc.py +0 -24
  19. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/utils/orderbook.py +2 -1
  20. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/utils/plotting/dashboard.py +2 -1
  21. qubx-0.6.0/src/qubx/utils/runner/__init__.py +0 -0
  22. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/utils/runner/accounts.py +3 -3
  23. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/utils/runner/runner.py +2 -1
  24. qubx-0.6.0/src/qubx/utils/version.py +182 -0
  25. qubx-0.5.7/src/qubx/cli/commands.py +0 -67
  26. qubx-0.5.7/src/qubx/utils/__init__.py +0 -5
  27. qubx-0.5.7/src/qubx/utils/runner/__init__.py +0 -1
  28. {qubx-0.5.7 → qubx-0.6.0}/README.md +0 -0
  29. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/backtester/__init__.py +0 -0
  30. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/backtester/account.py +0 -0
  31. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/backtester/broker.py +0 -0
  32. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/backtester/data.py +0 -0
  33. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/backtester/management.py +0 -0
  34. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/backtester/ome.py +0 -0
  35. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/backtester/optimization.py +0 -0
  36. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/backtester/simulated_data.py +0 -0
  37. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/cli/__init__.py +0 -0
  38. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/connectors/ccxt/__init__.py +0 -0
  39. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/connectors/ccxt/account.py +0 -0
  40. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/connectors/ccxt/broker.py +0 -0
  41. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/connectors/ccxt/customizations.py +0 -0
  42. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/connectors/ccxt/data.py +0 -0
  43. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/connectors/ccxt/exceptions.py +0 -0
  44. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/connectors/ccxt/factory.py +0 -0
  45. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/connectors/ccxt/utils.py +0 -0
  46. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/core/__init__.py +0 -0
  47. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/core/account.py +0 -0
  48. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/core/basics.py +0 -0
  49. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/core/context.py +0 -0
  50. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/core/exceptions.py +0 -0
  51. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/core/helpers.py +0 -0
  52. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/core/interfaces.py +0 -0
  53. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/core/metrics.py +0 -0
  54. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/core/mixins/__init__.py +0 -0
  55. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/core/mixins/processing.py +0 -0
  56. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/core/mixins/subscription.py +0 -0
  57. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/core/mixins/trading.py +0 -0
  58. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/core/mixins/universe.py +0 -0
  59. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/core/series.pxd +0 -0
  60. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/core/series.pyx +0 -0
  61. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/core/utils.pyi +0 -0
  62. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/core/utils.pyx +0 -0
  63. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/data/__init__.py +0 -0
  64. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/data/helpers.py +0 -0
  65. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/data/readers.py +0 -0
  66. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/data/tardis.py +0 -0
  67. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/gathering/simplest.py +0 -0
  68. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/math/__init__.py +0 -0
  69. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/math/stats.py +0 -0
  70. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/pandaz/__init__.py +0 -0
  71. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/pandaz/ta.py +0 -0
  72. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/pandaz/utils.py +0 -0
  73. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/resources/instruments/symbols-binance.cm.json +0 -0
  74. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/resources/instruments/symbols-binance.json +0 -0
  75. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/resources/instruments/symbols-binance.um.json +0 -0
  76. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/resources/instruments/symbols-bitfinex.f.json +0 -0
  77. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/resources/instruments/symbols-bitfinex.json +0 -0
  78. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/resources/instruments/symbols-kraken.f.json +0 -0
  79. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/resources/instruments/symbols-kraken.json +0 -0
  80. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/ta/__init__.py +0 -0
  81. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/ta/indicators.pxd +0 -0
  82. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/ta/indicators.pyi +0 -0
  83. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/ta/indicators.pyx +0 -0
  84. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/trackers/__init__.py +0 -0
  85. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/trackers/abvanced.py +0 -0
  86. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/trackers/composite.py +0 -0
  87. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/trackers/rebalancers.py +0 -0
  88. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/trackers/riskctrl.py +0 -0
  89. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/trackers/sizers.py +0 -0
  90. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/utils/charting/lookinglass.py +0 -0
  91. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/utils/charting/mpl_helpers.py +0 -0
  92. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/utils/marketdata/binance.py +0 -0
  93. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/utils/marketdata/ccxt.py +0 -0
  94. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/utils/marketdata/dukas.py +0 -0
  95. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/utils/ntp.py +0 -0
  96. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/utils/numbers_utils.py +0 -0
  97. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/utils/plotting/__init__.py +0 -0
  98. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/utils/plotting/data.py +0 -0
  99. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/utils/plotting/interfaces.py +0 -0
  100. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/utils/plotting/renderers/__init__.py +0 -0
  101. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/utils/plotting/renderers/plotly.py +0 -0
  102. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/utils/runner/_jupyter_runner.pyt +0 -0
  103. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/utils/runner/configs.py +0 -0
  104. {qubx-0.5.7 → qubx-0.6.0}/src/qubx/utils/time.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: Qubx
3
- Version: 0.5.7
3
+ Version: 0.6.0
4
4
  Summary: Qubx - Quantitative Trading Framework
5
5
  Author: Dmitry Marienko
6
6
  Author-email: dmitry.marienko@xlydian.com
@@ -15,7 +15,10 @@ Requires-Dist: croniter (>=2.0.5,<3.0.0)
15
15
  Requires-Dist: cython (==3.0.8)
16
16
  Requires-Dist: dash (>=2.18.2,<3.0.0)
17
17
  Requires-Dist: dash-bootstrap-components (>=1.6.0,<2.0.0)
18
+ Requires-Dist: gitpython (>=3.1.44,<4.0.0)
18
19
  Requires-Dist: importlib-metadata
20
+ Requires-Dist: ipywidgets (>=8.1.5,<9.0.0)
21
+ Requires-Dist: jupyter (>=1.1.1,<2.0.0)
19
22
  Requires-Dist: jupyter-console (>=6.6.3,<7.0.0)
20
23
  Requires-Dist: loguru (>=0.7.2,<0.8.0)
21
24
  Requires-Dist: matplotlib (>=3.8.4,<4.0.0)
@@ -21,6 +21,7 @@ PROFILE_MODE = bool(os.getenv("PROFILE_MODE", ""))
21
21
  ANNOTATION_MODE = bool(os.getenv("ANNOTATION_MODE", ""))
22
22
  BUILD_DIR = "build/optimized"
23
23
  COPY_TO_SOURCE = os.getenv("COPY_TO_SOURCE", "true") == "true"
24
+ PYO3_ONLY = os.getenv("PYO3_ONLY", "false") == "true" # Skip Cython compilation if true
24
25
 
25
26
  ################################################################################
26
27
  # CYTHON BUILD
@@ -175,7 +176,7 @@ def build() -> None:
175
176
  # _build_rust_libs()
176
177
  # _copy_rust_dylibs_to_project()
177
178
 
178
- if True: # not PYO3_ONLY:
179
+ if not PYO3_ONLY: # Only build Cython extensions if PYO3_ONLY is false
179
180
  # Create C Extensions to feed into cythonize()
180
181
  extensions = _build_extensions()
181
182
  distribution = _build_distribution(extensions)
@@ -214,9 +215,8 @@ if __name__ == "__main__":
214
215
  print(f"BUILD_DIR={BUILD_DIR}")
215
216
  print(f"PROFILE_MODE={PROFILE_MODE}")
216
217
  print(f"ANNOTATION_MODE={ANNOTATION_MODE}")
217
- # print(f"PARALLEL_BUILD={PARALLEL_BUILD}")
218
+ print(f"PYO3_ONLY={PYO3_ONLY}")
218
219
  print(f"COPY_TO_SOURCE={COPY_TO_SOURCE}")
219
- # print(f"PYO3_ONLY={PYO3_ONLY}\n")
220
220
 
221
221
  print(f">> {GREEN}Starting build...{RES}")
222
222
  ts_start = datetime.datetime.now(datetime.timezone.utc)
@@ -1,19 +1,28 @@
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.5.7"
7
+ version = "0.6.0"
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
- include = [
13
- # Compiled extensions must be included in the wheel distributions
14
- { path = "src/**/*.so", format = "wheel" },
15
- { path = "src/**/*.pyd", format = "wheel" },
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
17
26
 
18
27
  [tool.poetry.dependencies]
19
28
  python = ">=3.10,<4.0"
@@ -49,60 +58,59 @@ dash-bootstrap-components = "^1.6.0"
49
58
  tabulate = "^0.9.0"
50
59
  jupyter-console = "^6.6.3"
51
60
  toml = "^0.10.2"
61
+ gitpython = "^3.1.44"
62
+ ipywidgets = "^8.1.5"
63
+ jupyter = "^1.1.1"
64
+
65
+ [tool.poetry.build]
66
+ script = "build.py"
67
+ generate-setup-file = false
68
+
69
+ [tool.poetry.scripts]
70
+ qubx = "qubx.cli.commands:main"
71
+
72
+ [tool.ruff.lint]
73
+ extend-select = [ "I",]
74
+ ignore = [ "E731", "E722", "E741",]
75
+
76
+ [tool.pytest.ini_options]
77
+ asyncio_mode = "auto"
78
+ asyncio_default_fixture_loop_scope = "function"
79
+ pythonpath = [ "src",]
80
+ markers = [ "integration: mark a test as an integration test",]
81
+ addopts = "--disable-warnings"
82
+ filterwarnings = [ "ignore:.*Jupyter is migrating.*:DeprecationWarning",]
83
+
84
+ [tool.ruff.lint.extend-per-file-ignores]
85
+ "*.ipynb" = [ "F405", "F401", "E701", "E402", "F403", "E401", "E702", "I001",]
52
86
 
53
87
  [tool.poetry.group.dev.dependencies]
54
88
  pre-commit = "^2.20.0"
55
89
  rust-just = "^1.36.0"
56
90
  twine = "^5.1.1"
57
-
58
- #[project.optional-dependencies]
59
- #numba = "^0.57.1"
60
91
  ipykernel = "^6.29.4"
61
92
  iprogress = "^0.4"
62
93
  click = "^8.1.7"
63
94
  ipywidgets = "^8.1.5"
64
95
  ruff = "^0.9.7"
65
-
66
- [build-system]
67
- requires = [
68
- "poetry-core",
69
- "setuptools",
70
- "numpy>=1.26.3",
71
- "cython==3.0.8",
72
- "toml>=0.10.2",
73
- ]
74
- build-backend = "poetry.core.masonry.api"
75
-
76
- [tool.poetry.build]
77
- script = "build.py"
78
- generate-setup-file = false
96
+ pytest-xdist = "^3.6.1"
97
+ nbformat = "^5.10.4"
98
+ markdown = "3.7"
99
+ mkdocs = "1.6.1"
100
+ mkdocs-material = "9.6.5"
101
+ mdx-truly-sane-lists = "1.3"
102
+ pymdown-extensions = "10.14.3"
103
+ jinja2 = "3.1.5"
104
+ mike = "2.1.3"
105
+ mkdocs-jupyter = "0.25.1"
106
+ debugpy = "^1.8.12"
79
107
 
80
108
  [tool.poetry.group.test.dependencies]
81
- pytest = { extras = ["lazyfixture"], version = "^8.2.0" }
82
109
  pytest-asyncio = "^0.24.0"
83
110
  pytest-mock = "^3.12.0"
84
111
  pytest-lazy-fixture = "^0.6.3"
85
112
  pytest-cov = "^4.1.0"
86
113
 
87
- [tool.pytest.ini_options]
88
- asyncio_mode = "auto"
89
- asyncio_default_fixture_loop_scope = "function"
90
- pythonpath = ["src"]
91
- markers = ["integration: mark a test as an integration test"]
92
- addopts = "--disable-warnings"
93
- filterwarnings = ["ignore:.*Jupyter is migrating.*:DeprecationWarning"]
94
-
95
- [tool.ruff]
96
- line-length = 120
97
- lint.extend-select = ["I"]
98
- lint.ignore = [
99
- "E731",
100
- "E722",
101
- "E741",
102
- ] # Ignore lambda assignments, bare except, and ambiguous variable names
103
-
104
- [tool.ruff.lint.extend-per-file-ignores]
105
- "*.ipynb" = ["F405", "F401", "E701", "E402", "F403", "E401", "E702"]
106
-
107
- [tool.poetry.scripts]
108
- qubx = "qubx.cli.commands:main"
114
+ [tool.poetry.group.test.dependencies.pytest]
115
+ extras = [ "lazyfixture",]
116
+ 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,11 @@ class QubxLogConfig:
47
67
  @staticmethod
48
68
  def setup_logger(level: str | None = None, custom_formatter: Callable | None = None):
49
69
  global logger
70
+
71
+ # First, remove all existing handlers to prevent resource leaks
72
+ # Use a safer approach that doesn't rely on internal attributes
73
+ logger.remove()
74
+
50
75
  config = {
51
76
  "handlers": [
52
77
  {"sink": sys.stdout, "format": "{time} - {message}"},
@@ -54,24 +79,42 @@ class QubxLogConfig:
54
79
  "extra": {"user": "someone"},
55
80
  }
56
81
  logger.configure(**config)
57
- logger.remove(None)
82
+
58
83
  level = level or QubxLogConfig.get_log_level()
59
- logger.add(sys.stdout, format=custom_formatter or formatter, colorize=True, level=level, enqueue=True)
84
+ # Add stdout handler with enqueue=True for thread/process safety
85
+ logger.add(
86
+ sys.stdout,
87
+ format=custom_formatter or formatter,
88
+ colorize=True,
89
+ level=level,
90
+ enqueue=True,
91
+ backtrace=True,
92
+ diagnose=True,
93
+ )
60
94
  logger = logger.opt(colors=True)
61
95
 
62
96
 
63
97
  QubxLogConfig.setup_logger()
64
98
 
65
99
 
66
- # - global lookup helper
67
- lookup = GlobalLookup(InstrumentsLookup(), FeesLookup())
68
-
69
-
70
100
  # registering magic for jupyter notebook
71
101
  if runtime_env() in ["notebook", "shell"]:
72
102
  from IPython.core.getipython import get_ipython
73
103
  from IPython.core.magic import Magics, line_cell_magic, line_magic, magics_class
74
104
 
105
+ from qubx.utils.charting.lookinglass import LookingGlass # noqa: F401
106
+ from qubx.utils.charting.mpl_helpers import ( # noqa: F401
107
+ ellips,
108
+ fig,
109
+ hline,
110
+ ohlc_plot,
111
+ plot_trends,
112
+ sbp,
113
+ set_mpl_theme,
114
+ vline,
115
+ )
116
+ from qubx.utils.misc import install_pyx_recompiler_for_dev
117
+
75
118
  @magics_class
76
119
  class QubxMagics(Magics):
77
120
  # process data manager
@@ -119,7 +162,7 @@ if runtime_env() in ["notebook", "shell"]:
119
162
  exec(_vscode_clr_trick, self.shell.user_ns)
120
163
 
121
164
  elif "light" in line.lower():
122
- set_mpl_theme("light")
165
+ sort: skip_mpl_theme("light")
123
166
 
124
167
  def _get_manager(self):
125
168
  if self.__manager is None:
@@ -3,7 +3,7 @@ Here stuff we want to have in every Jupyter notebook after calling %qubx magic
3
3
  """
4
4
 
5
5
  import qubx
6
- from qubx.utils import runtime_env
6
+ from qubx import runtime_env
7
7
  from qubx.utils.misc import add_project_to_system_path, logo
8
8
 
9
9
 
@@ -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, lookup
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, lookup
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()