Qubx 0.6.0__tar.gz → 0.6.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of Qubx might be problematic. Click here for more details.
- {qubx-0.6.0 → qubx-0.6.1}/PKG-INFO +4 -2
- {qubx-0.6.0 → qubx-0.6.1}/pyproject.toml +29 -28
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/__init__.py +1 -4
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/cli/release.py +11 -3
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/core/context.py +5 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/core/interfaces.py +46 -6
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/core/mixins/processing.py +25 -0
- qubx-0.6.1/src/qubx/exporters/__init__.py +10 -0
- qubx-0.6.1/src/qubx/exporters/formatters/__init__.py +11 -0
- qubx-0.6.1/src/qubx/exporters/formatters/base.py +162 -0
- qubx-0.6.1/src/qubx/exporters/formatters/incremental.py +16 -0
- qubx-0.6.1/src/qubx/exporters/formatters/slack.py +183 -0
- qubx-0.6.1/src/qubx/exporters/redis_streams.py +177 -0
- qubx-0.6.1/src/qubx/exporters/slack.py +174 -0
- {qubx-0.6.0 → qubx-0.6.1}/README.md +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/build.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/_nb_magic.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/backtester/__init__.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/backtester/account.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/backtester/broker.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/backtester/data.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/backtester/management.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/backtester/ome.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/backtester/optimization.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/backtester/simulated_data.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/backtester/simulator.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/backtester/utils.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/cli/__init__.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/cli/commands.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/cli/deploy.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/cli/misc.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/connectors/ccxt/__init__.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/connectors/ccxt/account.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/connectors/ccxt/broker.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/connectors/ccxt/customizations.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/connectors/ccxt/data.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/connectors/ccxt/exceptions.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/connectors/ccxt/factory.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/connectors/ccxt/utils.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/core/__init__.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/core/account.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/core/basics.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/core/exceptions.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/core/helpers.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/core/loggers.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/core/lookups.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/core/metrics.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/core/mixins/__init__.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/core/mixins/market.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/core/mixins/subscription.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/core/mixins/trading.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/core/mixins/universe.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/core/series.pxd +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/core/series.pyi +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/core/series.pyx +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/core/utils.pyi +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/core/utils.pyx +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/data/__init__.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/data/helpers.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/data/readers.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/data/tardis.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/gathering/simplest.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/math/__init__.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/math/stats.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/pandaz/__init__.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/pandaz/ta.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/pandaz/utils.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/resources/instruments/symbols-binance.cm.json +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/resources/instruments/symbols-binance.json +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/resources/instruments/symbols-binance.um.json +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/resources/instruments/symbols-bitfinex.f.json +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/resources/instruments/symbols-bitfinex.json +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/resources/instruments/symbols-kraken.f.json +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/resources/instruments/symbols-kraken.json +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/ta/__init__.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/ta/indicators.pxd +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/ta/indicators.pyi +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/ta/indicators.pyx +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/trackers/__init__.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/trackers/abvanced.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/trackers/composite.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/trackers/rebalancers.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/trackers/riskctrl.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/trackers/sizers.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/utils/__init__.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/utils/_pyxreloader.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/utils/charting/lookinglass.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/utils/charting/mpl_helpers.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/utils/marketdata/binance.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/utils/marketdata/ccxt.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/utils/marketdata/dukas.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/utils/misc.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/utils/ntp.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/utils/numbers_utils.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/utils/orderbook.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/utils/plotting/__init__.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/utils/plotting/dashboard.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/utils/plotting/data.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/utils/plotting/interfaces.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/utils/plotting/renderers/__init__.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/utils/plotting/renderers/plotly.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/utils/runner/__init__.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/utils/runner/_jupyter_runner.pyt +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/utils/runner/accounts.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/utils/runner/configs.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/utils/runner/runner.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/utils/time.py +0 -0
- {qubx-0.6.0 → qubx-0.6.1}/src/qubx/utils/version.py +0 -0
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
2
|
Name: Qubx
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.1
|
|
4
4
|
Summary: Qubx - Quantitative Trading Framework
|
|
5
|
+
Home-page: https://github.com/xLydianSoftware/Qubx
|
|
5
6
|
Author: Dmitry Marienko
|
|
6
7
|
Author-email: dmitry.marienko@xlydian.com
|
|
7
8
|
Requires-Python: >=3.10,<4.0
|
|
@@ -37,6 +38,7 @@ Requires-Dist: pymongo (>=4.6.1,<5.0.0)
|
|
|
37
38
|
Requires-Dist: python-binance (>=1.0.19,<2.0.0)
|
|
38
39
|
Requires-Dist: python-dotenv (>=1.0.0,<2.0.0)
|
|
39
40
|
Requires-Dist: pyyaml (>=6.0.2,<7.0.0)
|
|
41
|
+
Requires-Dist: redis (>=5.2.1,<6.0.0)
|
|
40
42
|
Requires-Dist: scikit-learn (>=1.4.2,<2.0.0)
|
|
41
43
|
Requires-Dist: scipy (>=1.12.0,<2.0.0)
|
|
42
44
|
Requires-Dist: sortedcontainers (>=2.4.0,<3.0.0)
|
|
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
|
|
|
4
4
|
|
|
5
5
|
[tool.poetry]
|
|
6
6
|
name = "Qubx"
|
|
7
|
-
version = "0.6.
|
|
7
|
+
version = "0.6.1"
|
|
8
8
|
description = "Qubx - Quantitative Trading Framework"
|
|
9
9
|
authors = [ "Dmitry Marienko <dmitry.marienko@xlydian.com>", "Yuriy Arabskyy <yuriy.arabskyy@xlydian.com>",]
|
|
10
10
|
readme = "README.md"
|
|
@@ -24,50 +24,51 @@ format = "wheel"
|
|
|
24
24
|
[tool.ruff]
|
|
25
25
|
line-length = 120
|
|
26
26
|
|
|
27
|
+
[tool.poetry.build]
|
|
28
|
+
script = "build.py"
|
|
29
|
+
generate-setup-file = false
|
|
30
|
+
|
|
31
|
+
[tool.poetry.scripts]
|
|
32
|
+
qubx = "qubx.cli.commands:main"
|
|
33
|
+
|
|
27
34
|
[tool.poetry.dependencies]
|
|
28
35
|
python = ">=3.10,<4.0"
|
|
29
36
|
numpy = "^1.26.3"
|
|
37
|
+
pandas = "^2.2.2"
|
|
38
|
+
pyarrow = "^15.0.0"
|
|
39
|
+
scipy = "^1.12.0"
|
|
40
|
+
scikit-learn = "^1.4.2"
|
|
41
|
+
statsmodels = "^0.14.2"
|
|
42
|
+
numba = "^0.59.1"
|
|
43
|
+
sortedcontainers = "^2.4.0"
|
|
30
44
|
ntplib = "^0.4.0"
|
|
45
|
+
python-binance = "^1.0.19"
|
|
46
|
+
ccxt = "^4.2.68"
|
|
47
|
+
pymongo = "^4.6.1"
|
|
48
|
+
redis = "^5.2.1"
|
|
49
|
+
psycopg = "^3.1.18"
|
|
50
|
+
psycopg-binary = "^3.1.19"
|
|
51
|
+
psycopg-pool = "^3.2.2"
|
|
52
|
+
matplotlib = "^3.8.4"
|
|
53
|
+
plotly = "^5.22.0"
|
|
54
|
+
dash = "^2.18.2"
|
|
55
|
+
dash-bootstrap-components = "^1.6.0"
|
|
31
56
|
loguru = "^0.7.2"
|
|
32
57
|
tqdm = "*"
|
|
33
58
|
importlib-metadata = "*"
|
|
34
59
|
stackprinter = "^0.2.10"
|
|
35
|
-
pymongo = "^4.6.1"
|
|
36
60
|
pydantic = "^2.9.2"
|
|
37
61
|
python-dotenv = "^1.0.0"
|
|
38
|
-
python-binance = "^1.0.19"
|
|
39
|
-
pyarrow = "^15.0.0"
|
|
40
|
-
scipy = "^1.12.0"
|
|
41
62
|
cython = "3.0.8"
|
|
42
|
-
ccxt = "^4.2.68"
|
|
43
63
|
croniter = "^2.0.5"
|
|
44
|
-
psycopg = "^3.1.18"
|
|
45
|
-
pandas = "^2.2.2"
|
|
46
|
-
statsmodels = "^0.14.2"
|
|
47
|
-
matplotlib = "^3.8.4"
|
|
48
|
-
numba = "^0.59.1"
|
|
49
|
-
scikit-learn = "^1.4.2"
|
|
50
|
-
plotly = "^5.22.0"
|
|
51
|
-
psycopg-binary = "^3.1.19"
|
|
52
|
-
psycopg-pool = "^3.2.2"
|
|
53
|
-
sortedcontainers = "^2.4.0"
|
|
54
64
|
msgspec = "^0.18.6"
|
|
55
65
|
pyyaml = "^6.0.2"
|
|
56
|
-
dash = "^2.18.2"
|
|
57
|
-
dash-bootstrap-components = "^1.6.0"
|
|
58
66
|
tabulate = "^0.9.0"
|
|
59
|
-
jupyter-console = "^6.6.3"
|
|
60
67
|
toml = "^0.10.2"
|
|
61
68
|
gitpython = "^3.1.44"
|
|
62
|
-
ipywidgets = "^8.1.5"
|
|
63
69
|
jupyter = "^1.1.1"
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
script = "build.py"
|
|
67
|
-
generate-setup-file = false
|
|
68
|
-
|
|
69
|
-
[tool.poetry.scripts]
|
|
70
|
-
qubx = "qubx.cli.commands:main"
|
|
70
|
+
jupyter-console = "^6.6.3"
|
|
71
|
+
ipywidgets = "^8.1.5"
|
|
71
72
|
|
|
72
73
|
[tool.ruff.lint]
|
|
73
74
|
extend-select = [ "I",]
|
|
@@ -77,7 +78,7 @@ ignore = [ "E731", "E722", "E741",]
|
|
|
77
78
|
asyncio_mode = "auto"
|
|
78
79
|
asyncio_default_fixture_loop_scope = "function"
|
|
79
80
|
pythonpath = [ "src",]
|
|
80
|
-
markers = [ "integration: mark
|
|
81
|
+
markers = [ "integration: mark test as requiring external services like Redis", "e2e: mark test as requiring external exchange connections and API credentials",]
|
|
81
82
|
addopts = "--disable-warnings"
|
|
82
83
|
filterwarnings = [ "ignore:.*Jupyter is migrating.*:DeprecationWarning",]
|
|
83
84
|
|
|
@@ -68,10 +68,6 @@ class QubxLogConfig:
|
|
|
68
68
|
def setup_logger(level: str | None = None, custom_formatter: Callable | None = None):
|
|
69
69
|
global logger
|
|
70
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
|
-
|
|
75
71
|
config = {
|
|
76
72
|
"handlers": [
|
|
77
73
|
{"sink": sys.stdout, "format": "{time} - {message}"},
|
|
@@ -79,6 +75,7 @@ class QubxLogConfig:
|
|
|
79
75
|
"extra": {"user": "someone"},
|
|
80
76
|
}
|
|
81
77
|
logger.configure(**config)
|
|
78
|
+
logger.remove(None)
|
|
82
79
|
|
|
83
80
|
level = level or QubxLogConfig.get_log_level()
|
|
84
81
|
# Add stdout handler with enqueue=True for thread/process safety
|
|
@@ -406,6 +406,15 @@ def _create_metadata(stg_name: str, git_info: ReleaseInfo, release_dir: str) ->
|
|
|
406
406
|
sort_keys=False,
|
|
407
407
|
)
|
|
408
408
|
|
|
409
|
+
# Create a README.md file
|
|
410
|
+
with open(os.path.join(release_dir, "README.md"), "wt") as fs:
|
|
411
|
+
fs.write(f"# {stg_name}\n\n")
|
|
412
|
+
fs.write("## Git Info\n\n")
|
|
413
|
+
fs.write(f"Tag: {git_info.tag}\n")
|
|
414
|
+
fs.write(f"Date: {git_info.time.isoformat()}\n")
|
|
415
|
+
fs.write(f"Author: {git_info.user}\n")
|
|
416
|
+
fs.write(f"Commit: {git_info.commit}\n")
|
|
417
|
+
|
|
409
418
|
|
|
410
419
|
def _modify_pyproject_toml(pyproject_path: str, package_name: str) -> None:
|
|
411
420
|
"""
|
|
@@ -553,7 +562,7 @@ def _get_imports(file_name: str, current_directory: str, what_to_look: list[str]
|
|
|
553
562
|
+ ".py"
|
|
554
563
|
)
|
|
555
564
|
imports.extend(_get_imports(f1, current_directory, what_to_look))
|
|
556
|
-
except Exception
|
|
565
|
+
except Exception:
|
|
557
566
|
pass
|
|
558
567
|
return imports
|
|
559
568
|
|
|
@@ -582,11 +591,10 @@ def make_tag_in_repo(repo: Repo, strategy_name_id: str, user: str, tag: str) ->
|
|
|
582
591
|
repo.config_writer().set_value("push", "followTags", "true").release()
|
|
583
592
|
|
|
584
593
|
_tn = datetime.now()
|
|
585
|
-
|
|
594
|
+
_ = repo.create_tag(
|
|
586
595
|
tag,
|
|
587
596
|
message=f"Release of '{strategy_name_id}' at {_tn.strftime('%Y-%b-%d %H:%M:%S')} by {user}",
|
|
588
597
|
)
|
|
589
|
-
# Fix: Push the tag reference properly
|
|
590
598
|
repo.remote("origin").push(f"refs/tags/{tag}")
|
|
591
599
|
return tag
|
|
592
600
|
|
|
@@ -31,6 +31,7 @@ from qubx.core.interfaces import (
|
|
|
31
31
|
IStrategy,
|
|
32
32
|
IStrategyContext,
|
|
33
33
|
ISubscriptionManager,
|
|
34
|
+
ITradeDataExport,
|
|
34
35
|
ITradingManager,
|
|
35
36
|
IUniverseManager,
|
|
36
37
|
PositionsTracker,
|
|
@@ -70,6 +71,7 @@ class StrategyContext(IStrategyContext):
|
|
|
70
71
|
|
|
71
72
|
_thread_data_loop: Thread | None = None # market data loop
|
|
72
73
|
_is_initialized: bool = False
|
|
74
|
+
_exporter: ITradeDataExport | None = None # Add exporter attribute
|
|
73
75
|
|
|
74
76
|
def __init__(
|
|
75
77
|
self,
|
|
@@ -84,6 +86,7 @@ class StrategyContext(IStrategyContext):
|
|
|
84
86
|
config: dict[str, Any] | None = None,
|
|
85
87
|
position_gathering: IPositionGathering | None = None, # TODO: make position gathering part of the strategy
|
|
86
88
|
aux_data_provider: DataReader | None = None,
|
|
89
|
+
exporter: ITradeDataExport | None = None, # Add exporter parameter
|
|
87
90
|
) -> None:
|
|
88
91
|
self.account = account
|
|
89
92
|
self.strategy = self.__instantiate_strategy(strategy, config)
|
|
@@ -96,6 +99,7 @@ class StrategyContext(IStrategyContext):
|
|
|
96
99
|
self._initial_instruments = instruments
|
|
97
100
|
|
|
98
101
|
self._cache = CachedMarketDataHolder()
|
|
102
|
+
self._exporter = exporter # Store the exporter
|
|
99
103
|
|
|
100
104
|
__position_tracker = self.strategy.tracker(self)
|
|
101
105
|
if __position_tracker is None:
|
|
@@ -149,6 +153,7 @@ class StrategyContext(IStrategyContext):
|
|
|
149
153
|
cache=self._cache,
|
|
150
154
|
scheduler=self._scheduler,
|
|
151
155
|
is_simulation=self._data_provider.is_simulation,
|
|
156
|
+
exporter=self._exporter, # Pass exporter to processing manager
|
|
152
157
|
)
|
|
153
158
|
self.__post_init__()
|
|
154
159
|
|
|
@@ -39,6 +39,46 @@ from qubx.core.series import OHLCV, Bar, Quote
|
|
|
39
39
|
RemovalPolicy = Literal["close", "wait_for_close", "wait_for_change"]
|
|
40
40
|
|
|
41
41
|
|
|
42
|
+
class ITradeDataExport:
|
|
43
|
+
"""Interface for exporting trading data to external systems."""
|
|
44
|
+
|
|
45
|
+
def export_signals(self, time: dt_64, signals: List[Signal], account: "IAccountViewer") -> None:
|
|
46
|
+
"""
|
|
47
|
+
Export signals to an external system.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
time: Timestamp when the signals were generated
|
|
51
|
+
signals: List of signals to export
|
|
52
|
+
account: Account viewer to get account information like total capital, leverage, etc.
|
|
53
|
+
"""
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
def export_target_positions(self, time: dt_64, targets: List[TargetPosition], account: "IAccountViewer") -> None:
|
|
57
|
+
"""
|
|
58
|
+
Export target positions to an external system.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
time: Timestamp when the target positions were generated
|
|
62
|
+
targets: List of target positions to export
|
|
63
|
+
account: Account viewer to get account information like total capital, leverage, etc.
|
|
64
|
+
"""
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
def export_position_changes(
|
|
68
|
+
self, time: dt_64, instrument: Instrument, price: float, account: "IAccountViewer"
|
|
69
|
+
) -> None:
|
|
70
|
+
"""
|
|
71
|
+
Export position changes to an external system.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
time: Timestamp when the leverage change occurred
|
|
75
|
+
instrument: The instrument for which the leverage changed
|
|
76
|
+
price: Price at which the leverage changed
|
|
77
|
+
account: Account viewer to get account information like total capital, leverage, etc.
|
|
78
|
+
"""
|
|
79
|
+
pass
|
|
80
|
+
|
|
81
|
+
|
|
42
82
|
class IAccountViewer:
|
|
43
83
|
account_id: str
|
|
44
84
|
|
|
@@ -579,9 +619,9 @@ class IUniverseManager:
|
|
|
579
619
|
instruments: List of instruments in the universe
|
|
580
620
|
skip_callback: Skip callback to the strategy
|
|
581
621
|
if_has_position_then: What to do if the instrument has a position
|
|
582
|
-
-
|
|
583
|
-
-
|
|
584
|
-
-
|
|
622
|
+
- "close" (default) - close position immediatelly and remove (unsubscribe) instrument from strategy
|
|
623
|
+
- "wait_for_close" - keep instrument and it's position until it's closed from strategy (or risk management), then remove instrument from strategy
|
|
624
|
+
- "wait_for_change" - keep instrument and position until strategy would try to change it - then close position and remove instrument
|
|
585
625
|
"""
|
|
586
626
|
...
|
|
587
627
|
|
|
@@ -599,9 +639,9 @@ class IUniverseManager:
|
|
|
599
639
|
Args:
|
|
600
640
|
instruments: List of instruments to remove
|
|
601
641
|
if_has_position_then: What to do if the instrument has a position
|
|
602
|
-
-
|
|
603
|
-
-
|
|
604
|
-
-
|
|
642
|
+
- "close" (default) - close position immediatelly and remove (unsubscribe) instrument from strategy
|
|
643
|
+
- "wait_for_close" - keep instrument and it's position until it's closed from strategy (or risk management), then remove instrument from strategy
|
|
644
|
+
- "wait_for_change" - keep instrument and position until strategy would try to change it - then close position and remove instrument
|
|
605
645
|
"""
|
|
606
646
|
...
|
|
607
647
|
|
|
@@ -28,6 +28,7 @@ from qubx.core.interfaces import (
|
|
|
28
28
|
IStrategyContext,
|
|
29
29
|
ISubscriptionManager,
|
|
30
30
|
ITimeProvider,
|
|
31
|
+
ITradeDataExport,
|
|
31
32
|
IUniverseManager,
|
|
32
33
|
PositionsTracker,
|
|
33
34
|
)
|
|
@@ -50,6 +51,7 @@ class ProcessingManager(IProcessingManager):
|
|
|
50
51
|
_cache: CachedMarketDataHolder
|
|
51
52
|
_scheduler: BasicScheduler
|
|
52
53
|
_universe_manager: IUniverseManager
|
|
54
|
+
_exporter: ITradeDataExport | None = None
|
|
53
55
|
|
|
54
56
|
_handlers: dict[str, Callable[["ProcessingManager", Instrument, str, Any], TriggerEvent | None]]
|
|
55
57
|
_strategy_name: str
|
|
@@ -78,6 +80,7 @@ class ProcessingManager(IProcessingManager):
|
|
|
78
80
|
cache: CachedMarketDataHolder,
|
|
79
81
|
scheduler: BasicScheduler,
|
|
80
82
|
is_simulation: bool,
|
|
83
|
+
exporter: ITradeDataExport | None = None,
|
|
81
84
|
):
|
|
82
85
|
self._context = context
|
|
83
86
|
self._strategy = strategy
|
|
@@ -92,6 +95,7 @@ class ProcessingManager(IProcessingManager):
|
|
|
92
95
|
self._universe_manager = universe_manager
|
|
93
96
|
self._cache = cache
|
|
94
97
|
self._scheduler = scheduler
|
|
98
|
+
self._exporter = exporter
|
|
95
99
|
|
|
96
100
|
self._pool = ThreadPool(2) if not self._is_simulation else None
|
|
97
101
|
self._handlers = {
|
|
@@ -239,7 +243,13 @@ class ProcessingManager(IProcessingManager):
|
|
|
239
243
|
# - check if trading is allowed for each target position
|
|
240
244
|
target_positions = [t for t in target_positions if self._universe_manager.is_trading_allowed(t.instrument)]
|
|
241
245
|
|
|
246
|
+
# - log target positions
|
|
242
247
|
self._logging.save_signals_targets(target_positions)
|
|
248
|
+
|
|
249
|
+
# - export target positions if exporter is available
|
|
250
|
+
if self._exporter is not None:
|
|
251
|
+
self._exporter.export_target_positions(self._time_provider.time(), target_positions, self._account)
|
|
252
|
+
|
|
243
253
|
return target_positions
|
|
244
254
|
|
|
245
255
|
def __process_signals_from_target_positions(
|
|
@@ -270,6 +280,10 @@ class ProcessingManager(IProcessingManager):
|
|
|
270
280
|
continue
|
|
271
281
|
signal.reference_price = q.mid_price()
|
|
272
282
|
|
|
283
|
+
# - export signals if exporter is available
|
|
284
|
+
if self._exporter is not None and signals:
|
|
285
|
+
self._exporter.export_signals(self._time_provider.time(), signals, self._account)
|
|
286
|
+
|
|
273
287
|
return signals
|
|
274
288
|
|
|
275
289
|
def _run_in_thread_pool(self, func: Callable, args=()):
|
|
@@ -414,14 +428,25 @@ class ProcessingManager(IProcessingManager):
|
|
|
414
428
|
self._account.process_deals(instrument, deals)
|
|
415
429
|
self._logging.save_deals(instrument, deals)
|
|
416
430
|
|
|
431
|
+
# - Process all deals first
|
|
417
432
|
for d in deals:
|
|
418
433
|
# - notify position gatherer and tracker
|
|
419
434
|
self._position_gathering.on_execution_report(self._context, instrument, d)
|
|
420
435
|
self._position_tracker.on_execution_report(self._context, instrument, d)
|
|
436
|
+
|
|
421
437
|
logger.debug(
|
|
422
438
|
f"[<y>{self.__class__.__name__}</y>(<g>{instrument}</g>)] :: executed <r>{d.order_id}</r> | {d.amount} @ {d.price}"
|
|
423
439
|
)
|
|
424
440
|
|
|
441
|
+
if self._exporter is not None and (q := self._market_data.quote(instrument)) is not None:
|
|
442
|
+
# - export position changes if exporter is available
|
|
443
|
+
self._exporter.export_position_changes(
|
|
444
|
+
time=self._time_provider.time(),
|
|
445
|
+
instrument=instrument,
|
|
446
|
+
price=q.mid_price(),
|
|
447
|
+
account=self._account,
|
|
448
|
+
)
|
|
449
|
+
|
|
425
450
|
# - notify universe manager about position change
|
|
426
451
|
self._universe_manager.on_alter_position(instrument)
|
|
427
452
|
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module contains exporters for trading data.
|
|
3
|
+
|
|
4
|
+
Exporters are used to export trading data to external systems.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from qubx.exporters.redis_streams import RedisStreamsExporter
|
|
8
|
+
from qubx.exporters.slack import SlackExporter
|
|
9
|
+
|
|
10
|
+
__all__ = ["RedisStreamsExporter", "SlackExporter"]
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Formatters for exporting trading data.
|
|
3
|
+
|
|
4
|
+
This module provides interfaces and implementations for formatting trading data
|
|
5
|
+
before it is exported to external systems.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from qubx.exporters.formatters.base import DefaultFormatter, IExportFormatter
|
|
9
|
+
from qubx.exporters.formatters.slack import SlackMessageFormatter
|
|
10
|
+
|
|
11
|
+
__all__ = ["IExportFormatter", "DefaultFormatter", "SlackMessageFormatter"]
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base formatter interfaces and implementations.
|
|
3
|
+
|
|
4
|
+
This module provides the base formatter interface and a default implementation
|
|
5
|
+
for formatting trading data before it is exported to external systems.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
from abc import ABC, abstractmethod
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
import pandas as pd
|
|
14
|
+
|
|
15
|
+
from qubx.core.basics import Instrument, Signal, TargetPosition, dt_64
|
|
16
|
+
from qubx.core.interfaces import IAccountViewer
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class IExportFormatter(ABC):
|
|
20
|
+
"""
|
|
21
|
+
Interface for formatting trading data before export.
|
|
22
|
+
|
|
23
|
+
Formatters are responsible for converting trading data objects (signals, target positions, etc.)
|
|
24
|
+
into a format suitable for export to external systems.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
@abstractmethod
|
|
28
|
+
def format_signal(self, time: dt_64, signal: Signal, account: IAccountViewer) -> dict[str, Any]:
|
|
29
|
+
"""
|
|
30
|
+
Format a signal for export.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
time: Timestamp when the signal was generated
|
|
34
|
+
signal: The signal to format
|
|
35
|
+
account: Account viewer to get account information like total capital, leverage, etc.
|
|
36
|
+
Returns:
|
|
37
|
+
A dictionary containing the formatted signal data
|
|
38
|
+
"""
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
@abstractmethod
|
|
42
|
+
def format_target_position(self, time: dt_64, target: TargetPosition, account: IAccountViewer) -> dict[str, Any]:
|
|
43
|
+
"""
|
|
44
|
+
Format a target position for export.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
time: Timestamp when the target position was generated
|
|
48
|
+
target: The target position to format
|
|
49
|
+
account: Account viewer to get account information like total capital, leverage, etc.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
A dictionary containing the formatted target position data
|
|
53
|
+
"""
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
@abstractmethod
|
|
57
|
+
def format_position_change(
|
|
58
|
+
self, time: dt_64, instrument: Instrument, price: float, account: IAccountViewer
|
|
59
|
+
) -> dict[str, Any]:
|
|
60
|
+
"""
|
|
61
|
+
Format a leverage change for export.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
time: Timestamp when the leverage change occurred
|
|
65
|
+
instrument: The instrument for which the leverage changed
|
|
66
|
+
price: Price at which the leverage changed
|
|
67
|
+
account: Account viewer to get account information like total capital, leverage, etc.
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
A dictionary containing the formatted leverage change data
|
|
71
|
+
"""
|
|
72
|
+
pass
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class DefaultFormatter(IExportFormatter):
|
|
76
|
+
"""
|
|
77
|
+
Default implementation of the IExportFormatter interface.
|
|
78
|
+
|
|
79
|
+
This formatter creates standardized JSON-serializable dictionaries for each data type.
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
def _format_timestamp(self, timestamp: Any) -> str:
|
|
83
|
+
"""
|
|
84
|
+
Format a timestamp for export.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
timestamp: The timestamp to format
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
The formatted timestamp as a string
|
|
91
|
+
"""
|
|
92
|
+
if timestamp is not None:
|
|
93
|
+
return pd.Timestamp(timestamp).isoformat()
|
|
94
|
+
else:
|
|
95
|
+
return ""
|
|
96
|
+
|
|
97
|
+
def format_signal(self, time: dt_64, signal: Signal, account: IAccountViewer) -> dict[str, Any]:
|
|
98
|
+
"""
|
|
99
|
+
Format a signal for export.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
time: Timestamp when the signal was generated
|
|
103
|
+
signal: The signal to format
|
|
104
|
+
account: Account viewer to get account information like total capital, leverage, etc.
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
A dictionary containing the formatted signal data
|
|
108
|
+
"""
|
|
109
|
+
return {
|
|
110
|
+
"timestamp": self._format_timestamp(time),
|
|
111
|
+
"instrument": signal.instrument.symbol,
|
|
112
|
+
"exchange": signal.instrument.exchange,
|
|
113
|
+
"direction": str(signal.signal),
|
|
114
|
+
"strength": str(abs(signal.signal)),
|
|
115
|
+
"reference_price": str(signal.reference_price) if signal.reference_price is not None else "",
|
|
116
|
+
"group": signal.group,
|
|
117
|
+
"metadata": json.dumps(signal.options) if signal.options else "",
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
def format_target_position(self, time: dt_64, target: TargetPosition, account: IAccountViewer) -> dict[str, Any]:
|
|
121
|
+
"""
|
|
122
|
+
Format a target position for export.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
time: Timestamp when the target position was generated
|
|
126
|
+
target: The target position to format
|
|
127
|
+
account: Account viewer to get account information like total capital, leverage, etc.
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
A dictionary containing the formatted target position data
|
|
131
|
+
"""
|
|
132
|
+
return {
|
|
133
|
+
"timestamp": self._format_timestamp(time),
|
|
134
|
+
"instrument": target.instrument.symbol,
|
|
135
|
+
"exchange": target.instrument.exchange,
|
|
136
|
+
"target_size": str(target.target_position_size),
|
|
137
|
+
"price": str(target.price) if target.price is not None else "",
|
|
138
|
+
"signal_id": str(id(target.signal)) if target.signal else "",
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
def format_position_change(
|
|
142
|
+
self, time: dt_64, instrument: Instrument, price: float, account: IAccountViewer
|
|
143
|
+
) -> dict[str, Any]:
|
|
144
|
+
"""
|
|
145
|
+
Format a position change for export.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
time: Timestamp when the leverage change occurred
|
|
149
|
+
instrument: The instrument for which the leverage changed
|
|
150
|
+
price: Price at which the leverage changed
|
|
151
|
+
account: Account viewer to get account information like total capital, leverage, etc.
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
A dictionary containing the formatted leverage change data
|
|
155
|
+
"""
|
|
156
|
+
return {
|
|
157
|
+
"timestamp": self._format_timestamp(time),
|
|
158
|
+
"instrument": instrument.symbol,
|
|
159
|
+
"exchange": instrument.exchange,
|
|
160
|
+
"price": price,
|
|
161
|
+
"target_quantity": account.get_position(instrument).quantity,
|
|
162
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from qubx.core.basics import Instrument, dt_64
|
|
4
|
+
from qubx.core.interfaces import IAccountViewer
|
|
5
|
+
from qubx.exporters.formatters.base import DefaultFormatter
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class IncrementalFormatter(DefaultFormatter):
|
|
9
|
+
"""
|
|
10
|
+
Incremental formatter for exporting trading data.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
def format_position_change(
|
|
14
|
+
self, time: dt_64, instrument: Instrument, price: float, account: IAccountViewer
|
|
15
|
+
) -> dict[str, Any]:
|
|
16
|
+
return {}
|