Qubx 0.6.91__cp312-cp312-manylinux_2_39_x86_64.whl → 0.6.94__cp312-cp312-manylinux_2_39_x86_64.whl
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/backtester/management.py +2 -1
- qubx/backtester/simulator.py +15 -1
- qubx/backtester/transfers.py +146 -0
- qubx/cli/commands.py +117 -2
- qubx/connectors/ccxt/factory.py +15 -20
- qubx/connectors/xlighter/__init__.py +83 -0
- qubx/connectors/xlighter/account.py +421 -0
- qubx/connectors/xlighter/broker.py +857 -0
- qubx/connectors/xlighter/client.py +378 -0
- qubx/connectors/xlighter/constants.py +125 -0
- qubx/connectors/xlighter/data.py +722 -0
- qubx/connectors/xlighter/extensions.py +248 -0
- qubx/connectors/xlighter/factory.py +285 -0
- qubx/connectors/xlighter/handlers/__init__.py +15 -0
- qubx/connectors/xlighter/handlers/base.py +104 -0
- qubx/connectors/xlighter/handlers/orderbook.py +120 -0
- qubx/connectors/xlighter/handlers/quote.py +158 -0
- qubx/connectors/xlighter/handlers/stats.py +351 -0
- qubx/connectors/xlighter/handlers/trades.py +146 -0
- qubx/connectors/xlighter/instruments.py +253 -0
- qubx/connectors/xlighter/parsers.py +675 -0
- qubx/connectors/xlighter/reader.py +490 -0
- qubx/connectors/xlighter/utils.py +281 -0
- qubx/connectors/xlighter/websocket.py +443 -0
- qubx/core/account.py +40 -34
- qubx/core/basics.py +32 -5
- qubx/core/context.py +71 -6
- qubx/core/detectors/__init__.py +4 -0
- qubx/core/detectors/delisting.py +81 -0
- qubx/core/{stale_data_detector.py → detectors/stale.py} +101 -100
- qubx/core/initializer.py +46 -1
- qubx/core/interfaces.py +283 -0
- qubx/core/metrics.py +96 -5
- qubx/core/mixins/processing.py +13 -17
- qubx/core/mixins/trading.py +162 -5
- qubx/core/mixins/universe.py +7 -0
- qubx/core/series.cpython-312-x86_64-linux-gnu.so +0 -0
- qubx/core/series.pxd +8 -0
- qubx/core/series.pyi +46 -2
- qubx/core/series.pyx +134 -4
- qubx/core/utils.cpython-312-x86_64-linux-gnu.so +0 -0
- qubx/data/storages/questdb.py +34 -8
- qubx/data/transformers.py +36 -1
- qubx/pandaz/utils.py +1 -1
- qubx/ta/indicators.cpython-312-x86_64-linux-gnu.so +0 -0
- qubx/utils/orderbook.py +102 -2
- qubx/utils/runner/accounts.py +30 -1
- qubx/utils/runner/configs.py +2 -1
- qubx/utils/runner/kernel_service.py +195 -0
- qubx/utils/runner/runner.py +111 -11
- qubx/utils/runner/textual/__init__.py +124 -7
- qubx/utils/runner/textual/app.py +289 -46
- qubx/utils/runner/textual/handlers.py +39 -21
- qubx/utils/runner/textual/init_code.py +168 -7
- qubx/utils/runner/textual/kernel.py +168 -9
- qubx/utils/runner/textual/styles.tcss +43 -9
- qubx/utils/runner/textual/widgets/__init__.py +5 -1
- qubx/utils/runner/textual/widgets/command_input.py +105 -0
- qubx/utils/runner/textual/widgets/debug_log.py +97 -0
- qubx/utils/runner/textual/widgets/orders_table.py +61 -0
- qubx/utils/runner/textual/widgets/positions_table.py +3 -1
- qubx/utils/runner/textual/widgets/quotes_table.py +90 -0
- qubx/utils/runner/textual/widgets/repl_output.py +84 -1
- qubx/utils/websocket_manager.py +592 -0
- {qubx-0.6.91.dist-info → qubx-0.6.94.dist-info}/METADATA +5 -2
- {qubx-0.6.91.dist-info → qubx-0.6.94.dist-info}/RECORD +69 -41
- {qubx-0.6.91.dist-info → qubx-0.6.94.dist-info}/WHEEL +0 -0
- {qubx-0.6.91.dist-info → qubx-0.6.94.dist-info}/entry_points.txt +0 -0
- {qubx-0.6.91.dist-info → qubx-0.6.94.dist-info}/licenses/LICENSE +0 -0
qubx/backtester/management.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import re
|
|
2
2
|
import zipfile
|
|
3
3
|
from collections import defaultdict
|
|
4
|
+
from os.path import expanduser
|
|
4
5
|
from pathlib import Path
|
|
5
6
|
|
|
6
7
|
import numpy as np
|
|
@@ -39,7 +40,7 @@ class BacktestsResultsManager:
|
|
|
39
40
|
"""
|
|
40
41
|
|
|
41
42
|
def __init__(self, path: str):
|
|
42
|
-
self.path = path
|
|
43
|
+
self.path = expanduser(path)
|
|
43
44
|
self.reload()
|
|
44
45
|
|
|
45
46
|
def reload(self) -> "BacktestsResultsManager":
|
qubx/backtester/simulator.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import Literal
|
|
1
|
+
from typing import Literal, cast
|
|
2
2
|
|
|
3
3
|
import pandas as pd
|
|
4
4
|
from joblib import delayed
|
|
@@ -15,6 +15,7 @@ from qubx.utils.runner.configs import PrefetchConfig
|
|
|
15
15
|
from qubx.utils.time import handle_start_stop, to_utc_naive
|
|
16
16
|
|
|
17
17
|
from .runner import SimulationRunner
|
|
18
|
+
from .transfers import SimulationTransferManager
|
|
18
19
|
from .utils import (
|
|
19
20
|
DataDecls_t,
|
|
20
21
|
ExchangeName_t,
|
|
@@ -300,6 +301,18 @@ def _run_setup(
|
|
|
300
301
|
if enable_inmemory_emitter and emitter is not None:
|
|
301
302
|
emitter_data = emitter.get_dataframe()
|
|
302
303
|
|
|
304
|
+
# - get transfers log
|
|
305
|
+
transfers_log = None
|
|
306
|
+
if hasattr(runner.ctx, "_transfer_manager") and isinstance(
|
|
307
|
+
getattr(runner.ctx, "_transfer_manager"), SimulationTransferManager
|
|
308
|
+
):
|
|
309
|
+
try:
|
|
310
|
+
transfer_manager = cast(SimulationTransferManager, getattr(runner.ctx, "_transfer_manager"))
|
|
311
|
+
transfers_log = transfer_manager.get_transfers_dataframe()
|
|
312
|
+
except Exception as e:
|
|
313
|
+
logger.error(f"Failed to get transfers log: {e}")
|
|
314
|
+
transfers_log = None
|
|
315
|
+
|
|
303
316
|
return TradingSessionResult(
|
|
304
317
|
setup_id,
|
|
305
318
|
setup.name,
|
|
@@ -319,6 +332,7 @@ def _run_setup(
|
|
|
319
332
|
is_simulation=True,
|
|
320
333
|
author=get_current_user(),
|
|
321
334
|
emitter_data=emitter_data,
|
|
335
|
+
transfers_log=transfers_log,
|
|
322
336
|
)
|
|
323
337
|
except Exception as e:
|
|
324
338
|
logger.error(f"Simulation setup {setup_id} failed with error: {e}")
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import uuid
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
import pandas as pd
|
|
5
|
+
|
|
6
|
+
from qubx import logger
|
|
7
|
+
from qubx.core.account import CompositeAccountProcessor
|
|
8
|
+
from qubx.core.basics import ITimeProvider
|
|
9
|
+
from qubx.core.interfaces import ITransferManager
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SimulationTransferManager(ITransferManager):
|
|
13
|
+
"""
|
|
14
|
+
Transfer manager for simulation mode.
|
|
15
|
+
|
|
16
|
+
Handles fund transfers between exchanges by directly manipulating account balances.
|
|
17
|
+
All transfers are instant and tracked in a DataFrame for export to results.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
_account: CompositeAccountProcessor
|
|
21
|
+
_time: ITimeProvider
|
|
22
|
+
_transfers: list[dict[str, Any]]
|
|
23
|
+
|
|
24
|
+
def __init__(self, account_processor: CompositeAccountProcessor, time_provider: ITimeProvider):
|
|
25
|
+
"""
|
|
26
|
+
Initialize simulation transfer manager.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
account_processor: Account processor (typically CompositeAccountProcessor)
|
|
30
|
+
time_provider: Time provider for timestamping transfers
|
|
31
|
+
"""
|
|
32
|
+
self._account = account_processor
|
|
33
|
+
self._time = time_provider
|
|
34
|
+
self._transfers = []
|
|
35
|
+
|
|
36
|
+
def transfer_funds(self, from_exchange: str, to_exchange: str, currency: str, amount: float) -> str:
|
|
37
|
+
"""
|
|
38
|
+
Transfer funds between exchanges (instant in simulation).
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
from_exchange: Source exchange identifier
|
|
42
|
+
to_exchange: Destination exchange identifier
|
|
43
|
+
currency: Currency to transfer
|
|
44
|
+
amount: Amount to transfer
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
str: Transaction ID (UUID)
|
|
48
|
+
|
|
49
|
+
Raises:
|
|
50
|
+
ValueError: If exchanges not found or insufficient funds
|
|
51
|
+
"""
|
|
52
|
+
# Generate transaction ID
|
|
53
|
+
transaction_id = f"sim_{uuid.uuid4().hex[:12]}"
|
|
54
|
+
|
|
55
|
+
# Get timestamp
|
|
56
|
+
timestamp = self._time.time()
|
|
57
|
+
|
|
58
|
+
# Get individual processors
|
|
59
|
+
try:
|
|
60
|
+
from_processor = self._account.get_account_processor(from_exchange)
|
|
61
|
+
to_processor = self._account.get_account_processor(to_exchange)
|
|
62
|
+
except (KeyError, AttributeError) as e:
|
|
63
|
+
raise ValueError(f"Exchange not found: {e}")
|
|
64
|
+
|
|
65
|
+
# Validate sufficient funds
|
|
66
|
+
from_balances = from_processor.get_balances()
|
|
67
|
+
if currency not in from_balances:
|
|
68
|
+
raise ValueError(f"Currency '{currency}' not found in {from_exchange}")
|
|
69
|
+
|
|
70
|
+
available = from_balances[currency].free
|
|
71
|
+
if available < amount:
|
|
72
|
+
raise ValueError(
|
|
73
|
+
f"Insufficient funds in {from_exchange}: "
|
|
74
|
+
f"{available:.8f} {currency} available, {amount:.8f} {currency} requested"
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# Execute transfer (instant balance manipulation)
|
|
78
|
+
from_balances[currency].total -= amount
|
|
79
|
+
from_balances[currency].free -= amount
|
|
80
|
+
|
|
81
|
+
to_balances = to_processor.get_balances()
|
|
82
|
+
to_balances[currency].total += amount
|
|
83
|
+
to_balances[currency].free += amount
|
|
84
|
+
|
|
85
|
+
# Record transfer
|
|
86
|
+
transfer_record = {
|
|
87
|
+
"transaction_id": transaction_id,
|
|
88
|
+
"timestamp": timestamp,
|
|
89
|
+
"from_exchange": from_exchange,
|
|
90
|
+
"to_exchange": to_exchange,
|
|
91
|
+
"currency": currency,
|
|
92
|
+
"amount": amount,
|
|
93
|
+
"status": "completed", # Always completed in simulation
|
|
94
|
+
}
|
|
95
|
+
self._transfers.append(transfer_record)
|
|
96
|
+
|
|
97
|
+
logger.debug(f"[SimTransfer] {amount:.8f} {currency} {from_exchange} → {to_exchange} (ID: {transaction_id})")
|
|
98
|
+
|
|
99
|
+
return transaction_id
|
|
100
|
+
|
|
101
|
+
def get_transfer_status(self, transaction_id: str) -> dict[str, Any]:
|
|
102
|
+
"""
|
|
103
|
+
Get the status of a transfer.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
transaction_id: Transaction ID
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
dict[str, Any]: Transfer status information
|
|
110
|
+
"""
|
|
111
|
+
# Find transfer
|
|
112
|
+
for transfer in self._transfers:
|
|
113
|
+
if transfer["transaction_id"] == transaction_id:
|
|
114
|
+
return transfer.copy()
|
|
115
|
+
|
|
116
|
+
# Not found
|
|
117
|
+
return {
|
|
118
|
+
"transaction_id": transaction_id,
|
|
119
|
+
"status": "not_found",
|
|
120
|
+
"error": f"Transaction {transaction_id} not found",
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
def get_transfers(self) -> dict[str, dict[str, Any]]:
|
|
124
|
+
"""
|
|
125
|
+
Get all transfers as a dictionary.
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
dict[str, dict[str, Any]]: Dictionary mapping transaction IDs to transfer info
|
|
129
|
+
"""
|
|
130
|
+
return {t["transaction_id"]: t for t in self._transfers}
|
|
131
|
+
|
|
132
|
+
def get_transfers_dataframe(self) -> pd.DataFrame:
|
|
133
|
+
"""
|
|
134
|
+
Get all transfers as a pandas DataFrame.
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
pd.DataFrame: DataFrame with columns [transaction_id, timestamp, from_exchange,
|
|
138
|
+
to_exchange, currency, amount, status]
|
|
139
|
+
"""
|
|
140
|
+
if not self._transfers:
|
|
141
|
+
# Return empty DataFrame with correct schema
|
|
142
|
+
return pd.DataFrame(
|
|
143
|
+
columns=["transaction_id", "from_exchange", "to_exchange", "currency", "amount", "status"] # type: ignore
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
return pd.DataFrame(self._transfers).set_index("timestamp")
|
qubx/cli/commands.py
CHANGED
|
@@ -72,11 +72,29 @@ def main(debug: bool, debug_port: int, log_level: str):
|
|
|
72
72
|
@click.option(
|
|
73
73
|
"--textual", "-t", is_flag=True, default=False, help="Run strategy in textual TUI.", show_default=True
|
|
74
74
|
)
|
|
75
|
+
@click.option(
|
|
76
|
+
"--textual-dev", is_flag=True, default=False, help="Enable Textual dev mode (use with 'textual console').", show_default=True
|
|
77
|
+
)
|
|
78
|
+
@click.option(
|
|
79
|
+
"--textual-web", is_flag=True, default=False, help="Serve Textual app in web browser.", show_default=True
|
|
80
|
+
)
|
|
81
|
+
@click.option(
|
|
82
|
+
"--textual-port", type=int, default=None, help="Port for Textual (web server: 8000, devtools: 8081).", show_default=False
|
|
83
|
+
)
|
|
84
|
+
@click.option(
|
|
85
|
+
"--textual-host", type=str, default="0.0.0.0", help="Host for Textual web server.", show_default=True
|
|
86
|
+
)
|
|
87
|
+
@click.option(
|
|
88
|
+
"--kernel-only", is_flag=True, default=False, help="Start kernel without UI (returns connection file).", show_default=True
|
|
89
|
+
)
|
|
90
|
+
@click.option(
|
|
91
|
+
"--connect", type=Path, default=None, help="Connect to existing kernel via connection file.", show_default=False
|
|
92
|
+
)
|
|
75
93
|
@click.option(
|
|
76
94
|
"--restore", "-r", is_flag=True, default=False, help="Restore strategy state from previous run.", show_default=True
|
|
77
95
|
)
|
|
78
96
|
@click.option("--no-color", is_flag=True, default=False, help="Disable colored logging output.", show_default=True)
|
|
79
|
-
def run(config_file: Path, account_file: Path | None, paper: bool, jupyter: bool, textual: bool, restore: bool, no_color: bool):
|
|
97
|
+
def run(config_file: Path, account_file: Path | None, paper: bool, jupyter: bool, textual: bool, textual_dev: bool, textual_web: bool, textual_port: int | None, textual_host: str, kernel_only: bool, connect: Path | None, restore: bool, no_color: bool):
|
|
80
98
|
"""
|
|
81
99
|
Starts the strategy with the given configuration file. If paper mode is enabled, account is not required.
|
|
82
100
|
|
|
@@ -94,6 +112,39 @@ def run(config_file: Path, account_file: Path | None, paper: bool, jupyter: bool
|
|
|
94
112
|
click.echo("Error: --jupyter and --textual cannot be used together.", err=True)
|
|
95
113
|
raise click.Abort()
|
|
96
114
|
|
|
115
|
+
# Handle --kernel-only mode
|
|
116
|
+
if kernel_only:
|
|
117
|
+
import asyncio
|
|
118
|
+
|
|
119
|
+
from qubx.utils.runner.kernel_service import KernelService
|
|
120
|
+
|
|
121
|
+
add_project_to_system_path()
|
|
122
|
+
add_project_to_system_path(str(config_file.parent.parent))
|
|
123
|
+
add_project_to_system_path(str(config_file.parent))
|
|
124
|
+
|
|
125
|
+
click.echo("Starting persistent kernel...")
|
|
126
|
+
connection_file = asyncio.run(KernelService.start(config_file, account_file, paper, restore))
|
|
127
|
+
click.echo(click.style("✓ Kernel started successfully!", fg="green", bold=True))
|
|
128
|
+
click.echo(click.style(f"Connection file: {connection_file}", fg="cyan"))
|
|
129
|
+
click.echo()
|
|
130
|
+
click.echo("To connect a UI to this kernel:")
|
|
131
|
+
click.echo(f" qubx run --textual --connect {connection_file}")
|
|
132
|
+
click.echo()
|
|
133
|
+
click.echo("To stop this kernel:")
|
|
134
|
+
click.echo(f" qubx kernel stop {connection_file}")
|
|
135
|
+
click.echo()
|
|
136
|
+
click.echo("Press Ctrl+C to stop the kernel and exit...")
|
|
137
|
+
|
|
138
|
+
# Keep the process alive until interrupted
|
|
139
|
+
try:
|
|
140
|
+
import signal
|
|
141
|
+
signal.pause()
|
|
142
|
+
except KeyboardInterrupt:
|
|
143
|
+
click.echo("\nShutting down kernel...")
|
|
144
|
+
asyncio.run(KernelService.stop(connection_file))
|
|
145
|
+
click.echo("Kernel stopped.")
|
|
146
|
+
return
|
|
147
|
+
|
|
97
148
|
add_project_to_system_path()
|
|
98
149
|
add_project_to_system_path(str(config_file.parent.parent))
|
|
99
150
|
add_project_to_system_path(str(config_file.parent))
|
|
@@ -101,7 +152,7 @@ def run(config_file: Path, account_file: Path | None, paper: bool, jupyter: bool
|
|
|
101
152
|
if jupyter:
|
|
102
153
|
run_strategy_yaml_in_jupyter(config_file, account_file, paper, restore)
|
|
103
154
|
elif textual:
|
|
104
|
-
run_strategy_yaml_in_textual(config_file, account_file, paper, restore)
|
|
155
|
+
run_strategy_yaml_in_textual(config_file, account_file, paper, restore, textual_dev, textual_web, textual_port, textual_host, connect)
|
|
105
156
|
else:
|
|
106
157
|
logo()
|
|
107
158
|
run_strategy_yaml(config_file, account_file, paper=paper, restore=restore, blocking=True, no_color=no_color)
|
|
@@ -461,5 +512,69 @@ def init(
|
|
|
461
512
|
raise click.Abort()
|
|
462
513
|
|
|
463
514
|
|
|
515
|
+
@main.group()
|
|
516
|
+
def kernel():
|
|
517
|
+
"""
|
|
518
|
+
Manage persistent Jupyter kernels for strategy execution.
|
|
519
|
+
|
|
520
|
+
Kernels can be started independently of the UI, allowing multiple
|
|
521
|
+
UI instances to connect to the same running strategy.
|
|
522
|
+
"""
|
|
523
|
+
pass
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
@kernel.command("list")
|
|
527
|
+
def kernel_list():
|
|
528
|
+
"""
|
|
529
|
+
List all active kernel sessions.
|
|
530
|
+
|
|
531
|
+
Shows connection files and associated strategy configurations
|
|
532
|
+
for all currently running kernels.
|
|
533
|
+
"""
|
|
534
|
+
from qubx.utils.runner.kernel_service import KernelService
|
|
535
|
+
|
|
536
|
+
active = KernelService.list_active()
|
|
537
|
+
|
|
538
|
+
if not active:
|
|
539
|
+
click.echo("No active kernels found.")
|
|
540
|
+
return
|
|
541
|
+
|
|
542
|
+
import datetime
|
|
543
|
+
|
|
544
|
+
click.echo(click.style("Active Kernels:", fg="cyan", bold=True))
|
|
545
|
+
click.echo()
|
|
546
|
+
for i, kernel_info in enumerate(active, 1):
|
|
547
|
+
# Format timestamp
|
|
548
|
+
ts = datetime.datetime.fromtimestamp(kernel_info["timestamp"])
|
|
549
|
+
time_str = ts.strftime("%Y-%m-%d %H:%M:%S")
|
|
550
|
+
|
|
551
|
+
click.echo(f"{i}. {click.style('Strategy:', fg='yellow')} {kernel_info['strategy_name']}")
|
|
552
|
+
click.echo(f" {click.style('Started:', fg='yellow')} {time_str}")
|
|
553
|
+
click.echo(f" {click.style('Connection:', fg='yellow')} {kernel_info['connection_file']}")
|
|
554
|
+
click.echo()
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
@kernel.command("stop")
|
|
558
|
+
@click.argument("connection-file", type=Path, required=True)
|
|
559
|
+
def kernel_stop(connection_file: Path):
|
|
560
|
+
"""
|
|
561
|
+
Stop a running kernel by its connection file.
|
|
562
|
+
|
|
563
|
+
This will gracefully shutdown the kernel and clean up
|
|
564
|
+
the connection file.
|
|
565
|
+
"""
|
|
566
|
+
import asyncio
|
|
567
|
+
|
|
568
|
+
from qubx.utils.runner.kernel_service import KernelService
|
|
569
|
+
|
|
570
|
+
if not connection_file.exists():
|
|
571
|
+
click.echo(click.style(f"✗ Connection file not found: {connection_file}", fg="red"))
|
|
572
|
+
raise click.Abort()
|
|
573
|
+
|
|
574
|
+
click.echo(f"Stopping kernel: {connection_file}")
|
|
575
|
+
asyncio.run(KernelService.stop(str(connection_file)))
|
|
576
|
+
click.echo(click.style("✓ Kernel stopped successfully", fg="green"))
|
|
577
|
+
|
|
578
|
+
|
|
464
579
|
if __name__ == "__main__":
|
|
465
580
|
main()
|
qubx/connectors/ccxt/factory.py
CHANGED
|
@@ -8,8 +8,8 @@ from qubx.core.basics import CtrlChannel
|
|
|
8
8
|
from qubx.core.interfaces import IAccountProcessor, IBroker, IDataProvider, ITimeProvider
|
|
9
9
|
|
|
10
10
|
from .account import CcxtAccountProcessor
|
|
11
|
-
from .exchanges import CUSTOM_ACCOUNTS, CUSTOM_BROKERS, EXCHANGE_ALIASES
|
|
12
11
|
from .exchange_manager import ExchangeManager
|
|
12
|
+
from .exchanges import CUSTOM_ACCOUNTS, CUSTOM_BROKERS, EXCHANGE_ALIASES
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
def get_ccxt_exchange(
|
|
@@ -22,10 +22,10 @@ def get_ccxt_exchange(
|
|
|
22
22
|
) -> cxp.Exchange:
|
|
23
23
|
"""
|
|
24
24
|
Get a raw CCXT exchange object.
|
|
25
|
-
|
|
25
|
+
|
|
26
26
|
Creates and configures a CCXT exchange instance without any stability wrapper.
|
|
27
27
|
Use get_ccxt_exchange_manager() if you need automatic stability management.
|
|
28
|
-
|
|
28
|
+
|
|
29
29
|
Parameters:
|
|
30
30
|
exchange (str): The exchange name.
|
|
31
31
|
api_key (str, optional): The API key. Default is None.
|
|
@@ -33,7 +33,7 @@ def get_ccxt_exchange(
|
|
|
33
33
|
loop (asyncio.AbstractEventLoop, optional): Event loop. Default is None.
|
|
34
34
|
use_testnet (bool): Use testnet/sandbox mode. Default is False.
|
|
35
35
|
**kwargs: Additional parameters for exchange configuration.
|
|
36
|
-
|
|
36
|
+
|
|
37
37
|
Returns:
|
|
38
38
|
Raw CCXT Exchange instance
|
|
39
39
|
"""
|
|
@@ -90,10 +90,10 @@ def get_ccxt_exchange_manager(
|
|
|
90
90
|
) -> ExchangeManager:
|
|
91
91
|
"""
|
|
92
92
|
Get a CCXT exchange with automatic stability management.
|
|
93
|
-
|
|
93
|
+
|
|
94
94
|
Returns ExchangeManager wrapper that handles exchange recreation
|
|
95
95
|
during data stall scenarios via self-monitoring.
|
|
96
|
-
|
|
96
|
+
|
|
97
97
|
Parameters:
|
|
98
98
|
exchange (str): The exchange name.
|
|
99
99
|
api_key (str, optional): The API key. Default is None.
|
|
@@ -102,28 +102,23 @@ def get_ccxt_exchange_manager(
|
|
|
102
102
|
use_testnet (bool): Use testnet/sandbox mode. Default is False.
|
|
103
103
|
check_interval_seconds (float): How often to check for stalls. Default is 30.0.
|
|
104
104
|
**kwargs: Additional parameters for exchange configuration.
|
|
105
|
-
|
|
105
|
+
|
|
106
106
|
Returns:
|
|
107
107
|
ExchangeManager wrapping the CCXT Exchange
|
|
108
108
|
"""
|
|
109
109
|
# Prepare factory parameters for ExchangeManager recreation
|
|
110
110
|
factory_params = {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
**{k: v for k, v in kwargs.items() if k !=
|
|
111
|
+
"exchange": exchange,
|
|
112
|
+
"api_key": api_key,
|
|
113
|
+
"secret": secret,
|
|
114
|
+
"loop": loop,
|
|
115
|
+
"use_testnet": use_testnet,
|
|
116
|
+
**{k: v for k, v in kwargs.items() if k != "check_interval_seconds"},
|
|
117
117
|
}
|
|
118
|
-
|
|
118
|
+
|
|
119
119
|
# Create raw CCXT exchange using public factory method
|
|
120
120
|
ccxt_exchange = get_ccxt_exchange(
|
|
121
|
-
exchange=exchange,
|
|
122
|
-
api_key=api_key,
|
|
123
|
-
secret=secret,
|
|
124
|
-
loop=loop,
|
|
125
|
-
use_testnet=use_testnet,
|
|
126
|
-
**kwargs
|
|
121
|
+
exchange=exchange, api_key=api_key, secret=secret, loop=loop, use_testnet=use_testnet, **kwargs
|
|
127
122
|
)
|
|
128
123
|
|
|
129
124
|
# Wrap in ExchangeManager for stability management
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""XLighter exchange connector for Qubx"""
|
|
2
|
+
|
|
3
|
+
from .account import LighterAccountProcessor
|
|
4
|
+
from .broker import LighterBroker
|
|
5
|
+
from .client import LighterClient
|
|
6
|
+
from .constants import (
|
|
7
|
+
DEDICATED_CHANNELS,
|
|
8
|
+
MULTIPLEX_CHANNELS,
|
|
9
|
+
WS_CHANNEL_ACCOUNT_ALL,
|
|
10
|
+
WS_CHANNEL_EXECUTED_TX,
|
|
11
|
+
WS_CHANNEL_MARKET_STATS,
|
|
12
|
+
WS_CHANNEL_ORDER_BOOK,
|
|
13
|
+
WS_CHANNEL_TRADE,
|
|
14
|
+
WS_CHANNEL_USER_STATS,
|
|
15
|
+
LighterMarginMode,
|
|
16
|
+
LighterOrderSide,
|
|
17
|
+
LighterOrderType,
|
|
18
|
+
LighterTimeInForce,
|
|
19
|
+
)
|
|
20
|
+
from .data import LighterDataProvider
|
|
21
|
+
from .factory import (
|
|
22
|
+
get_xlighter_account,
|
|
23
|
+
get_xlighter_broker,
|
|
24
|
+
get_xlighter_client,
|
|
25
|
+
get_xlighter_data_provider,
|
|
26
|
+
)
|
|
27
|
+
from .instruments import LighterInstrumentLoader, load_lighter_instruments
|
|
28
|
+
from .parsers import PositionState
|
|
29
|
+
from .reader import XLighterDataReader
|
|
30
|
+
from .utils import (
|
|
31
|
+
convert_lighter_order,
|
|
32
|
+
convert_lighter_orderbook,
|
|
33
|
+
convert_lighter_quote,
|
|
34
|
+
convert_lighter_trade,
|
|
35
|
+
lighter_order_side_to_qubx,
|
|
36
|
+
lighter_symbol_to_qubx,
|
|
37
|
+
qubx_order_side_to_lighter,
|
|
38
|
+
qubx_symbol_to_lighter,
|
|
39
|
+
)
|
|
40
|
+
from .websocket import LighterWebSocketManager
|
|
41
|
+
|
|
42
|
+
__all__ = [
|
|
43
|
+
# Core Components
|
|
44
|
+
"LighterClient",
|
|
45
|
+
"LighterDataProvider",
|
|
46
|
+
"LighterBroker",
|
|
47
|
+
"LighterAccountProcessor",
|
|
48
|
+
"LighterWebSocketManager",
|
|
49
|
+
# Factory Functions
|
|
50
|
+
"get_xlighter_client",
|
|
51
|
+
"get_xlighter_data_provider",
|
|
52
|
+
"get_xlighter_account",
|
|
53
|
+
"get_xlighter_broker",
|
|
54
|
+
# Data Reader
|
|
55
|
+
"XLighterDataReader",
|
|
56
|
+
# Instruments
|
|
57
|
+
"LighterInstrumentLoader",
|
|
58
|
+
"load_lighter_instruments",
|
|
59
|
+
# Parsers
|
|
60
|
+
"PositionState",
|
|
61
|
+
# Constants
|
|
62
|
+
"LighterOrderType",
|
|
63
|
+
"LighterTimeInForce",
|
|
64
|
+
"LighterOrderSide",
|
|
65
|
+
"LighterMarginMode",
|
|
66
|
+
"WS_CHANNEL_ORDER_BOOK",
|
|
67
|
+
"WS_CHANNEL_TRADE",
|
|
68
|
+
"WS_CHANNEL_MARKET_STATS",
|
|
69
|
+
"WS_CHANNEL_ACCOUNT_ALL",
|
|
70
|
+
"WS_CHANNEL_USER_STATS",
|
|
71
|
+
"WS_CHANNEL_EXECUTED_TX",
|
|
72
|
+
"MULTIPLEX_CHANNELS",
|
|
73
|
+
"DEDICATED_CHANNELS",
|
|
74
|
+
# Utils
|
|
75
|
+
"lighter_symbol_to_qubx",
|
|
76
|
+
"qubx_symbol_to_lighter",
|
|
77
|
+
"lighter_order_side_to_qubx",
|
|
78
|
+
"qubx_order_side_to_lighter",
|
|
79
|
+
"convert_lighter_orderbook",
|
|
80
|
+
"convert_lighter_trade",
|
|
81
|
+
"convert_lighter_quote",
|
|
82
|
+
"convert_lighter_order",
|
|
83
|
+
]
|