Qubx 0.5.0__tar.gz → 0.5.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.0 → qubx-0.5.1}/PKG-INFO +3 -1
- {qubx-0.5.0 → qubx-0.5.1}/pyproject.toml +22 -14
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/__init__.py +10 -6
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/_nb_magic.py +23 -23
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/backtester/account.py +14 -2
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/backtester/broker.py +5 -0
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/backtester/data.py +3 -0
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/backtester/ome.py +5 -8
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/backtester/optimization.py +5 -5
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/backtester/simulated_data.py +12 -13
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/backtester/simulator.py +5 -3
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/connectors/ccxt/broker.py +10 -4
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/connectors/ccxt/data.py +11 -0
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/core/basics.py +38 -115
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/core/context.py +8 -1
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/core/helpers.py +5 -3
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/core/interfaces.py +35 -2
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/core/loggers.py +1 -0
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/core/metrics.py +248 -13
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/core/mixins/processing.py +45 -53
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/core/mixins/subscription.py +7 -4
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/core/mixins/trading.py +3 -0
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/data/helpers.py +1 -1
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/data/readers.py +8 -4
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/pandaz/utils.py +3 -0
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/trackers/composite.py +5 -5
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/trackers/rebalancers.py +13 -27
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/trackers/riskctrl.py +10 -9
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/trackers/sizers.py +4 -7
- qubx-0.5.1/src/qubx/utils/_jupyter_runner.pyt +59 -0
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/utils/misc.py +8 -0
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/utils/runner.py +198 -140
- qubx-0.5.0/src/qubx/utils/helpers.py +0 -14
- {qubx-0.5.0 → qubx-0.5.1}/README.md +0 -0
- {qubx-0.5.0 → qubx-0.5.1}/build.py +0 -0
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/backtester/__init__.py +0 -0
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/backtester/utils.py +0 -0
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/connectors/ccxt/__init__.py +0 -0
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/connectors/ccxt/account.py +0 -0
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/connectors/ccxt/ccxt_connector.py +0 -0
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/connectors/ccxt/customizations.py +0 -0
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/connectors/ccxt/exceptions.py +0 -0
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/connectors/ccxt/factory.py +0 -0
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/connectors/ccxt/utils.py +0 -0
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/core/__init__.py +0 -0
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/core/account.py +0 -0
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/core/exceptions.py +0 -0
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/core/lookups.py +0 -0
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/core/mixins/__init__.py +0 -0
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/core/mixins/market.py +0 -0
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/core/mixins/universe.py +0 -0
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/core/series.pxd +0 -0
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/core/series.pyi +0 -0
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/core/series.pyx +0 -0
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/core/utils.pyi +0 -0
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/core/utils.pyx +0 -0
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/data/__init__.py +0 -0
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/data/tardis.py +0 -0
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/gathering/simplest.py +0 -0
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/math/__init__.py +0 -0
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/math/stats.py +0 -0
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/pandaz/__init__.py +0 -0
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/pandaz/ta.py +0 -0
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/plotting/__init__.py +0 -0
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/plotting/dashboard.py +0 -0
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/plotting/data.py +0 -0
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/plotting/interfaces.py +0 -0
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/plotting/renderers/__init__.py +0 -0
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/plotting/renderers/plotly.py +0 -0
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/ta/__init__.py +0 -0
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/ta/indicators.pxd +0 -0
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/ta/indicators.pyi +0 -0
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/ta/indicators.pyx +0 -0
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/trackers/__init__.py +0 -0
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/utils/__init__.py +0 -0
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/utils/_pyxreloader.py +0 -0
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/utils/charting/lookinglass.py +0 -0
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/utils/charting/mpl_helpers.py +0 -0
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/utils/marketdata/binance.py +0 -0
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/utils/marketdata/ccxt.py +0 -0
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/utils/marketdata/dukas.py +0 -0
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/utils/ntp.py +0 -0
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/utils/numbers_utils.py +0 -0
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/utils/orderbook.py +0 -0
- {qubx-0.5.0 → qubx-0.5.1}/src/qubx/utils/time.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: Qubx
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.1
|
|
4
4
|
Summary: Qubx - quantitative trading framework
|
|
5
5
|
Home-page: https://github.com/dmarienko/Qubx
|
|
6
6
|
Author: Dmitry Marienko
|
|
@@ -16,6 +16,7 @@ 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
18
|
Requires-Dist: importlib-metadata
|
|
19
|
+
Requires-Dist: jupyter-console (>=6.6.3,<7.0.0)
|
|
19
20
|
Requires-Dist: loguru (>=0.7.2,<0.8.0)
|
|
20
21
|
Requires-Dist: matplotlib (>=3.8.4,<4.0.0)
|
|
21
22
|
Requires-Dist: msgspec (>=0.18.6,<0.19.0)
|
|
@@ -38,6 +39,7 @@ Requires-Dist: scipy (>=1.12.0,<2.0.0)
|
|
|
38
39
|
Requires-Dist: sortedcontainers (>=2.4.0,<3.0.0)
|
|
39
40
|
Requires-Dist: stackprinter (>=0.2.10,<0.3.0)
|
|
40
41
|
Requires-Dist: statsmodels (>=0.14.2,<0.15.0)
|
|
42
|
+
Requires-Dist: tabulate (>=0.9.0,<0.10.0)
|
|
41
43
|
Requires-Dist: tqdm
|
|
42
44
|
Project-URL: Repository, https://github.com/dmarienko/Qubx
|
|
43
45
|
Description-Content-Type: text/markdown
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "Qubx"
|
|
3
|
-
version = "0.5.
|
|
3
|
+
version = "0.5.1"
|
|
4
4
|
description = "Qubx - quantitative trading framework"
|
|
5
|
-
authors = [
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
{ include = "qubx", from = "src" },
|
|
5
|
+
authors = [
|
|
6
|
+
"Dmitry Marienko <dmitry@gmail.com>",
|
|
7
|
+
"Yuriy Arabskyy <yuriy.arabskyy@gmail.com>",
|
|
9
8
|
]
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
packages = [{ include = "qubx", from = "src" }]
|
|
10
11
|
repository = "https://github.com/dmarienko/Qubx"
|
|
11
12
|
include = [
|
|
12
13
|
# Compiled extensions must be included in the wheel distributions
|
|
@@ -45,6 +46,8 @@ msgspec = "^0.18.6"
|
|
|
45
46
|
pyyaml = "^6.0.2"
|
|
46
47
|
dash = "^2.18.2"
|
|
47
48
|
dash-bootstrap-components = "^1.6.0"
|
|
49
|
+
tabulate = "^0.9.0"
|
|
50
|
+
jupyter-console = "^6.6.3"
|
|
48
51
|
|
|
49
52
|
[tool.poetry.group.dev.dependencies]
|
|
50
53
|
pre-commit = "^2.20.0"
|
|
@@ -59,7 +62,13 @@ click = "^8.1.7"
|
|
|
59
62
|
ipywidgets = "^8.1.5"
|
|
60
63
|
|
|
61
64
|
[build-system]
|
|
62
|
-
requires = [
|
|
65
|
+
requires = [
|
|
66
|
+
"poetry-core",
|
|
67
|
+
"setuptools",
|
|
68
|
+
"numpy>=1.26.3",
|
|
69
|
+
"cython==3.0.8",
|
|
70
|
+
"toml>=0.10.2",
|
|
71
|
+
]
|
|
63
72
|
build-backend = "poetry.core.masonry.api"
|
|
64
73
|
|
|
65
74
|
[tool.poetry.build]
|
|
@@ -67,7 +76,7 @@ script = "build.py"
|
|
|
67
76
|
generate-setup-file = false
|
|
68
77
|
|
|
69
78
|
[tool.poetry.group.test.dependencies]
|
|
70
|
-
pytest = {extras = ["lazyfixture"], version = "^8.2.0"}
|
|
79
|
+
pytest = { extras = ["lazyfixture"], version = "^8.2.0" }
|
|
71
80
|
pytest-asyncio = "^0.24.0"
|
|
72
81
|
pytest-mock = "*"
|
|
73
82
|
|
|
@@ -75,16 +84,15 @@ pytest-mock = "*"
|
|
|
75
84
|
asyncio_mode = "auto"
|
|
76
85
|
asyncio_default_fixture_loop_scope = "function"
|
|
77
86
|
pythonpath = ["src"]
|
|
78
|
-
markers = [
|
|
79
|
-
"integration: mark a test as an integration test",
|
|
80
|
-
]
|
|
87
|
+
markers = ["integration: mark a test as an integration test"]
|
|
81
88
|
addopts = "--disable-warnings -s"
|
|
82
|
-
filterwarnings = [
|
|
83
|
-
"ignore:.*Jupyter is migrating.*:DeprecationWarning",
|
|
84
|
-
]
|
|
89
|
+
filterwarnings = ["ignore:.*Jupyter is migrating.*:DeprecationWarning"]
|
|
85
90
|
|
|
86
91
|
[tool.ruff]
|
|
87
92
|
line-length = 120
|
|
88
93
|
|
|
89
94
|
[tool.ruff.lint.extend-per-file-ignores]
|
|
90
|
-
"*.ipynb" = ["F405", "F401", "E701", "E402", "F403", "E401", "E702"]
|
|
95
|
+
"*.ipynb" = ["F405", "F401", "E701", "E402", "F403", "E401", "E702"]
|
|
96
|
+
|
|
97
|
+
[tool.poetry.scripts]
|
|
98
|
+
qubx = "qubx.utils.runner:main"
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
1
3
|
from typing import Callable
|
|
2
|
-
from qubx.utils import set_mpl_theme, runtime_env
|
|
3
|
-
from qubx.utils.misc import install_pyx_recompiler_for_dev
|
|
4
4
|
|
|
5
|
+
import stackprinter
|
|
5
6
|
from loguru import logger
|
|
6
|
-
|
|
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
|
|
8
11
|
|
|
9
12
|
# - TODO: import some main methods from packages
|
|
10
13
|
|
|
@@ -66,8 +69,8 @@ lookup = GlobalLookup(InstrumentsLookup(), FeesLookup())
|
|
|
66
69
|
|
|
67
70
|
# registering magic for jupyter notebook
|
|
68
71
|
if runtime_env() in ["notebook", "shell"]:
|
|
69
|
-
from IPython.core.magic import Magics, magics_class, line_magic, line_cell_magic
|
|
70
72
|
from IPython.core.getipython import get_ipython
|
|
73
|
+
from IPython.core.magic import Magics, line_cell_magic, line_magic, magics_class
|
|
71
74
|
|
|
72
75
|
@magics_class
|
|
73
76
|
class QubxMagics(Magics):
|
|
@@ -136,7 +139,8 @@ if runtime_env() in ["notebook", "shell"]:
|
|
|
136
139
|
|
|
137
140
|
"""
|
|
138
141
|
import multiprocessing as m
|
|
139
|
-
import
|
|
142
|
+
import re
|
|
143
|
+
import time
|
|
140
144
|
|
|
141
145
|
# create ext args
|
|
142
146
|
name = None
|
|
@@ -151,7 +155,7 @@ if runtime_env() in ["notebook", "shell"]:
|
|
|
151
155
|
return
|
|
152
156
|
|
|
153
157
|
ipy = get_ipython()
|
|
154
|
-
for a in [x for x in re.split("[\ ,;]", line.strip()) if x]:
|
|
158
|
+
for a in [x for x in re.split(r"[\ ,;]", line.strip()) if x]:
|
|
155
159
|
ipy.push({a: self._get_manager().Value(None, None)})
|
|
156
160
|
|
|
157
161
|
# code to run
|
|
@@ -32,60 +32,60 @@ if runtime_env() in ["notebook", "shell"]:
|
|
|
32
32
|
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
33
33
|
|
|
34
34
|
# - - - - Common stuff - - - -
|
|
35
|
+
from datetime import time, timedelta
|
|
36
|
+
|
|
35
37
|
import numpy as np
|
|
36
38
|
import pandas as pd
|
|
37
|
-
|
|
39
|
+
|
|
40
|
+
# - - - - Charting stuff - - - -
|
|
41
|
+
from matplotlib import pyplot as plt
|
|
38
42
|
from tqdm.auto import tqdm
|
|
39
43
|
|
|
40
44
|
# - - - - TA stuff and indicators - - - -
|
|
41
45
|
import qubx.pandaz.ta as pta
|
|
42
46
|
import qubx.ta.indicators as ta
|
|
47
|
+
from qubx.backtester.optimization import variate
|
|
48
|
+
|
|
49
|
+
# - - - - Simulator stuff - - - -
|
|
50
|
+
from qubx.backtester.simulator import simulate
|
|
43
51
|
|
|
44
52
|
# - - - - Portfolio analysis - - - -
|
|
45
53
|
from qubx.core.metrics import (
|
|
46
|
-
tearsheet,
|
|
47
54
|
chart_signals,
|
|
48
|
-
get_symbol_pnls,
|
|
49
|
-
get_equity,
|
|
50
|
-
portfolio_metrics,
|
|
51
|
-
pnl,
|
|
52
55
|
drop_symbols,
|
|
56
|
+
get_symbol_pnls,
|
|
53
57
|
pick_symbols,
|
|
58
|
+
pnl,
|
|
59
|
+
portfolio_metrics,
|
|
60
|
+
tearsheet,
|
|
54
61
|
)
|
|
62
|
+
from qubx.data.helpers import loader
|
|
55
63
|
|
|
56
64
|
# - - - - Data reading - - - -
|
|
57
65
|
from qubx.data.readers import (
|
|
58
|
-
CsvStorageDataReader,
|
|
59
|
-
MultiQdbConnector,
|
|
60
|
-
QuestDBConnector,
|
|
61
66
|
AsOhlcvSeries,
|
|
62
67
|
AsPandasFrame,
|
|
63
68
|
AsQuotes,
|
|
64
69
|
AsTimestampedRecords,
|
|
70
|
+
CsvStorageDataReader,
|
|
71
|
+
MultiQdbConnector,
|
|
72
|
+
QuestDBConnector,
|
|
65
73
|
RestoreTicksFromOHLC,
|
|
66
74
|
)
|
|
67
|
-
from qubx.data.helpers import loader
|
|
68
|
-
|
|
69
|
-
# - - - - Simulator stuff - - - -
|
|
70
|
-
from qubx.backtester.simulator import simulate
|
|
71
|
-
from qubx.backtester.optimization import variate
|
|
72
|
-
|
|
73
|
-
# - - - - Charting stuff - - - -
|
|
74
|
-
from matplotlib import pyplot as plt
|
|
75
|
-
from qubx.utils.charting.mpl_helpers import fig, subplot, sbp, plot_trends, ohlc_plot
|
|
76
|
-
from qubx.utils.charting.lookinglass import LookingGlass
|
|
77
75
|
|
|
78
76
|
# - - - - Utils - - - -
|
|
79
77
|
from qubx.pandaz.utils import (
|
|
80
|
-
scols,
|
|
81
|
-
srows,
|
|
82
|
-
ohlc_resample,
|
|
83
78
|
continuous_periods,
|
|
84
|
-
generate_equal_date_ranges,
|
|
85
79
|
drop_duplicated_indexes,
|
|
80
|
+
generate_equal_date_ranges,
|
|
81
|
+
ohlc_resample,
|
|
86
82
|
retain_columns_and_join,
|
|
87
83
|
rolling_forward_test_split,
|
|
84
|
+
scols,
|
|
85
|
+
srows,
|
|
88
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
89
|
|
|
90
90
|
# - setup short numpy output format
|
|
91
91
|
np_fmt_short()
|
|
@@ -3,15 +3,17 @@ from qubx.backtester.ome import OrdersManagementEngine
|
|
|
3
3
|
from qubx.core.account import BasicAccountProcessor
|
|
4
4
|
from qubx.core.basics import (
|
|
5
5
|
ZERO_COSTS,
|
|
6
|
+
BatchEvent,
|
|
6
7
|
CtrlChannel,
|
|
7
8
|
Instrument,
|
|
8
9
|
Order,
|
|
9
10
|
Position,
|
|
11
|
+
Timestamped,
|
|
10
12
|
TransactionCostsCalculator,
|
|
11
13
|
dt_64,
|
|
12
14
|
)
|
|
13
15
|
from qubx.core.interfaces import ITimeProvider
|
|
14
|
-
from qubx.core.series import Bar, Quote, Trade
|
|
16
|
+
from qubx.core.series import Bar, OrderBook, Quote, Trade
|
|
15
17
|
|
|
16
18
|
|
|
17
19
|
class SimulatedAccountProcessor(BasicAccountProcessor):
|
|
@@ -101,7 +103,7 @@ class SimulatedAccountProcessor(BasicAccountProcessor):
|
|
|
101
103
|
return super().process_order(order, update_locked_value)
|
|
102
104
|
|
|
103
105
|
def emulate_quote_from_data(
|
|
104
|
-
self, instrument: Instrument, timestamp: dt_64, data: float |
|
|
106
|
+
self, instrument: Instrument, timestamp: dt_64, data: float | Timestamped | BatchEvent
|
|
105
107
|
) -> Quote | None:
|
|
106
108
|
if instrument not in self._half_tick_size:
|
|
107
109
|
_ = self.get_position(instrument)
|
|
@@ -109,15 +111,25 @@ class SimulatedAccountProcessor(BasicAccountProcessor):
|
|
|
109
111
|
_ts2 = self._half_tick_size[instrument]
|
|
110
112
|
if isinstance(data, Quote):
|
|
111
113
|
return data
|
|
114
|
+
|
|
112
115
|
elif isinstance(data, Trade):
|
|
113
116
|
if data.taker: # type: ignore
|
|
114
117
|
return Quote(timestamp, data.price - _ts2 * 2, data.price, 0, 0) # type: ignore
|
|
115
118
|
else:
|
|
116
119
|
return Quote(timestamp, data.price, data.price + _ts2 * 2, 0, 0) # type: ignore
|
|
120
|
+
|
|
117
121
|
elif isinstance(data, Bar):
|
|
118
122
|
return Quote(timestamp, data.close - _ts2, data.close + _ts2, 0, 0) # type: ignore
|
|
123
|
+
|
|
124
|
+
elif isinstance(data, OrderBook):
|
|
125
|
+
return data.to_quote()
|
|
126
|
+
|
|
127
|
+
elif isinstance(data, BatchEvent):
|
|
128
|
+
return self.emulate_quote_from_data(instrument, timestamp, data.data[-1])
|
|
129
|
+
|
|
119
130
|
elif isinstance(data, float):
|
|
120
131
|
return Quote(timestamp, data - _ts2, data + _ts2, 0, 0)
|
|
132
|
+
|
|
121
133
|
else:
|
|
122
134
|
return None
|
|
123
135
|
|
|
@@ -18,9 +18,11 @@ class SimulatedBroker(IBroker):
|
|
|
18
18
|
self,
|
|
19
19
|
channel: CtrlChannel,
|
|
20
20
|
account: SimulatedAccountProcessor,
|
|
21
|
+
exchange_id: str = "simulated",
|
|
21
22
|
) -> None:
|
|
22
23
|
self.channel = channel
|
|
23
24
|
self._account = account
|
|
25
|
+
self._exchange_id = exchange_id
|
|
24
26
|
|
|
25
27
|
@property
|
|
26
28
|
def is_simulated_trading(self) -> bool:
|
|
@@ -80,3 +82,6 @@ class SimulatedBroker(IBroker):
|
|
|
80
82
|
self.channel.send((instrument, "order", report.order, False))
|
|
81
83
|
if report.exec is not None:
|
|
82
84
|
self.channel.send((instrument, "deals", [report.exec], False))
|
|
85
|
+
|
|
86
|
+
def exchange(self) -> str:
|
|
87
|
+
return self._exchange_id.upper()
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
from typing import List, Dict
|
|
2
1
|
from dataclasses import dataclass
|
|
3
2
|
from operator import neg
|
|
3
|
+
from typing import Dict, List
|
|
4
4
|
|
|
5
5
|
import numpy as np
|
|
6
6
|
from sortedcontainers import SortedDict
|
|
7
7
|
|
|
8
8
|
from qubx import logger
|
|
9
9
|
from qubx.core.basics import (
|
|
10
|
+
OPTION_FILL_AT_SIGNAL_PRICE,
|
|
10
11
|
Deal,
|
|
11
12
|
Instrument,
|
|
13
|
+
ITimeProvider,
|
|
12
14
|
Order,
|
|
13
15
|
OrderSide,
|
|
14
16
|
OrderType,
|
|
@@ -16,14 +18,12 @@ from qubx.core.basics import (
|
|
|
16
18
|
Signal,
|
|
17
19
|
TransactionCostsCalculator,
|
|
18
20
|
dt_64,
|
|
19
|
-
ITimeProvider,
|
|
20
|
-
OPTION_FILL_AT_SIGNAL_PRICE,
|
|
21
21
|
)
|
|
22
|
-
from qubx.core.series import Quote, Trade
|
|
23
22
|
from qubx.core.exceptions import (
|
|
24
23
|
ExchangeError,
|
|
25
24
|
InvalidOrder,
|
|
26
25
|
)
|
|
26
|
+
from qubx.core.series import Quote, Trade
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
@dataclass
|
|
@@ -127,11 +127,8 @@ class OrdersManagementEngine:
|
|
|
127
127
|
time_in_force: str = "gtc",
|
|
128
128
|
**options,
|
|
129
129
|
) -> OmeReport:
|
|
130
|
-
|
|
131
130
|
if self.bbo is None:
|
|
132
|
-
raise ExchangeError(
|
|
133
|
-
f"Simulator is not ready for order management - no any quote for {self.instrument.symbol}"
|
|
134
|
-
)
|
|
131
|
+
raise ExchangeError(f"Simulator is not ready for order management - no quote for {self.instrument.symbol}")
|
|
135
132
|
|
|
136
133
|
# - validate order parameters
|
|
137
134
|
self._validate_order(order_side, order_type, amount, price, time_in_force)
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
from typing import Any, Dict, List, Sequence, Tuple, Type
|
|
2
|
-
import numpy as np
|
|
3
1
|
import re
|
|
4
|
-
|
|
5
|
-
from types import FunctionType
|
|
6
2
|
from itertools import product
|
|
3
|
+
from types import FunctionType
|
|
4
|
+
from typing import Any, Dict, List, Sequence, Tuple, Type
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
def _wrap_single_list(param_grid: List | Dict) -> Dict[str, Any] | List:
|
|
@@ -167,7 +167,7 @@ def variate(clz: Type[Any] | List[Type[Any]], *args, conditions=None, **kwargs)
|
|
|
167
167
|
"""
|
|
168
168
|
|
|
169
169
|
def _cmprss(xs: str):
|
|
170
|
-
return "".join([x[0] for x in re.split("((?<!-)(?=[A-Z]))|_|(\d)", xs) if x])
|
|
170
|
+
return "".join([x[0] for x in re.split(r"((?<!-)(?=[A-Z]))|_|(\d)", xs) if x])
|
|
171
171
|
|
|
172
172
|
if isinstance(clz, type):
|
|
173
173
|
sfx = _cmprss(clz.__name__)
|
|
@@ -5,8 +5,7 @@ from typing import Any, Iterable, Iterator, TypeAlias
|
|
|
5
5
|
import pandas as pd
|
|
6
6
|
|
|
7
7
|
from qubx import logger
|
|
8
|
-
from qubx.core.basics import BatchEvent, DataType, Instrument,
|
|
9
|
-
from qubx.core.series import Bar, OrderBook, Quote, Trade
|
|
8
|
+
from qubx.core.basics import BatchEvent, DataType, Instrument, Timestamped, dt_64
|
|
10
9
|
from qubx.data.readers import (
|
|
11
10
|
AsDict,
|
|
12
11
|
AsQuotes,
|
|
@@ -18,8 +17,7 @@ from qubx.data.readers import (
|
|
|
18
17
|
RestoreTradesFromOHLC,
|
|
19
18
|
)
|
|
20
19
|
|
|
21
|
-
|
|
22
|
-
SlicerOutData: TypeAlias = tuple[str, int, InData] | tuple
|
|
20
|
+
SlicerOutData: TypeAlias = tuple[str, int, Timestamped] | tuple
|
|
23
21
|
|
|
24
22
|
|
|
25
23
|
class IteratedDataStreamsSlicer(Iterator[SlicerOutData]):
|
|
@@ -29,8 +27,8 @@ class IteratedDataStreamsSlicer(Iterator[SlicerOutData]):
|
|
|
29
27
|
It supports adding / removing new data streams to the slicer on the fly (during the itration).
|
|
30
28
|
"""
|
|
31
29
|
|
|
32
|
-
_iterators: dict[str, Iterator[list[
|
|
33
|
-
_buffers: dict[str, list[
|
|
30
|
+
_iterators: dict[str, Iterator[list[Timestamped]]]
|
|
31
|
+
_buffers: dict[str, list[Timestamped]]
|
|
34
32
|
_keys: deque[str]
|
|
35
33
|
_iterating: bool
|
|
36
34
|
|
|
@@ -40,7 +38,7 @@ class IteratedDataStreamsSlicer(Iterator[SlicerOutData]):
|
|
|
40
38
|
self._keys = deque()
|
|
41
39
|
self._iterating = False
|
|
42
40
|
|
|
43
|
-
def put(self, data: dict[str, Iterator[list[
|
|
41
|
+
def put(self, data: dict[str, Iterator[list[Timestamped]]]):
|
|
44
42
|
_rebuild = False
|
|
45
43
|
for k, vi in data.items():
|
|
46
44
|
if k not in self._keys:
|
|
@@ -85,10 +83,10 @@ class IteratedDataStreamsSlicer(Iterator[SlicerOutData]):
|
|
|
85
83
|
_init_seq = dict(sorted(_init_seq.items(), key=lambda item: item[1]))
|
|
86
84
|
self._keys = deque(_init_seq.keys())
|
|
87
85
|
|
|
88
|
-
def _load_next_chunk_to_buffer(self, index: str) -> list[
|
|
86
|
+
def _load_next_chunk_to_buffer(self, index: str) -> list[Timestamped]:
|
|
89
87
|
return list(reversed(next(self._iterators[index])))
|
|
90
88
|
|
|
91
|
-
def _pop_top(self, k: str) ->
|
|
89
|
+
def _pop_top(self, k: str) -> Timestamped:
|
|
92
90
|
v = (data := self._buffers[k]).pop()
|
|
93
91
|
if not data:
|
|
94
92
|
try:
|
|
@@ -226,8 +224,6 @@ class DataFetcher:
|
|
|
226
224
|
_requests = self._specs if not to_load else set(self._make_request_id(i) for i in to_load)
|
|
227
225
|
_r_iters = {}
|
|
228
226
|
|
|
229
|
-
# logger.debug(f"{self._fetcher_id} loading {_requests}")
|
|
230
|
-
|
|
231
227
|
for _r in _requests: # - TODO: replace this loop with multi-instrument request after DataReader refactoring
|
|
232
228
|
if _r in self._specs:
|
|
233
229
|
_start = pd.Timestamp(start)
|
|
@@ -247,7 +243,10 @@ class DataFetcher:
|
|
|
247
243
|
if self._timeframe:
|
|
248
244
|
_args["timeframe"] = self._timeframe
|
|
249
245
|
|
|
250
|
-
|
|
246
|
+
try:
|
|
247
|
+
_r_iters[self._fetcher_id + "." + _r] = self._reader.read(**_args) # type: ignore
|
|
248
|
+
except Exception as e:
|
|
249
|
+
logger.error(f">>> (DataFetcher::load) - failed to load <g>'{self._fetcher_id}'</g> data: {e}")
|
|
251
250
|
else:
|
|
252
251
|
raise IndexError(
|
|
253
252
|
f"Instrument {_r} is not subscribed for this data {self._requested_data_type} in {self._fetcher_id} !"
|
|
@@ -438,7 +437,7 @@ class IterableSimulationData(Iterator):
|
|
|
438
437
|
self._slicing_iterator = iter(self._slicer_ctrl)
|
|
439
438
|
return self
|
|
440
439
|
|
|
441
|
-
def __next__(self) -> tuple[Instrument, str,
|
|
440
|
+
def __next__(self) -> tuple[Instrument, str, Timestamped, bool]: # type: ignore
|
|
442
441
|
try:
|
|
443
442
|
while data := next(self._slicing_iterator): # type: ignore
|
|
444
443
|
k, t, v = data
|
|
@@ -5,15 +5,16 @@ import pandas as pd
|
|
|
5
5
|
from joblib import delayed
|
|
6
6
|
|
|
7
7
|
from qubx import QubxLogConfig, logger, lookup
|
|
8
|
-
from qubx.core.basics import DataType
|
|
8
|
+
from qubx.core.basics import DataType
|
|
9
9
|
from qubx.core.context import StrategyContext
|
|
10
10
|
from qubx.core.exceptions import 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.metrics import TradingSessionResult
|
|
14
15
|
from qubx.data.helpers import InMemoryCachedReader, TimeGuardedWrapper
|
|
15
16
|
from qubx.data.readers import DataReader
|
|
16
|
-
from qubx.utils.misc import ProgressParallel
|
|
17
|
+
from qubx.utils.misc import ProgressParallel, get_current_user
|
|
17
18
|
|
|
18
19
|
from .account import SimulatedAccountProcessor
|
|
19
20
|
from .broker import SimulatedBroker
|
|
@@ -226,7 +227,7 @@ def _run_setup(
|
|
|
226
227
|
accurate_stop_orders_execution=accurate_stop_orders_execution,
|
|
227
228
|
)
|
|
228
229
|
scheduler = SimulatedScheduler(channel, lambda: time_provider.time().item())
|
|
229
|
-
broker = SimulatedBroker(channel, account)
|
|
230
|
+
broker = SimulatedBroker(channel, account, setup.exchange)
|
|
230
231
|
data_provider = SimulatedDataProvider(
|
|
231
232
|
exchange_id=setup.exchange,
|
|
232
233
|
channel=channel,
|
|
@@ -346,4 +347,5 @@ def _run_setup(
|
|
|
346
347
|
strategy_class=_s_class,
|
|
347
348
|
parameters=_s_params,
|
|
348
349
|
is_simulation=True,
|
|
350
|
+
author=get_current_user(),
|
|
349
351
|
)
|
|
@@ -22,7 +22,7 @@ from .utils import ccxt_convert_order_info, instrument_to_ccxt_symbol
|
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
class CcxtBroker(IBroker):
|
|
25
|
-
|
|
25
|
+
_exchange: cxp.Exchange
|
|
26
26
|
|
|
27
27
|
_positions: dict[Instrument, Position]
|
|
28
28
|
_loop: AsyncThreadLoop
|
|
@@ -34,7 +34,7 @@ class CcxtBroker(IBroker):
|
|
|
34
34
|
time_provider: ITimeProvider,
|
|
35
35
|
account: IAccountProcessor,
|
|
36
36
|
):
|
|
37
|
-
self.
|
|
37
|
+
self._exchange = exchange
|
|
38
38
|
self.ccxt_exchange_id = str(exchange.name)
|
|
39
39
|
self.channel = channel
|
|
40
40
|
self.time_provider = time_provider
|
|
@@ -73,7 +73,7 @@ class CcxtBroker(IBroker):
|
|
|
73
73
|
r: dict[str, Any] | None = None
|
|
74
74
|
try:
|
|
75
75
|
r = self._loop.submit(
|
|
76
|
-
self.
|
|
76
|
+
self._exchange.create_order(
|
|
77
77
|
symbol=ccxt_symbol,
|
|
78
78
|
type=order_type, # type: ignore
|
|
79
79
|
side=order_side, # type: ignore
|
|
@@ -109,7 +109,7 @@ class CcxtBroker(IBroker):
|
|
|
109
109
|
try:
|
|
110
110
|
logger.info(f"Canceling order {order_id} ...")
|
|
111
111
|
r = self._loop.submit(
|
|
112
|
-
self.
|
|
112
|
+
self._exchange.cancel_order(order_id, symbol=instrument_to_ccxt_symbol(order.instrument))
|
|
113
113
|
).result()
|
|
114
114
|
except Exception as err:
|
|
115
115
|
logger.error(f"Canceling [{order}] exception : {err}")
|
|
@@ -122,3 +122,9 @@ class CcxtBroker(IBroker):
|
|
|
122
122
|
|
|
123
123
|
def update_order(self, order_id: str, price: float | None = None, amount: float | None = None) -> Order:
|
|
124
124
|
raise NotImplementedError("Not implemented yet")
|
|
125
|
+
|
|
126
|
+
def exchange(self) -> str:
|
|
127
|
+
"""
|
|
128
|
+
Return the name of the exchange this broker is connected to.
|
|
129
|
+
"""
|
|
130
|
+
return self.ccxt_exchange_id.upper()
|
|
@@ -423,6 +423,14 @@ class CcxtDataProvider(IDataProvider):
|
|
|
423
423
|
False, # not historical bar
|
|
424
424
|
)
|
|
425
425
|
)
|
|
426
|
+
if not (
|
|
427
|
+
self.has_subscription(instrument, DataType.ORDERBOOK)
|
|
428
|
+
or self.has_subscription(instrument, DataType.QUOTE)
|
|
429
|
+
):
|
|
430
|
+
_price = ohlcvs[-1][4]
|
|
431
|
+
_s2 = instrument.tick_size / 2.0
|
|
432
|
+
_bid, _ask = _price - _s2, _price + _s2
|
|
433
|
+
self._last_quotes[instrument] = Quote(oh[0] * 1_000_000, _bid, _ask, 0.0, 0.0)
|
|
426
434
|
|
|
427
435
|
# ohlc subscription reuses the same connection always, unsubscriptions don't work properly
|
|
428
436
|
# but it's likely not very needed
|
|
@@ -599,3 +607,6 @@ class CcxtDataProvider(IDataProvider):
|
|
|
599
607
|
name=name,
|
|
600
608
|
unsubscriber=un_watch_funding_rates,
|
|
601
609
|
)
|
|
610
|
+
|
|
611
|
+
def exchange(self) -> str:
|
|
612
|
+
return self._exchange_id.upper()
|