Qubx 0.0.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.0.1/PKG-INFO ADDED
@@ -0,0 +1,39 @@
1
+ Metadata-Version: 2.1
2
+ Name: Qubx
3
+ Version: 0.0.1
4
+ Summary: Qubx - quantitative trading framework
5
+ Home-page: https://github.com/dmarienko/Qubx
6
+ Author: Dmitry Marienko
7
+ Author-email: dmitry@gmail.com
8
+ Requires-Python: >=3.9,<4.0
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3.9
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Requires-Dist: cython (==3.0.8)
15
+ Requires-Dist: loguru (>=0.7.2,<0.8.0)
16
+ Requires-Dist: ntplib (>=0.4.0,<0.5.0)
17
+ Requires-Dist: numpy (>=1.26.3,<2.0.0)
18
+ Requires-Dist: pyarrow (>=15.0.0,<16.0.0)
19
+ Requires-Dist: pydantic (>=1.10.2,<2.0.0)
20
+ Requires-Dist: pymongo (>=4.6.1,<5.0.0)
21
+ Requires-Dist: pytest[lazyfixture] (>=7.2.0,<8.0.0)
22
+ Requires-Dist: python-binance (>=1.0.19,<2.0.0)
23
+ Requires-Dist: python-dotenv (>=1.0.0,<2.0.0)
24
+ Requires-Dist: scipy (>=1.12.0,<2.0.0)
25
+ Requires-Dist: stackprinter (>=0.2.10,<0.3.0)
26
+ Project-URL: Repository, https://github.com/dmarienko/Qubx
27
+ Description-Content-Type: text/markdown
28
+
29
+ # Qubx
30
+
31
+ ### Next generation of Qube quantitative backtesting framework (QUBX)
32
+ ```
33
+ /////\
34
+ ///// \
35
+ \\\\\ /
36
+ \\\\\/ (c) 2024, by M.D.E
37
+ ```
38
+
39
+
qubx-0.0.1/README.md ADDED
@@ -0,0 +1,10 @@
1
+ # Qubx
2
+
3
+ ### Next generation of Qube quantitative backtesting framework (QUBX)
4
+ ```
5
+ /////\
6
+ ///// \
7
+ \\\\\ /
8
+ \\\\\/ (c) 2024, by M.D.E
9
+ ```
10
+
qubx-0.0.1/build.py ADDED
@@ -0,0 +1,265 @@
1
+ import datetime
2
+ import itertools
3
+ import os
4
+ import platform
5
+ import shutil
6
+ import subprocess
7
+ import sysconfig
8
+
9
+ from pathlib import Path
10
+ import os
11
+ import numpy as np
12
+ import toml
13
+ from Cython.Build import build_ext
14
+ from Cython.Build import cythonize
15
+ from Cython.Compiler import Options
16
+ from Cython.Compiler.Version import version as cython_compiler_version
17
+ from setuptools import Distribution
18
+ from setuptools import Extension
19
+
20
+
21
+ BUILD_MODE = os.getenv("BUILD_MODE", "release")
22
+ PROFILE_MODE = bool(os.getenv("PROFILE_MODE", ""))
23
+ ANNOTATION_MODE = bool(os.getenv("ANNOTATION_MODE", ""))
24
+ BUILD_DIR = "build/optimized"
25
+ COPY_TO_SOURCE = os.getenv("COPY_TO_SOURCE", "true") == "true"
26
+
27
+ ################################################################################
28
+ # CYTHON BUILD
29
+ ################################################################################
30
+ # https://cython.readthedocs.io/en/latest/src/userguide/source_files_and_compilation.html
31
+
32
+ Options.docstrings = True # Include docstrings in modules
33
+ Options.fast_fail = True # Abort the compilation on the first error occurred
34
+ Options.annotate = ANNOTATION_MODE # Create annotated HTML files for each .pyx
35
+ if ANNOTATION_MODE:
36
+ Options.annotate_coverage_xml = "coverage.xml"
37
+ Options.fast_fail = True # Abort compilation on first error
38
+ Options.warning_errors = True # Treat compiler warnings as errors
39
+ Options.extra_warnings = True
40
+
41
+ CYTHON_COMPILER_DIRECTIVES = {
42
+ "language_level": "3",
43
+ "cdivision": True, # If division is as per C with no check for zero (35% speed up)
44
+ "nonecheck": True, # Insert extra check for field access on C extensions
45
+ "embedsignature": True, # If docstrings should be embedded into C signatures
46
+ "profile": PROFILE_MODE, # If we're debugging or profiling
47
+ "linetrace": PROFILE_MODE, # If we're debugging or profiling
48
+ "warn.maybe_uninitialized": True,
49
+ }
50
+
51
+
52
+ def _build_extensions() -> list[Extension]:
53
+ # Regarding the compiler warning: #warning "Using deprecated NumPy API,
54
+ # disable it with " "#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION"
55
+ # https://stackoverflow.com/questions/52749662/using-deprecated-numpy-api
56
+ # From the Cython docs: "For the time being, it is just a warning that you can ignore."
57
+ define_macros: list[tuple[str, str | None]] = [
58
+ ("NPY_NO_DEPRECATED_API", "NPY_1_7_API_VERSION"),
59
+ ]
60
+ if PROFILE_MODE or ANNOTATION_MODE:
61
+ # Profiling requires special macro directives
62
+ define_macros.append(("CYTHON_TRACE", "1"))
63
+
64
+ extra_compile_args = []
65
+ extra_link_args = []
66
+ # extra_link_args = RUST_LIBS
67
+
68
+ if platform.system() != "Windows":
69
+ # Suppress warnings produced by Cython boilerplate
70
+ extra_compile_args.append("-Wno-unreachable-code")
71
+ if BUILD_MODE == "release":
72
+ extra_compile_args.append("-O2")
73
+ extra_compile_args.append("-pipe")
74
+
75
+ if platform.system() == "Windows":
76
+ extra_link_args += [
77
+ "AdvAPI32.Lib",
78
+ "bcrypt.lib",
79
+ "Crypt32.lib",
80
+ "Iphlpapi.lib",
81
+ "Kernel32.lib",
82
+ "ncrypt.lib",
83
+ "Netapi32.lib",
84
+ "ntdll.lib",
85
+ "Ole32.lib",
86
+ "OleAut32.lib",
87
+ "Pdh.lib",
88
+ "PowrProf.lib",
89
+ "Psapi.lib",
90
+ "schannel.lib",
91
+ "secur32.lib",
92
+ "Shell32.lib",
93
+ "User32.Lib",
94
+ "UserEnv.Lib",
95
+ "WS2_32.Lib",
96
+ ]
97
+
98
+ print("Creating C extension modules...")
99
+ print(f"define_macros={define_macros}")
100
+ print(f"extra_compile_args={extra_compile_args}")
101
+
102
+ return [
103
+ Extension(
104
+ name=str(pyx.relative_to(".")).replace(os.path.sep, ".")[:-4],
105
+ sources=[str(pyx)],
106
+ include_dirs=[np.get_include()], #, *RUST_INCLUDES],
107
+ define_macros=define_macros,
108
+ language="c",
109
+ extra_link_args=extra_link_args,
110
+ extra_compile_args=extra_compile_args,
111
+ ) for pyx in itertools.chain(Path("src/qubx").rglob("*.pyx"))
112
+ ]
113
+
114
+
115
+ def _build_distribution(extensions: list[Extension]) -> Distribution:
116
+ nthreads = os.cpu_count() or 1
117
+ if platform.system() == "Windows":
118
+ nthreads = min(nthreads, 60)
119
+ print(f"nthreads={nthreads}")
120
+
121
+ distribution = Distribution(
122
+ {
123
+ "name": "qubx",
124
+ "ext_modules": cythonize(
125
+ module_list=extensions,
126
+ compiler_directives=CYTHON_COMPILER_DIRECTIVES,
127
+ nthreads=nthreads,
128
+ build_dir=BUILD_DIR,
129
+ gdb_debug=PROFILE_MODE,
130
+ ),
131
+ "zip_safe": False,
132
+ },
133
+ )
134
+ return distribution
135
+
136
+
137
+ def _copy_build_dir_to_project(cmd: build_ext) -> None:
138
+ # Copy built extensions back to the project tree
139
+ for output in cmd.get_outputs():
140
+ relative_extension = Path(output).relative_to(cmd.build_lib)
141
+ if not Path(output).exists():
142
+ continue
143
+
144
+ # Copy the file and set permissions
145
+ shutil.copyfile(output, relative_extension)
146
+ mode = relative_extension.stat().st_mode
147
+ mode |= (mode & 0o444) >> 2
148
+ relative_extension.chmod(mode)
149
+
150
+ print("Copied all compiled dynamic library files into source")
151
+
152
+
153
+ def _strip_unneeded_symbols() -> None:
154
+ try:
155
+ print("Stripping unneeded symbols from binaries...")
156
+ for so in itertools.chain(Path("nautilus_trader").rglob("*.so")):
157
+ if platform.system() == "Linux":
158
+ strip_cmd = ["strip", "--strip-unneeded", so]
159
+ elif platform.system() == "Darwin":
160
+ strip_cmd = ["strip", "-x", so]
161
+ else:
162
+ raise RuntimeError(f"Cannot strip symbols for platform {platform.system()}")
163
+ subprocess.run(
164
+ strip_cmd, # type: ignore [arg-type] # noqa
165
+ check=True,
166
+ capture_output=True,
167
+ )
168
+ except subprocess.CalledProcessError as e:
169
+ raise RuntimeError(f"Error when stripping symbols.\n{e}") from e
170
+
171
+
172
+ def build() -> None:
173
+ """
174
+ Construct the extensions and distribution.
175
+ """
176
+ # _build_rust_libs()
177
+ # _copy_rust_dylibs_to_project()
178
+
179
+ if True: #not PYO3_ONLY:
180
+ # Create C Extensions to feed into cythonize()
181
+ extensions = _build_extensions()
182
+ distribution = _build_distribution(extensions)
183
+
184
+ # Build and run the command
185
+ print("Compiling C extension modules...")
186
+ cmd: build_ext = build_ext(distribution)
187
+ # if PARALLEL_BUILD:
188
+ # cmd.parallel = os.cpu_count()
189
+ cmd.ensure_finalized()
190
+ cmd.run()
191
+
192
+ if COPY_TO_SOURCE:
193
+ # Copy the build back into the source tree for development and wheel packaging
194
+ _copy_build_dir_to_project(cmd)
195
+
196
+ if BUILD_MODE == "release" and platform.system() in ("Linux", "Darwin"):
197
+ # Only strip symbols for release builds
198
+ _strip_unneeded_symbols()
199
+
200
+
201
+ if __name__ == "__main__":
202
+ qubx_platform = toml.load("pyproject.toml")["tool"]["poetry"]["version"]
203
+ print("\033[36m")
204
+ print("=====================================================================")
205
+ print(f"Qubx Builder {qubx_platform}")
206
+ print("=====================================================================\033[0m")
207
+ print(f"System: {platform.system()} {platform.machine()}")
208
+ # print(f"Clang: {_get_clang_version()}")
209
+ # print(f"Rust: {_get_rustc_version()}")
210
+ print(f"Python: {platform.python_version()}")
211
+ print(f"Cython: {cython_compiler_version}")
212
+ print(f"NumPy: {np.__version__}\n")
213
+
214
+ print(f"BUILD_MODE={BUILD_MODE}")
215
+ print(f"BUILD_DIR={BUILD_DIR}")
216
+ print(f"PROFILE_MODE={PROFILE_MODE}")
217
+ print(f"ANNOTATION_MODE={ANNOTATION_MODE}")
218
+ # print(f"PARALLEL_BUILD={PARALLEL_BUILD}")
219
+ print(f"COPY_TO_SOURCE={COPY_TO_SOURCE}")
220
+ # print(f"PYO3_ONLY={PYO3_ONLY}\n")
221
+
222
+ print("Starting build...")
223
+ ts_start = datetime.datetime.now(datetime.timezone.utc)
224
+ build()
225
+ print(f"Build time: {datetime.datetime.now(datetime.timezone.utc) - ts_start}")
226
+ print("\033[32m" + "Build completed" + "\033[0m")
227
+
228
+ # # See if Cython is installed
229
+ # try:
230
+ # from Cython.Build import cythonize
231
+ # # Do nothing if Cython is not available
232
+ # except ImportError:
233
+ # # Got to provide this function. Otherwise, poetry will fail
234
+ # def build(setup_kwargs):
235
+ # pass
236
+
237
+ # # Cython is installed. Compile
238
+ # else:
239
+ # from setuptools import Extension
240
+ # from setuptools.dist import Distribution
241
+ # from distutils.command.build_ext import build_ext
242
+
243
+ # # This function will be executed in setup.py:
244
+ # def build(setup_kwargs):
245
+ # # The file you want to compile
246
+ # extensions = [
247
+ # "src/qubx/core/series.pyx",
248
+ # "src/qubx/core/utils.pyx",
249
+ # "src/qubx/ta/indicators.pyx",
250
+ # ]
251
+
252
+ # # gcc arguments hack: enable optimizations
253
+ # os.environ['CFLAGS'] = '-O3'
254
+
255
+ # # Build
256
+ # import numpy as np
257
+ # setup_kwargs.update({
258
+ # 'ext_modules': cythonize(
259
+ # extensions,
260
+ # language_level=3,
261
+ # compiler_directives={'linetrace': True},
262
+ # include_path=[np.get_include()]
263
+ # ),
264
+ # 'cmdclass': {'build_ext': build_ext}
265
+ # })
@@ -0,0 +1,56 @@
1
+ [tool.poetry]
2
+ name = "Qubx"
3
+ version = "0.0.1"
4
+ description = "Qubx - quantitative trading framework"
5
+ authors = ["Dmitry Marienko <dmitry@gmail.com>"]
6
+ readme = "README.md"
7
+ packages = [
8
+ { include = "qubx", from = "src" },
9
+ ]
10
+ repository = "https://github.com/dmarienko/Qubx"
11
+ include = [
12
+ # Compiled extensions must be included in the wheel distributions
13
+ { path = "src/**/*.so", format = "wheel" },
14
+ { path = "src/**/*.pyd", format = "wheel" },
15
+ ]
16
+
17
+ [tool.poetry.dependencies]
18
+ python = ">=3.9,<4.0"
19
+ pytest = {extras = ["lazyfixture"], version = "^7.2.0"}
20
+ numpy = "^1.26.3"
21
+ ntplib = "^0.4.0"
22
+ loguru = "^0.7.2"
23
+ stackprinter = "^0.2.10"
24
+ #websocket-client = "^1.6.3"
25
+ #websockets = "^11.0.3"
26
+ pymongo = "^4.6.1"
27
+ pydantic = "^1.10.2"
28
+ python-dotenv = "^1.0.0"
29
+ python-binance = "^1.0.19"
30
+ pyarrow = "^15.0.0"
31
+ scipy = "^1.12.0"
32
+ cython = "3.0.8"
33
+
34
+ [tool.poetry.group.dev.dependencies]
35
+ pre-commit = "^2.20.0"
36
+ pytest = "^7.1.3"
37
+
38
+ #[project.optional-dependencies]
39
+ #numba = "^0.57.1"
40
+
41
+ [build-system]
42
+ requires = ["poetry-core", "setuptools", "numpy>=1.26.3", "cython==3.0.8", "toml>=0.10.2",]
43
+ build-backend = "poetry.core.masonry.api"
44
+
45
+ [tool.poetry.build]
46
+ script = "build.py"
47
+ generate-setup-file = false
48
+
49
+
50
+ [tool.poetry.group.test.dependencies]
51
+ pytest = "^7.1.3"
52
+ pytest-mock = "*"
53
+
54
+
55
+ [tool.pytest.ini_options]
56
+ pythonpath = ["src"]
@@ -0,0 +1,164 @@
1
+ from qubx.utils import set_mpl_theme, runtime_env, install_pyx_recompiler_for_dev
2
+ install_pyx_recompiler_for_dev()
3
+
4
+ from loguru import logger
5
+ import os, sys, stackprinter
6
+ from qubx.core.lookups import InstrumentsLookup
7
+
8
+
9
+ def formatter(record):
10
+ end = record["extra"].get("end", "\n")
11
+ fmt = "<lvl>{message}</lvl>%s" % end
12
+ if record["level"].name in {"WARNING", "SNAKY"}:
13
+ fmt = "<cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - %s" % fmt
14
+
15
+ prefix = "<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> [ <level>%s</level> ] " % record["level"].icon
16
+
17
+ if record["exception"] is not None:
18
+ # stackprinter.set_excepthook(style='darkbg2')
19
+ record["extra"]["stack"] = stackprinter.format(record["exception"], style="darkbg")
20
+ fmt += "\n{extra[stack]}\n"
21
+
22
+ if record["level"].name in {"TEXT"}:
23
+ prefix = ""
24
+
25
+ return prefix + fmt
26
+
27
+
28
+ config = {
29
+ "handlers": [ {"sink": sys.stdout, "format": "{time} - {message}"}, ],
30
+ "extra": {"user": "someone"},
31
+ }
32
+
33
+
34
+ logger.configure(**config)
35
+ logger.remove(None)
36
+ logger.add(sys.stdout, format=formatter, colorize=True)
37
+ logger = logger.opt(colors=True)
38
+
39
+ lookup = InstrumentsLookup()
40
+
41
+ # registering magic for jupyter notebook
42
+ if runtime_env() in ['notebook', 'shell']:
43
+ from IPython.core.magic import (Magics, magics_class, line_magic, line_cell_magic)
44
+ from IPython import get_ipython
45
+
46
+ @magics_class
47
+ class QubxMagics(Magics):
48
+ # process data manager
49
+ __manager = None
50
+
51
+ @line_magic
52
+ def qubxd(self, line: str):
53
+ self.qubx_setup('dark')
54
+
55
+ @line_magic
56
+ def qubxl(self, line: str):
57
+ self.qubx_setup('light')
58
+
59
+ @line_magic
60
+ def qubx_setup(self, line: str):
61
+ """
62
+ QUBE framework initialization
63
+ """
64
+ import os
65
+
66
+ tpl_path = os.path.join(os.path.dirname(__file__), "_nb_magic.py")
67
+ # print("TPL:", tpl_path)
68
+ with open(tpl_path, 'r', encoding="utf8") as myfile:
69
+ s = myfile.read()
70
+
71
+ exec(s, self.shell.user_ns)
72
+
73
+ # setup more funcy mpl theme instead of ugly default
74
+ if line:
75
+ if 'dark' in line.lower():
76
+ set_mpl_theme('dark')
77
+
78
+ elif 'light' in line.lower():
79
+ set_mpl_theme('light')
80
+
81
+ # install additional plotly helpers
82
+ # from qube.charting.plot_helpers import install_plotly_helpers
83
+ # install_plotly_helpers()
84
+
85
+ def _get_manager(self):
86
+ if self.__manager is None:
87
+ import multiprocessing as m
88
+ self.__manager = m.Manager()
89
+ return self.__manager
90
+
91
+ @line_cell_magic
92
+ def proc(self, line, cell=None):
93
+ """
94
+ Run cell in separate process
95
+
96
+ >>> %%proc x, y as MyProc1
97
+ >>> x.set('Hello')
98
+ >>> y.set([1,2,3,4])
99
+
100
+ """
101
+ import multiprocessing as m
102
+ import time, re
103
+
104
+ # create ext args
105
+ name = None
106
+ if line:
107
+ # check if custom process name was provided
108
+ if ' as ' in line:
109
+ line, name = line.split('as')
110
+ if not name.isspace():
111
+ name = name.strip()
112
+ else:
113
+ print('>>> Process name must be specified afer "as" keyword !')
114
+ return
115
+
116
+ ipy = get_ipython()
117
+ for a in [x for x in re.split('[\ ,;]', line.strip()) if x]:
118
+ ipy.push({a: self._get_manager().Value(None, None)})
119
+
120
+ # code to run
121
+ lines = '\n'.join([' %s' % x for x in cell.split('\n')])
122
+
123
+ def fn():
124
+ result = get_ipython().run_cell(lines)
125
+
126
+ # send errors to parent
127
+ if result.error_before_exec:
128
+ raise result.error_before_exec
129
+
130
+ if result.error_in_exec:
131
+ raise result.error_in_exec
132
+
133
+ t_start = str(time.time()).replace('.', '_')
134
+ f_id = f'proc_{t_start}' if name is None else name
135
+ if self._is_task_name_already_used(f_id):
136
+ f_id = f"{f_id}_{t_start}"
137
+
138
+ task = m.Process(target=fn, name=f_id)
139
+ task.start()
140
+ print(' -> Task %s is started' % f_id)
141
+
142
+ def _is_task_name_already_used(self, name):
143
+ import multiprocessing as m
144
+ for p in m.active_children():
145
+ if p.name == name:
146
+ return True
147
+ return False
148
+
149
+ @line_magic
150
+ def list_proc(self, line):
151
+ import multiprocessing as m
152
+ for p in m.active_children():
153
+ print(p.name)
154
+
155
+ @line_magic
156
+ def kill_proc(self, line):
157
+ import multiprocessing as m
158
+ for p in m.active_children():
159
+ if line and p.name.startswith(line):
160
+ p.terminate()
161
+
162
+
163
+ # - registering magic here
164
+ get_ipython().register_magics(QubxMagics)
@@ -0,0 +1,69 @@
1
+ """"
2
+ Here stuff we want to have in every Jupyter notebook after calling %qube magic
3
+ """
4
+ import importlib_metadata
5
+
6
+ import qubx
7
+ from qubx.utils import runtime_env
8
+ from qubx.utils.misc import add_project_to_system_path
9
+
10
+
11
+ def np_fmt_short():
12
+ # default np output is 75 columns so extend it a bit and suppress scientific fmt for small floats
13
+ np.set_printoptions(linewidth=240, suppress=True)
14
+
15
+
16
+ def np_fmt_reset():
17
+ # reset default np printing options
18
+ np.set_printoptions(edgeitems=3, infstr='inf', linewidth=75, nanstr='nan', precision=8,
19
+ suppress=False, threshold=1000, formatter=None)
20
+
21
+
22
+ if runtime_env() in ['notebook', 'shell']:
23
+
24
+ # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
25
+ # -- all imports below will appear in notebook after calling %%alphalab magic ---
26
+ # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
27
+
28
+ # - - - - Common stuff - - - -
29
+ import numpy as np
30
+ import pandas as pd
31
+ from datetime import time, timedelta
32
+ from tqdm.auto import tqdm
33
+
34
+ # - - - - TA stuff and indicators - - - -
35
+ # - - - - Portfolio analysis - - - -
36
+ # - - - - Simulator stuff - - - -
37
+ # - - - - Learn stuff - - - -
38
+ # - - - - Charting stuff - - - -
39
+ from matplotlib import pyplot as plt
40
+ from qubx.utils.charting.mpl_helpers import fig, subplot, sbp
41
+ # - - - - Utils - - - -
42
+
43
+ # - setup short numpy output format
44
+ np_fmt_short()
45
+
46
+ # - add project home to system path
47
+ add_project_to_system_path()
48
+
49
+ # - check current version
50
+ try:
51
+ version = importlib_metadata.version('qube2')
52
+ except:
53
+ version = 'Dev'
54
+
55
+ # some new logo
56
+ if not hasattr(qubx.QubxMagics, '__already_initialized__'):
57
+ from qubx.utils.misc import (green, yellow, cyan, magenta, white, blue, red)
58
+
59
+ print(
60
+ f"""
61
+ {red("╻")}
62
+ {green("┏┓ ╻ ")} {red("┃")} {yellow("┏┓")} {cyan("Quantitative Backtesting Environment")}
63
+ {green("┃┃ ┓┏ ┣┓ ┏┓")} {red("┃")} {yellow("┏┛")}
64
+ {green("┗┻ ┗┻ ┗┛ ┗ ")} {red("┃")} {yellow("┗━")} (c) 2024, ver. {magenta(version.rstrip())}
65
+ {red("╹")}
66
+ """
67
+ )
68
+ qubx.QubxMagics.__already_initialized__ = True
69
+
File without changes