Qubx 0.5.7__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.

Files changed (100) hide show
  1. qubx/__init__.py +207 -0
  2. qubx/_nb_magic.py +100 -0
  3. qubx/backtester/__init__.py +5 -0
  4. qubx/backtester/account.py +145 -0
  5. qubx/backtester/broker.py +87 -0
  6. qubx/backtester/data.py +296 -0
  7. qubx/backtester/management.py +378 -0
  8. qubx/backtester/ome.py +296 -0
  9. qubx/backtester/optimization.py +201 -0
  10. qubx/backtester/simulated_data.py +558 -0
  11. qubx/backtester/simulator.py +362 -0
  12. qubx/backtester/utils.py +780 -0
  13. qubx/cli/__init__.py +0 -0
  14. qubx/cli/commands.py +67 -0
  15. qubx/connectors/ccxt/__init__.py +0 -0
  16. qubx/connectors/ccxt/account.py +495 -0
  17. qubx/connectors/ccxt/broker.py +132 -0
  18. qubx/connectors/ccxt/customizations.py +193 -0
  19. qubx/connectors/ccxt/data.py +612 -0
  20. qubx/connectors/ccxt/exceptions.py +17 -0
  21. qubx/connectors/ccxt/factory.py +93 -0
  22. qubx/connectors/ccxt/utils.py +307 -0
  23. qubx/core/__init__.py +0 -0
  24. qubx/core/account.py +251 -0
  25. qubx/core/basics.py +850 -0
  26. qubx/core/context.py +420 -0
  27. qubx/core/exceptions.py +38 -0
  28. qubx/core/helpers.py +480 -0
  29. qubx/core/interfaces.py +1150 -0
  30. qubx/core/loggers.py +514 -0
  31. qubx/core/lookups.py +475 -0
  32. qubx/core/metrics.py +1512 -0
  33. qubx/core/mixins/__init__.py +13 -0
  34. qubx/core/mixins/market.py +94 -0
  35. qubx/core/mixins/processing.py +428 -0
  36. qubx/core/mixins/subscription.py +203 -0
  37. qubx/core/mixins/trading.py +88 -0
  38. qubx/core/mixins/universe.py +270 -0
  39. qubx/core/series.cpython-312-x86_64-linux-gnu.so +0 -0
  40. qubx/core/series.pxd +125 -0
  41. qubx/core/series.pyi +118 -0
  42. qubx/core/series.pyx +988 -0
  43. qubx/core/utils.cpython-312-x86_64-linux-gnu.so +0 -0
  44. qubx/core/utils.pyi +6 -0
  45. qubx/core/utils.pyx +62 -0
  46. qubx/data/__init__.py +25 -0
  47. qubx/data/helpers.py +416 -0
  48. qubx/data/readers.py +1562 -0
  49. qubx/data/tardis.py +100 -0
  50. qubx/gathering/simplest.py +88 -0
  51. qubx/math/__init__.py +3 -0
  52. qubx/math/stats.py +129 -0
  53. qubx/pandaz/__init__.py +23 -0
  54. qubx/pandaz/ta.py +2757 -0
  55. qubx/pandaz/utils.py +638 -0
  56. qubx/resources/instruments/symbols-binance.cm.json +1 -0
  57. qubx/resources/instruments/symbols-binance.json +1 -0
  58. qubx/resources/instruments/symbols-binance.um.json +1 -0
  59. qubx/resources/instruments/symbols-bitfinex.f.json +1 -0
  60. qubx/resources/instruments/symbols-bitfinex.json +1 -0
  61. qubx/resources/instruments/symbols-kraken.f.json +1 -0
  62. qubx/resources/instruments/symbols-kraken.json +1 -0
  63. qubx/ta/__init__.py +0 -0
  64. qubx/ta/indicators.cpython-312-x86_64-linux-gnu.so +0 -0
  65. qubx/ta/indicators.pxd +149 -0
  66. qubx/ta/indicators.pyi +41 -0
  67. qubx/ta/indicators.pyx +787 -0
  68. qubx/trackers/__init__.py +3 -0
  69. qubx/trackers/abvanced.py +236 -0
  70. qubx/trackers/composite.py +146 -0
  71. qubx/trackers/rebalancers.py +129 -0
  72. qubx/trackers/riskctrl.py +641 -0
  73. qubx/trackers/sizers.py +235 -0
  74. qubx/utils/__init__.py +5 -0
  75. qubx/utils/_pyxreloader.py +281 -0
  76. qubx/utils/charting/lookinglass.py +1057 -0
  77. qubx/utils/charting/mpl_helpers.py +1183 -0
  78. qubx/utils/marketdata/binance.py +284 -0
  79. qubx/utils/marketdata/ccxt.py +90 -0
  80. qubx/utils/marketdata/dukas.py +130 -0
  81. qubx/utils/misc.py +541 -0
  82. qubx/utils/ntp.py +63 -0
  83. qubx/utils/numbers_utils.py +7 -0
  84. qubx/utils/orderbook.py +491 -0
  85. qubx/utils/plotting/__init__.py +0 -0
  86. qubx/utils/plotting/dashboard.py +150 -0
  87. qubx/utils/plotting/data.py +137 -0
  88. qubx/utils/plotting/interfaces.py +25 -0
  89. qubx/utils/plotting/renderers/__init__.py +0 -0
  90. qubx/utils/plotting/renderers/plotly.py +0 -0
  91. qubx/utils/runner/__init__.py +1 -0
  92. qubx/utils/runner/_jupyter_runner.pyt +60 -0
  93. qubx/utils/runner/accounts.py +88 -0
  94. qubx/utils/runner/configs.py +65 -0
  95. qubx/utils/runner/runner.py +470 -0
  96. qubx/utils/time.py +312 -0
  97. qubx-0.5.7.dist-info/METADATA +105 -0
  98. qubx-0.5.7.dist-info/RECORD +100 -0
  99. qubx-0.5.7.dist-info/WHEEL +4 -0
  100. qubx-0.5.7.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,137 @@
1
+ from dataclasses import dataclass, field
2
+ from typing import Literal, Dict, List, Optional, Union, Any
3
+ from datetime import datetime
4
+ import pandas as pd
5
+
6
+ PlotType = Literal["candlestick", "line", "scatter", "bar", "area", "signal", "execution", "track", "trend"]
7
+
8
+
9
+ @dataclass
10
+ class PlotStyle:
11
+ """Visual properties of a plot"""
12
+
13
+ color: Optional[str] = None
14
+ width: float = 1.0
15
+ opacity: float = 1.0
16
+ dash: Optional[str] = None # solid, dash, dot
17
+ marker: Optional[str] = None # triangle-up, triangle-down etc
18
+ marker_size: float = 10.0
19
+ fill_color: Optional[str] = None
20
+
21
+
22
+ @dataclass
23
+ class SeriesData:
24
+ """Single data series"""
25
+
26
+ data: Union[pd.Series, pd.DataFrame]
27
+ type: PlotType
28
+ name: str
29
+ style: PlotStyle = field(default_factory=PlotStyle)
30
+ hover_text: Optional[List[str]] = None
31
+
32
+
33
+ @dataclass
34
+ class SubplotData:
35
+ """Group of series in one subplot"""
36
+
37
+ series: List[SeriesData]
38
+ height_ratio: float = 1.0
39
+ y_axis_range: Optional[tuple[float, float]] = None
40
+ show_legend: bool = True
41
+
42
+
43
+ @dataclass
44
+ class PlotData:
45
+ """Complete plot specification"""
46
+
47
+ main: SubplotData # Main chart (usually price)
48
+ studies: Dict[str, SubplotData] = field(default_factory=dict) # Additional studies
49
+ title: str = ""
50
+
51
+ def to_looking_glass(self) -> Dict[str, Any]:
52
+ """Convert to LookingGlass format"""
53
+ return {
54
+ "master": self._convert_subplot(self.main),
55
+ "studies": (
56
+ {name: self._convert_subplot(study) for name, study in self.studies.items()} if self.studies else None
57
+ ),
58
+ }
59
+
60
+ def _convert_subplot(self, subplot: SubplotData) -> Union[pd.DataFrame, List[Any]]:
61
+ """Convert SubplotData to LookingGlass format"""
62
+ if not subplot.series:
63
+ return pd.DataFrame()
64
+
65
+ # If single OHLCV series, return as main dataframe
66
+ if len(subplot.series) == 1 and subplot.series[0].type == "candlestick":
67
+ return (
68
+ subplot.series[0].data
69
+ if isinstance(subplot.series[0].data, pd.DataFrame)
70
+ else subplot.series[0].data.to_frame()
71
+ )
72
+
73
+ # Convert multiple series into list format
74
+ converted = []
75
+ for series in subplot.series:
76
+ if series.type == "candlestick":
77
+ converted.append(
78
+ {
79
+ "data": series.data,
80
+ "type": "candlestick",
81
+ "name": series.name,
82
+ "style": {
83
+ "increasing_line_color": series.style.color or "green",
84
+ "decreasing_line_color": series.style.color or "red",
85
+ "opacity": series.style.opacity,
86
+ },
87
+ }
88
+ )
89
+
90
+ elif series.type in ["line", "area", "step"]:
91
+ converted.append(
92
+ {
93
+ "data": series.data,
94
+ "type": series.type,
95
+ "name": series.name,
96
+ "style": {
97
+ "color": series.style.color,
98
+ "width": series.style.width,
99
+ "dash": series.style.dash,
100
+ "fill": series.style.fill_color if series.type == "area" else None,
101
+ "opacity": series.style.opacity,
102
+ },
103
+ }
104
+ )
105
+
106
+ elif series.type in ["signal", "execution"]:
107
+ converted.append(
108
+ {
109
+ "data": series.data,
110
+ "type": "scatter",
111
+ "name": series.name,
112
+ "style": {
113
+ "color": series.style.color,
114
+ "marker": series.style.marker or "circle",
115
+ "size": series.style.marker_size,
116
+ "opacity": series.style.opacity,
117
+ },
118
+ "hover_text": series.hover_text,
119
+ }
120
+ )
121
+
122
+ elif series.type == "trend":
123
+ converted.append(
124
+ {
125
+ "data": series.data,
126
+ "type": "line",
127
+ "name": series.name,
128
+ "style": {
129
+ "color": series.style.color,
130
+ "width": series.style.width,
131
+ "dash": series.style.dash or "dash",
132
+ "opacity": series.style.opacity,
133
+ },
134
+ }
135
+ )
136
+
137
+ return converted
@@ -0,0 +1,25 @@
1
+ from typing import List, Tuple
2
+ from dataclasses import dataclass
3
+
4
+
5
+ @dataclass
6
+ class PlotData:
7
+ master: dict
8
+ studies: dict | None = None
9
+ master_size: int = 3
10
+ study_size: int = 1
11
+
12
+
13
+ class IPlotter:
14
+
15
+ def get_plots(self) -> List[str]:
16
+ """
17
+ Get the list of plots that this object can generate.
18
+ """
19
+ ...
20
+
21
+ def get_plot_data(self, plot_name: str) -> Tuple[str, dict]:
22
+ """
23
+ Get the data for the specified plot.
24
+ """
25
+ ...
File without changes
File without changes
@@ -0,0 +1 @@
1
+ from .runner import create_strategy_context, run_strategy, run_strategy_yaml
@@ -0,0 +1,60 @@
1
+ import qubx
2
+ from qubx.core.basics import Instrument
3
+ %qubxd
4
+
5
+ import pandas as pd
6
+ import nest_asyncio;
7
+ nest_asyncio.apply()
8
+
9
+ from pathlib import Path
10
+ from qubx.core.context import StrategyContext
11
+ from qubx.utils.misc import dequotify, quotify
12
+ from qubx.utils.runner import run_strategy_yaml
13
+ from qubx.pandaz.utils import *
14
+ import qubx.pandaz.ta as pta
15
+ import qubx.ta.indicators as ta
16
+
17
+ # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
18
+ account_file = Path('{account_file}') if '{account_file}' != 'None' else None
19
+ ctx: StrategyContext = run_strategy_yaml(Path('{config_file}'), account_file, {paper}) # type: ignore
20
+ assert ctx is not None, 'Strategy context is not created'
21
+
22
+ # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
23
+ def orders(instrument: Instrument | None=None):
24
+ return ctx.get_orders(instrument)
25
+
26
+ def trade(instrument: Instrument, qty: float, price=None, tif='gtc'):
27
+ return ctx.trade(instrument, qty, price, tif)
28
+
29
+ # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
30
+ def pnl_report(all=True):
31
+ from tabulate import tabulate
32
+
33
+ d = dict()
34
+ for s, p in ctx.get_positions().items():
35
+ mv = round(p.market_value_funds, 3)
36
+ if mv != 0.0 or all:
37
+ d[dequotify(s.symbol)] = dict(
38
+ Position=round(p.quantity, p.instrument.size_precision),
39
+ PnL=p.total_pnl(),
40
+ AvgPrice=round(p.position_avg_price_funds, p.instrument.price_precision),
41
+ LastPrice=round(p.last_update_price, p.instrument.price_precision),
42
+ MktValue=mv
43
+ )
44
+ d = pd.DataFrame.from_dict(d).T
45
+ # d = d[d['PnL'] != 0.0]
46
+ if d.empty:
47
+ print('-(no open positions yet)-')
48
+ return
49
+
50
+ d = d.sort_values('PnL' ,ascending=False)
51
+ # d = pd.concat((d, pd.Series(dict(TOTAL=d['PnL'].sum()), name='PnL'))).fillna('')
52
+ d = pd.concat((d, scols(pd.Series(dict(TOTAL=d['PnL'].sum()), name='PnL'), pd.Series(dict(TOTAL=d['MktValue'].sum()), name='MktValue')))).fillna('')
53
+ print(tabulate(d, ['Position', 'PnL', 'AvgPrice', 'LastPrice', 'MktValue'], tablefmt='rounded_grid'))
54
+
55
+ # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
56
+ __exit = exit
57
+ def exit():
58
+ ctx.stop()
59
+ __exit()
60
+ # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -0,0 +1,88 @@
1
+ from dataclasses import dataclass
2
+ from pathlib import Path
3
+
4
+ import toml
5
+ from pydantic import BaseModel
6
+
7
+
8
+ class ExchangeSettings(BaseModel):
9
+ exchange: str
10
+ testnet: bool = False
11
+ base_currency: str = "USDT"
12
+ commissions: str | None = None
13
+ initial_capital: float = 100_000
14
+
15
+
16
+ class ExchangeCredentials(ExchangeSettings):
17
+ name: str
18
+ api_key: str
19
+ secret: str
20
+
21
+
22
+ class AccountConfiguration(BaseModel):
23
+ defaults: list[ExchangeSettings]
24
+ accounts: list[ExchangeCredentials]
25
+
26
+
27
+ class AccountConfigurationManager:
28
+ """
29
+ Manages account configurations.
30
+ """
31
+
32
+ def __init__(
33
+ self,
34
+ account_config: Path | None = None,
35
+ strategy_dir: Path | None = None,
36
+ search_qubx_dir: bool = False,
37
+ ):
38
+ self._exchange_settings: dict[str, ExchangeSettings] = {}
39
+ self._exchange_credentials: dict[str, ExchangeCredentials] = {}
40
+ self._settings_to_config: dict[str, Path] = {}
41
+ self._credentials_to_config: dict[str, Path] = {}
42
+
43
+ self._config_paths = [Path("~/.qubx/accounts.toml").expanduser()] if search_qubx_dir else []
44
+ if strategy_dir:
45
+ self._config_paths.append(strategy_dir / "accounts.toml")
46
+ if account_config:
47
+ self._config_paths.append(account_config)
48
+ self._config_paths = [config for config in self._config_paths if config.exists()]
49
+ for config in self._config_paths:
50
+ self._load(config)
51
+
52
+ def get_exchange_settings(self, exchange: str) -> ExchangeSettings:
53
+ """
54
+ Get the basic settings for an exchange such as the base currency and commission tier.
55
+ """
56
+ exchange = exchange.upper()
57
+ if exchange not in self._exchange_settings:
58
+ return ExchangeSettings(exchange=exchange)
59
+ return self._exchange_settings[exchange.upper()].model_copy()
60
+
61
+ def get_exchange_credentials(self, exchange: str) -> ExchangeCredentials:
62
+ """
63
+ Get the api key and secret for an exchange as well as the base currency and commission tier.
64
+ """
65
+ return self._exchange_credentials[exchange.upper()].model_copy()
66
+
67
+ def get_config_path_for_settings(self, exchange: str) -> Path:
68
+ return self._settings_to_config[exchange.upper()]
69
+
70
+ def get_config_path_for_credentials(self, exchange: str) -> Path:
71
+ return self._credentials_to_config[exchange.upper()]
72
+
73
+ def __repr__(self):
74
+ exchanges = set(self._exchange_credentials.keys()) | set(self._exchange_settings.keys())
75
+ _e_str = "\n".join([f" - {exchange}" for exchange in exchanges])
76
+ return f"AccountManager:\n{_e_str}"
77
+
78
+ def _load(self, config: Path):
79
+ config_dict = toml.load(config)
80
+ account_config = AccountConfiguration(**config_dict)
81
+ for exchange_config in account_config.defaults:
82
+ _exchange = exchange_config.exchange.upper()
83
+ self._exchange_settings[_exchange] = exchange_config
84
+ self._settings_to_config[_exchange] = config
85
+ for exchange_config in account_config.accounts:
86
+ _exchange = exchange_config.exchange.upper()
87
+ self._exchange_credentials[_exchange] = exchange_config
88
+ self._credentials_to_config[_exchange] = config
@@ -0,0 +1,65 @@
1
+ from pathlib import Path
2
+
3
+ import yaml
4
+ from pydantic import BaseModel, Field
5
+
6
+
7
+ class ExchangeConfig(BaseModel):
8
+ connector: str
9
+ universe: list[str]
10
+
11
+
12
+ class AuxConfig(BaseModel):
13
+ reader: str
14
+ args: dict = Field(default_factory=dict)
15
+
16
+
17
+ class LoggingConfig(BaseModel):
18
+ logger: str
19
+ position_interval: str
20
+ portfolio_interval: str
21
+ heartbeat_interval: str = "1m"
22
+
23
+
24
+ class StrategyConfig(BaseModel):
25
+ strategy: str
26
+ parameters: dict = Field(default_factory=dict)
27
+ exchanges: dict[str, ExchangeConfig]
28
+ logging: LoggingConfig
29
+ aux: AuxConfig | None = None
30
+
31
+
32
+ def load_strategy_config_from_yaml(path: Path | str, key: str | None = None) -> StrategyConfig:
33
+ """
34
+ Loads a strategy configuration from a YAML file.
35
+
36
+ Args:
37
+ path (str | Path): The path to the YAML file.
38
+ key (str | None): The key to extract from the YAML file.
39
+
40
+ Returns:
41
+ StrategyConfig: The parsed configuration.
42
+ """
43
+ path = Path(path)
44
+ if not path.exists():
45
+ raise FileNotFoundError(f"File {path} not found.")
46
+ with path.open("r") as f:
47
+ config_dict = yaml.safe_load(f)
48
+ if key:
49
+ config_dict = config_dict[key]
50
+ return StrategyConfig(**config_dict)
51
+
52
+
53
+ class StrategySimulationConfig(BaseModel):
54
+ strategy: str | list[str]
55
+ parameters: dict = Field(default_factory=dict)
56
+ data: dict = Field(default_factory=dict)
57
+ simulation: dict = Field(default_factory=dict)
58
+ description: str | list[str] | None = None
59
+ variate: dict = Field(default_factory=dict)
60
+
61
+
62
+ def load_simulation_config_from_yaml(path: Path | str) -> StrategySimulationConfig:
63
+ with open(path, "r") as f:
64
+ cfg = yaml.safe_load(f)
65
+ return StrategySimulationConfig(**cfg)