Qubx 0.5.6__tar.gz → 0.5.7__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 (99) hide show
  1. {qubx-0.5.6 → qubx-0.5.7}/PKG-INFO +12 -9
  2. {qubx-0.5.6 → qubx-0.5.7}/README.md +7 -3
  3. {qubx-0.5.6 → qubx-0.5.7}/build.py +3 -7
  4. {qubx-0.5.6 → qubx-0.5.7}/pyproject.toml +16 -7
  5. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/_nb_magic.py +16 -16
  6. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/backtester/__init__.py +2 -0
  7. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/backtester/data.py +4 -0
  8. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/backtester/management.py +159 -18
  9. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/backtester/ome.py +3 -3
  10. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/backtester/optimization.py +5 -2
  11. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/backtester/simulated_data.py +1 -1
  12. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/connectors/ccxt/account.py +0 -1
  13. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/connectors/ccxt/broker.py +3 -1
  14. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/connectors/ccxt/factory.py +0 -1
  15. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/connectors/ccxt/utils.py +4 -10
  16. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/core/account.py +1 -1
  17. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/core/basics.py +1 -1
  18. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/core/loggers.py +1 -2
  19. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/core/lookups.py +57 -31
  20. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/core/metrics.py +40 -2
  21. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/core/mixins/__init__.py +8 -0
  22. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/core/mixins/universe.py +1 -3
  23. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/data/__init__.py +18 -6
  24. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/data/helpers.py +20 -11
  25. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/data/readers.py +28 -25
  26. qubx-0.5.7/src/qubx/math/__init__.py +3 -0
  27. qubx-0.5.7/src/qubx/pandaz/__init__.py +23 -0
  28. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/pandaz/ta.py +192 -21
  29. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/trackers/riskctrl.py +14 -2
  30. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/trackers/sizers.py +7 -1
  31. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/utils/runner/runner.py +2 -0
  32. qubx-0.5.6/src/qubx/math/__init__.py +0 -1
  33. qubx-0.5.6/src/qubx/pandaz/__init__.py +0 -15
  34. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/__init__.py +0 -0
  35. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/backtester/account.py +0 -0
  36. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/backtester/broker.py +0 -0
  37. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/backtester/simulator.py +0 -0
  38. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/backtester/utils.py +0 -0
  39. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/cli/__init__.py +0 -0
  40. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/cli/commands.py +0 -0
  41. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/connectors/ccxt/__init__.py +0 -0
  42. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/connectors/ccxt/customizations.py +0 -0
  43. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/connectors/ccxt/data.py +0 -0
  44. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/connectors/ccxt/exceptions.py +0 -0
  45. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/core/__init__.py +0 -0
  46. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/core/context.py +0 -0
  47. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/core/exceptions.py +0 -0
  48. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/core/helpers.py +0 -0
  49. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/core/interfaces.py +0 -0
  50. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/core/mixins/market.py +0 -0
  51. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/core/mixins/processing.py +0 -0
  52. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/core/mixins/subscription.py +0 -0
  53. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/core/mixins/trading.py +0 -0
  54. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/core/series.pxd +0 -0
  55. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/core/series.pyi +0 -0
  56. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/core/series.pyx +0 -0
  57. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/core/utils.pyi +0 -0
  58. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/core/utils.pyx +0 -0
  59. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/data/tardis.py +0 -0
  60. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/gathering/simplest.py +0 -0
  61. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/math/stats.py +0 -0
  62. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/pandaz/utils.py +0 -0
  63. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/resources/instruments/symbols-binance.cm.json +0 -0
  64. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/resources/instruments/symbols-binance.json +0 -0
  65. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/resources/instruments/symbols-binance.um.json +0 -0
  66. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/resources/instruments/symbols-bitfinex.f.json +0 -0
  67. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/resources/instruments/symbols-bitfinex.json +0 -0
  68. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/resources/instruments/symbols-kraken.f.json +0 -0
  69. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/resources/instruments/symbols-kraken.json +0 -0
  70. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/ta/__init__.py +0 -0
  71. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/ta/indicators.pxd +0 -0
  72. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/ta/indicators.pyi +0 -0
  73. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/ta/indicators.pyx +0 -0
  74. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/trackers/__init__.py +0 -0
  75. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/trackers/abvanced.py +0 -0
  76. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/trackers/composite.py +0 -0
  77. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/trackers/rebalancers.py +0 -0
  78. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/utils/__init__.py +0 -0
  79. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/utils/_pyxreloader.py +0 -0
  80. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/utils/charting/lookinglass.py +0 -0
  81. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/utils/charting/mpl_helpers.py +0 -0
  82. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/utils/marketdata/binance.py +0 -0
  83. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/utils/marketdata/ccxt.py +0 -0
  84. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/utils/marketdata/dukas.py +0 -0
  85. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/utils/misc.py +0 -0
  86. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/utils/ntp.py +0 -0
  87. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/utils/numbers_utils.py +0 -0
  88. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/utils/orderbook.py +0 -0
  89. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/utils/plotting/__init__.py +0 -0
  90. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/utils/plotting/dashboard.py +0 -0
  91. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/utils/plotting/data.py +0 -0
  92. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/utils/plotting/interfaces.py +0 -0
  93. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/utils/plotting/renderers/__init__.py +0 -0
  94. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/utils/plotting/renderers/plotly.py +0 -0
  95. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/utils/runner/__init__.py +0 -0
  96. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/utils/runner/_jupyter_runner.pyt +0 -0
  97. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/utils/runner/accounts.py +0 -0
  98. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/utils/runner/configs.py +0 -0
  99. {qubx-0.5.6 → qubx-0.5.7}/src/qubx/utils/time.py +0 -0
@@ -1,15 +1,15 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: Qubx
3
- Version: 0.5.6
4
- Summary: Qubx - quantitative trading framework
5
- Home-page: https://github.com/dmarienko/Qubx
3
+ Version: 0.5.7
4
+ Summary: Qubx - Quantitative Trading Framework
6
5
  Author: Dmitry Marienko
7
- Author-email: dmitry@gmail.com
6
+ Author-email: dmitry.marienko@xlydian.com
8
7
  Requires-Python: >=3.10,<4.0
9
8
  Classifier: Programming Language :: Python :: 3
10
9
  Classifier: Programming Language :: Python :: 3.10
11
10
  Classifier: Programming Language :: Python :: 3.11
12
11
  Classifier: Programming Language :: Python :: 3.12
12
+ Classifier: Programming Language :: Python :: 3.13
13
13
  Requires-Dist: ccxt (>=4.2.68,<5.0.0)
14
14
  Requires-Dist: croniter (>=2.0.5,<3.0.0)
15
15
  Requires-Dist: cython (==3.0.8)
@@ -42,20 +42,25 @@ Requires-Dist: statsmodels (>=0.14.2,<0.15.0)
42
42
  Requires-Dist: tabulate (>=0.9.0,<0.10.0)
43
43
  Requires-Dist: toml (>=0.10.2,<0.11.0)
44
44
  Requires-Dist: tqdm
45
- Project-URL: Repository, https://github.com/dmarienko/Qubx
45
+ Project-URL: Repository, https://github.com/xLydianSoftware/Qubx
46
46
  Description-Content-Type: text/markdown
47
47
 
48
48
  # Qubx
49
49
 
50
+ [![CI](https://github.com/xLydianSoftware/Qubx/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/xLydianSoftware/Qubx/actions/workflows/ci.yml)
51
+
50
52
  ## Next generation of Qube quantitative backtesting framework (QUBX)
51
53
  ```
52
54
  ⠀⠀⡰⡖⠒⠒⢒⢦⠀⠀
53
55
  ⠀⢠⠃⠈⢆⣀⣎⣀⣱⡀ QUBX | Quantitative Backtesting Environment
54
56
  ⠀⢳⠒⠒⡞⠚⡄⠀⡰⠁ (c) 2024, by Dmytro Mariienko
55
57
  ⠀⠀⠱⣜⣀⣀⣈⣦⠃⠀⠀⠀
56
-
57
58
  ```
58
59
 
60
+ ## Documentation
61
+
62
+ See [Qubx Documentation](https://xlydiansoftware.github.io/Qubx/en/latest/)
63
+
59
64
  ## Installation
60
65
  > pip install qubx
61
66
 
@@ -76,7 +81,6 @@ base_currency = USDT
76
81
  ```
77
82
 
78
83
  ## Running tests
79
-
80
84
  We use `pytest` for running tests. For running unit tests execute
81
85
  ```
82
86
  just test
@@ -99,4 +103,3 @@ To run the tests simply call
99
103
  ```
100
104
  just test-integration
101
105
  ```
102
-
@@ -1,14 +1,19 @@
1
1
  # Qubx
2
2
 
3
+ [![CI](https://github.com/xLydianSoftware/Qubx/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/xLydianSoftware/Qubx/actions/workflows/ci.yml)
4
+
3
5
  ## Next generation of Qube quantitative backtesting framework (QUBX)
4
6
  ```
5
7
  ⠀⠀⡰⡖⠒⠒⢒⢦⠀⠀
6
8
  ⠀⢠⠃⠈⢆⣀⣎⣀⣱⡀ QUBX | Quantitative Backtesting Environment
7
9
  ⠀⢳⠒⠒⡞⠚⡄⠀⡰⠁ (c) 2024, by Dmytro Mariienko
8
10
  ⠀⠀⠱⣜⣀⣀⣈⣦⠃⠀⠀⠀
9
-
10
11
  ```
11
12
 
13
+ ## Documentation
14
+
15
+ See [Qubx Documentation](https://xlydiansoftware.github.io/Qubx/en/latest/)
16
+
12
17
  ## Installation
13
18
  > pip install qubx
14
19
 
@@ -29,7 +34,6 @@ base_currency = USDT
29
34
  ```
30
35
 
31
36
  ## Running tests
32
-
33
37
  We use `pytest` for running tests. For running unit tests execute
34
38
  ```
35
39
  just test
@@ -51,4 +55,4 @@ BINANCE_FUTURES_SECRET=...
51
55
  To run the tests simply call
52
56
  ```
53
57
  just test-integration
54
- ```
58
+ ```
@@ -4,18 +4,14 @@ import os
4
4
  import platform
5
5
  import shutil
6
6
  import subprocess
7
- import sysconfig
8
-
9
7
  from pathlib import Path
10
- import os
8
+
11
9
  import numpy as np
12
10
  import toml
13
- from Cython.Build import build_ext
14
- from Cython.Build import cythonize
11
+ from Cython.Build import build_ext, cythonize
15
12
  from Cython.Compiler import Options
16
13
  from Cython.Compiler.Version import version as cython_compiler_version
17
- from setuptools import Distribution
18
- from setuptools import Extension
14
+ from setuptools import Distribution, Extension
19
15
 
20
16
  RED, BLUE, GREEN, YLW, RES = "\033[31m", "\033[36m", "\033[32m", "\033[33m", "\033[0m"
21
17
 
@@ -1,14 +1,14 @@
1
1
  [tool.poetry]
2
2
  name = "Qubx"
3
- version = "0.5.6"
4
- description = "Qubx - quantitative trading framework"
3
+ version = "0.5.7"
4
+ description = "Qubx - Quantitative Trading Framework"
5
5
  authors = [
6
- "Dmitry Marienko <dmitry@gmail.com>",
7
- "Yuriy Arabskyy <yuriy.arabskyy@gmail.com>",
6
+ "Dmitry Marienko <dmitry.marienko@xlydian.com>",
7
+ "Yuriy Arabskyy <yuriy.arabskyy@xlydian.com>",
8
8
  ]
9
9
  readme = "README.md"
10
10
  packages = [{ include = "qubx", from = "src" }]
11
- repository = "https://github.com/dmarienko/Qubx"
11
+ repository = "https://github.com/xLydianSoftware/Qubx"
12
12
  include = [
13
13
  # Compiled extensions must be included in the wheel distributions
14
14
  { path = "src/**/*.so", format = "wheel" },
@@ -61,6 +61,7 @@ ipykernel = "^6.29.4"
61
61
  iprogress = "^0.4"
62
62
  click = "^8.1.7"
63
63
  ipywidgets = "^8.1.5"
64
+ ruff = "^0.9.7"
64
65
 
65
66
  [build-system]
66
67
  requires = [
@@ -79,18 +80,26 @@ generate-setup-file = false
79
80
  [tool.poetry.group.test.dependencies]
80
81
  pytest = { extras = ["lazyfixture"], version = "^8.2.0" }
81
82
  pytest-asyncio = "^0.24.0"
82
- pytest-mock = "*"
83
+ pytest-mock = "^3.12.0"
84
+ pytest-lazy-fixture = "^0.6.3"
85
+ pytest-cov = "^4.1.0"
83
86
 
84
87
  [tool.pytest.ini_options]
85
88
  asyncio_mode = "auto"
86
89
  asyncio_default_fixture_loop_scope = "function"
87
90
  pythonpath = ["src"]
88
91
  markers = ["integration: mark a test as an integration test"]
89
- addopts = "--disable-warnings -s"
92
+ addopts = "--disable-warnings"
90
93
  filterwarnings = ["ignore:.*Jupyter is migrating.*:DeprecationWarning"]
91
94
 
92
95
  [tool.ruff]
93
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
94
103
 
95
104
  [tool.ruff.lint.extend-per-file-ignores]
96
105
  "*.ipynb" = ["F405", "F401", "E701", "E402", "F403", "E401", "E702"]
@@ -32,25 +32,25 @@ if runtime_env() in ["notebook", "shell"]:
32
32
  # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
33
33
 
34
34
  # - - - - Common stuff - - - -
35
- from datetime import time, timedelta
35
+ from datetime import time, timedelta # noqa: F401
36
36
 
37
- import numpy as np
38
- import pandas as pd
37
+ import numpy as np # noqa: F401
38
+ import pandas as pd # noqa: F401
39
39
 
40
40
  # - - - - Charting stuff - - - -
41
- from matplotlib import pyplot as plt
42
- from tqdm.auto import tqdm
41
+ from matplotlib import pyplot as plt # noqa: F401
42
+ from tqdm.auto import tqdm # noqa: F401
43
43
 
44
44
  # - - - - TA stuff and indicators - - - -
45
- import qubx.pandaz.ta as pta
46
- import qubx.ta.indicators as ta
47
- from qubx.backtester.optimization import variate
45
+ import qubx.pandaz.ta as pta # noqa: F401
46
+ import qubx.ta.indicators as ta # noqa: F401
47
+ from qubx.backtester.optimization import variate # noqa: F401
48
48
 
49
49
  # - - - - Simulator stuff - - - -
50
- from qubx.backtester.simulator import simulate
50
+ from qubx.backtester.simulator import simulate # noqa: F401
51
51
 
52
52
  # - - - - Portfolio analysis - - - -
53
- from qubx.core.metrics import (
53
+ from qubx.core.metrics import ( # noqa: F401
54
54
  chart_signals,
55
55
  drop_symbols,
56
56
  get_symbol_pnls,
@@ -59,10 +59,10 @@ if runtime_env() in ["notebook", "shell"]:
59
59
  portfolio_metrics,
60
60
  tearsheet,
61
61
  )
62
- from qubx.data.helpers import loader
62
+ from qubx.data.helpers import loader # noqa: F401
63
63
 
64
64
  # - - - - Data reading - - - -
65
- from qubx.data.readers import (
65
+ from qubx.data.readers import ( # noqa: F401
66
66
  AsOhlcvSeries,
67
67
  AsPandasFrame,
68
68
  AsQuotes,
@@ -74,7 +74,7 @@ if runtime_env() in ["notebook", "shell"]:
74
74
  )
75
75
 
76
76
  # - - - - Utils - - - -
77
- from qubx.pandaz.utils import (
77
+ from qubx.pandaz.utils import ( # noqa: F401
78
78
  continuous_periods,
79
79
  drop_duplicated_indexes,
80
80
  generate_equal_date_ranges,
@@ -84,9 +84,9 @@ if runtime_env() in ["notebook", "shell"]:
84
84
  scols,
85
85
  srows,
86
86
  )
87
- from qubx.utils.charting.lookinglass import LookingGlass
88
- from qubx.utils.charting.mpl_helpers import fig, ohlc_plot, plot_trends, sbp, subplot
89
- from qubx.utils.misc import this_project_root
87
+ from qubx.utils.charting.lookinglass import LookingGlass # noqa: F401
88
+ from qubx.utils.charting.mpl_helpers import fig, ohlc_plot, plot_trends, sbp, subplot # noqa: F401
89
+ from qubx.utils.misc import this_project_root # noqa: F401
90
90
 
91
91
  # - setup short numpy output format
92
92
  np_fmt_short()
@@ -1,3 +1,5 @@
1
+ __all__ = ["BacktestsResultsManager", "variate", "simulate"]
2
+
1
3
  from .management import BacktestsResultsManager
2
4
  from .optimization import variate
3
5
  from .simulator import simulate
@@ -229,6 +229,10 @@ class SimulatedDataProvider(IDataProvider):
229
229
  """
230
230
  bars = []
231
231
 
232
+ # - if no records, return empty list to avoid exception from infer_series_frequency
233
+ if not records:
234
+ return bars
235
+
232
236
  _data_tf = infer_series_frequency([r.time for r in records[:50]])
233
237
  timeframe_ns = _data_tf.item()
234
238
 
@@ -6,7 +6,7 @@ from pathlib import Path
6
6
  import pandas as pd
7
7
  import yaml
8
8
 
9
- from qubx.core.metrics import TradingSessionResult, _pfl_metrics_prepare
9
+ from qubx.core.metrics import TradingSessionResult
10
10
  from qubx.utils.misc import blue, cyan, green, magenta, red, yellow
11
11
 
12
12
 
@@ -24,12 +24,16 @@ class BacktestsResultsManager:
24
24
 
25
25
  Methods
26
26
  -------
27
- reload()
27
+ - reload()
28
28
  Reloads all backtesting results from the specified path
29
- list(regex="", with_metrics=False)
29
+ - list(regex="", with_metrics=False)
30
30
  Lists all backtesting results, optionally filtered by regex and including metrics
31
- load(name)
31
+ - load(name)
32
32
  Loads a specific backtesting result by name
33
+ - load_config(name)
34
+ Loads the configuration YAML file for a specific backtest result
35
+ - delete(name)
36
+ Deletes one or more backtest results
33
37
  """
34
38
 
35
39
  def __init__(self, path: str):
@@ -38,6 +42,9 @@ class BacktestsResultsManager:
38
42
 
39
43
  def reload(self) -> "BacktestsResultsManager":
40
44
  self.results = {}
45
+ self.variations = {}
46
+
47
+ _vars = defaultdict(list)
41
48
  names = defaultdict(lambda: 0)
42
49
  for p in Path(self.path).glob("**/*.zip"):
43
50
  with zipfile.ZipFile(p, "r") as zip_ref:
@@ -45,6 +52,13 @@ class BacktestsResultsManager:
45
52
  info = yaml.safe_load(zip_ref.read("info.yml"))
46
53
  info["path"] = str(p)
47
54
  n = info.get("name", "")
55
+ var_set_name = info.get("variation_name", "")
56
+
57
+ # - put variations aside
58
+ if var_set_name:
59
+ _vars[var_set_name].append(info)
60
+ continue
61
+
48
62
  _new_name = n if names[n] == 0 else f"{n}.{names[n]}"
49
63
  names[n] += 1
50
64
  info["name"] = _new_name
@@ -58,6 +72,18 @@ class BacktestsResultsManager:
58
72
  self.results[n]["idx"] = _idx
59
73
  _idx += 1
60
74
 
75
+ # - reindex variations at the end
76
+ for n in sorted(_vars.keys()):
77
+ self.variations[_idx] = {
78
+ "name": n,
79
+ "idx": _idx,
80
+ "variations": _vars[n],
81
+ "created": pd.Timestamp(_vars[n][0].get("creation_time", "")).round("1s"),
82
+ "author": _vars[n][0].get("author", ""),
83
+ "description": _vars[n][0].get("description", ""),
84
+ }
85
+ _idx += 1
86
+
61
87
  return self
62
88
 
63
89
  def __getitem__(
@@ -66,25 +92,87 @@ class BacktestsResultsManager:
66
92
  return self.load(name)
67
93
 
68
94
  def load(
69
- self, name: str | int | list[int] | list[str] | slice
95
+ self, name_or_idx: str | int | list[int] | list[str] | slice
70
96
  ) -> TradingSessionResult | list[TradingSessionResult]:
71
- match name:
97
+ match name_or_idx:
72
98
  case list():
73
- return [self.load(i) for i in name]
99
+ return [self.load(i) for i in name_or_idx] # type: ignore
100
+ case str():
101
+ return [self.load(i) for i in self._find_indices(name_or_idx)] # type: ignore
74
102
  case slice():
75
- return [self.load(i) for i in range(name.start, name.stop, name.step if name.step else 1)]
103
+ return [
104
+ self.load(i)
105
+ for i in range(name_or_idx.start, name_or_idx.stop, name_or_idx.step if name_or_idx.step else 1)
106
+ ] # type: ignore
107
+ case int():
108
+ if name_or_idx > len(self.results) and name_or_idx in self.variations:
109
+ return [
110
+ TradingSessionResult.from_file(v.get("path", ""))
111
+ for v in self.variations[name_or_idx].get("variations", [])
112
+ ]
113
+
114
+ # - load by index
115
+ for info in self.results.values():
116
+ if info.get("idx", -1) == name_or_idx:
117
+ return TradingSessionResult.from_file(info["path"])
76
118
 
119
+ raise ValueError(f"No result found for '{name_or_idx}' !")
120
+
121
+ def load_config(self, name: str | int) -> str:
122
+ """Load the configuration YAML file for a specific backtest result.
123
+
124
+ Args:
125
+ name (str | int): The name or index of the backtest result. If str, matches against the backtest name.
126
+ If int, matches against the backtest index.
127
+
128
+ Returns:
129
+ str: The contents of the configuration YAML file as a string.
130
+
131
+ Raises:
132
+ ValueError: If no backtest result is found matching the provided name/index.
133
+ """
134
+ p = None
77
135
  for info in self.results.values():
78
136
  match name:
79
137
  case int():
80
138
  if info.get("idx", -1) == name:
81
- return TradingSessionResult.from_file(info["path"])
139
+ n = info.get("name", "")
140
+ p = info.get("path", {})
141
+ break
82
142
  case str():
83
143
  if info.get("name", "") == name:
84
- return TradingSessionResult.from_file(info["path"])
85
- raise ValueError(f"No result found for {name}")
144
+ n = info.get("name", "")
145
+ p = info.get("path", {})
146
+ break
147
+ if p is None:
148
+ raise ValueError(f"No result found for {name}")
149
+
150
+ # - name may have .1, .2, etc. so we need to remove it
151
+ n = n.split(".")[0] if "." in n else n
152
+ with zipfile.ZipFile(p, "r") as zip_ref:
153
+ return zip_ref.read(f"{n}.yaml").decode("utf-8")
86
154
 
87
155
  def delete(self, name: str | int | list[int] | list[str] | slice):
156
+ """Delete one or more backtest results.
157
+
158
+ Args:
159
+ name: Identifier(s) for the backtest result(s) to delete. Can be:
160
+ - str: Name of backtest or regex pattern to match multiple backtests
161
+ - int: Index of specific backtest
162
+ - list[int]: List of backtest indices
163
+ - list[str]: List of backtest names
164
+ - slice: Range of backtest indices to delete
165
+
166
+ Prints:
167
+ Message confirming which backtest(s) were deleted, or error if none found.
168
+ Deleted backtest names are shown in red text.
169
+
170
+ Note:
171
+ - For string names, supports regex pattern matching against backtest names and strategy class names
172
+ - Deletes the underlying results files and reloads the results index
173
+ - Operation is irreversible
174
+ """
175
+
88
176
  def _del_idx(idx):
89
177
  for info in self.results.values():
90
178
  if info.get("idx", -1) == idx:
@@ -134,15 +222,25 @@ class BacktestsResultsManager:
134
222
 
135
223
  try:
136
224
  if not re.match(regex, n, re.IGNORECASE):
137
- if not re.match(regex, s_cls, re.IGNORECASE):
138
- continue
225
+ # if not re.match(regex, s_cls, re.IGNORECASE):
226
+ continue
139
227
  except Exception:
140
228
  if regex.lower() != n.lower() and regex.lower() != s_cls.lower():
141
229
  continue
142
230
 
143
231
  yield info.get("idx", -1)
144
232
 
145
- def list(self, regex: str = "", with_metrics=True, params=False, as_table=False):
233
+ def list(
234
+ self,
235
+ regex: str = "",
236
+ with_metrics=True,
237
+ params=False,
238
+ as_table=False,
239
+ pretty_print=False,
240
+ sort_by: str | None = "sharpe",
241
+ ascending=False,
242
+ show_variations=True,
243
+ ):
146
244
  """List backtesting results with optional filtering and formatting.
147
245
 
148
246
  Args:
@@ -162,8 +260,8 @@ class BacktestsResultsManager:
162
260
 
163
261
  if regex:
164
262
  if not re.match(regex, n, re.IGNORECASE):
165
- if not re.match(regex, s_cls, re.IGNORECASE):
166
- continue
263
+ # if not re.match(regex, s_cls, re.IGNORECASE):
264
+ continue
167
265
 
168
266
  name = info.get("name", "")
169
267
  smbs = ", ".join(info.get("symbols", list()))
@@ -180,7 +278,7 @@ class BacktestsResultsManager:
180
278
  dscr = dscr.split("\n")
181
279
  for _d in dscr:
182
280
  _s += f"\n\t{magenta('# ' + _d)}"
183
- _one_line_dscr += " " + _d
281
+ _one_line_dscr += "\u25cf " + _d + "\n"
184
282
 
185
283
  _s += f"\n\tstrategy: {green(s_cls)}"
186
284
  _s += f"\n\tinterval: {blue(start)} - {blue(stop)}"
@@ -232,6 +330,49 @@ class BacktestsResultsManager:
232
330
  },
233
331
  )
234
332
 
333
+ # - variations (only if not as_table for the time being)
334
+ if not as_table and show_variations:
335
+ for _i, vi in self.variations.items():
336
+ n = vi.get("name", "")
337
+ if regex:
338
+ if not re.match(regex, n, re.IGNORECASE):
339
+ continue
340
+
341
+ _s = f"{yellow(str(_i))} - {red(str(n))} set of {len(vi.get('variations'))} variations ::: {magenta(vi.get('created'))} by {cyan(vi.get('author'))}"
342
+
343
+ dscr = vi.get("description", "").split("\n")
344
+ for _d in dscr:
345
+ _s += f"\n\t{magenta('# ' + _d)}"
346
+
347
+ _mtrx = {}
348
+ for v in vi.get("variations", []):
349
+ _nm = v.get("name", "")
350
+ _nm = _nm.split("_")[-1].strip("()")
351
+ _mtrx[_nm] = v.get("performance", {})
352
+
353
+ _m_repr = pd.DataFrame.from_dict(_mtrx, orient="index")[
354
+ ["gain", "cagr", "sharpe", "qr", "max_dd_pct", "mdd_usd", "fees", "execs"]
355
+ ].astype(float)
356
+ _m_repr = _m_repr.round(3)
357
+ _m_repr = _m_repr.sort_values(by=sort_by, ascending=ascending) if sort_by else _m_repr
358
+ _m_repr = _m_repr.to_string(index=True)
359
+
360
+ print(_s)
361
+ for _i, _l in enumerate(_m_repr.split("\n")):
362
+ if _i == 0:
363
+ print("\t " + red(_l))
364
+ else:
365
+ print("\t " + blue(_l))
366
+
235
367
  if as_table:
236
368
  _df = pd.DataFrame.from_records(_t_rep, index="Index")
237
- return _df.sort_values(by="Created", ascending=False)
369
+ _df = _df.sort_values(by=sort_by, ascending=ascending) if sort_by else _df
370
+ if pretty_print:
371
+ from IPython.display import HTML
372
+
373
+ return HTML(
374
+ _df.to_html()
375
+ .replace("\\n", "<br><hr style='border-color: #005000; '/>")
376
+ .replace("<td>", '<td align="left" valign="top">')
377
+ )
378
+ return _df
@@ -20,7 +20,7 @@ from qubx.core.exceptions import (
20
20
  ExchangeError,
21
21
  InvalidOrder,
22
22
  )
23
- from qubx.core.series import Quote, Trade
23
+ from qubx.core.series import Quote
24
24
 
25
25
 
26
26
  @dataclass
@@ -275,7 +275,7 @@ class OrdersManagementEngine:
275
275
  _s = f"= = ({np.datetime64(timestamp, 'ns')}) = =\n"
276
276
  for k, v in reversed(self.asks.items()):
277
277
  _sizes = ",".join([f"{self.active_orders[o].quantity}" for o in v])
278
- _s += f" {k} : [{ _sizes }]\n"
278
+ _s += f" {k} : [{_sizes}]\n"
279
279
  if k == self.bbo.ask:
280
280
  _a = False
281
281
 
@@ -286,7 +286,7 @@ class OrdersManagementEngine:
286
286
  _s1 = ""
287
287
  for k, v in self.bids.items():
288
288
  _sizes = ",".join([f"{self.active_orders[o].quantity}" for o in v])
289
- _s1 += f" {k} : [{ _sizes }]\n"
289
+ _s1 += f" {k} : [{_sizes}]\n"
290
290
  if k == self.bbo.bid:
291
291
  _b = False
292
292
  _s1 += "= = = = = = = = = = = = = = = = = = = =\n"
@@ -117,7 +117,10 @@ def dicts_product(d1: dict, d2: dict) -> dict:
117
117
  }
118
118
 
119
119
  """
120
- flatten = lambda l: [item for sublist in l for item in (sublist if isinstance(sublist, list) else [sublist])] # noqa: E731
120
+
121
+ def flatten(lst):
122
+ return [item for sublist in lst for item in (sublist if isinstance(sublist, list) else [sublist])]
123
+
121
124
  return {(a + " + " + b): flatten([d1[a], d2[b]]) for a, b in product(d1.keys(), d2.keys())}
122
125
 
123
126
 
@@ -192,7 +195,7 @@ def variate(clz: Type[Any] | list[Type[Any]], *args, conditions=None, **kwargs)
192
195
 
193
196
  return _dict(
194
197
  {
195
- f"{sfx}_({ ','.join(dic2str(z)) })": _mk(clz, *args, **z)
198
+ f"{sfx}_({','.join(dic2str(z))})": _mk(clz, *args, **z)
196
199
  for z in permutate_params(kwargs, conditions=conditions)
197
200
  }
198
201
  )
@@ -5,7 +5,7 @@ from typing import Any, Iterator, TypeAlias
5
5
  import pandas as pd
6
6
 
7
7
  from qubx import logger
8
- from qubx.core.basics import DataType, Instrument, Timestamped, dt_64
8
+ from qubx.core.basics import DataType, Instrument, Timestamped
9
9
  from qubx.core.exceptions import SimulationError
10
10
  from qubx.data.readers import (
11
11
  AsDict,
@@ -13,7 +13,6 @@ from qubx import logger
13
13
  from qubx.core.account import BasicAccountProcessor
14
14
  from qubx.core.basics import (
15
15
  CtrlChannel,
16
- DataType,
17
16
  Deal,
18
17
  Instrument,
19
18
  ITimeProvider,
@@ -108,9 +108,11 @@ class CcxtBroker(IBroker):
108
108
  order = orders[order_id]
109
109
  try:
110
110
  logger.info(f"Canceling order {order_id} ...")
111
- r = self._loop.submit(
111
+ result = self._loop.submit(
112
112
  self._exchange.cancel_order(order_id, symbol=instrument_to_ccxt_symbol(order.instrument))
113
113
  ).result()
114
+ logger.debug(f"Cancel order result: {result}")
115
+ return order
114
116
  except Exception as err:
115
117
  logger.error(f"Canceling [{order}] exception : {err}")
116
118
  logger.error(traceback.format_exc())
@@ -2,7 +2,6 @@ import asyncio
2
2
  from threading import Thread
3
3
  from typing import Any
4
4
 
5
- import ccxt
6
5
  import ccxt.pro as cxp
7
6
 
8
7
  from .customizations import BinancePortfolioMargin, BinanceQV, BinanceQVUSDM
@@ -1,35 +1,29 @@
1
- import asyncio
2
1
  import re
3
- from typing import Any, Dict, List, Optional, Tuple
2
+ from typing import Any, Dict, List
4
3
 
5
4
  import numpy as np
6
5
  import pandas as pd
7
6
 
8
- import ccxt
9
7
  import ccxt.pro as cxp
10
8
  from ccxt import BadSymbol
11
- from qubx import logger, lookup
9
+ from qubx import logger
12
10
  from qubx.core.basics import (
13
11
  AssetBalance,
14
- AssetType,
15
12
  Deal,
16
13
  FundingRate,
17
14
  Instrument,
18
15
  Liquidation,
19
- MarketType,
20
16
  Order,
21
17
  Position,
22
18
  )
23
- from qubx.core.series import Bar, OrderBook, Quote, TimeSeries, Trade, time_as_nsec
19
+ from qubx.core.series import OrderBook, Quote, Trade, time_as_nsec
24
20
  from qubx.utils.marketdata.ccxt import (
25
- ccxt_build_qubx_exchange_name,
26
21
  ccxt_symbol_to_instrument,
27
22
  )
28
23
  from qubx.utils.orderbook import build_orderbook_snapshots
29
24
 
30
25
  from .exceptions import (
31
26
  CcxtLiquidationParsingError,
32
- CcxtOrderBookParsingError,
33
27
  CcxtSymbolNotRecognized,
34
28
  )
35
29
 
@@ -137,7 +131,7 @@ def ccxt_restore_position_from_deals(
137
131
 
138
132
  def ccxt_convert_trade(trade: dict[str, Any]) -> Trade:
139
133
  t_ns = trade["timestamp"] * 1_000_000 # this is trade time
140
- s, info, price, amnt = trade["symbol"], trade["info"], trade["price"], trade["amount"]
134
+ info, price, amnt = trade["info"], trade["price"], trade["amount"]
141
135
  m = info["m"]
142
136
  return Trade(t_ns, price, amnt, int(not m), int(trade["id"]))
143
137