Qubx 0.1.84__tar.gz → 0.1.86__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of Qubx might be problematic. Click here for more details.

Files changed (39) hide show
  1. {qubx-0.1.84 → qubx-0.1.86}/PKG-INFO +7 -3
  2. {qubx-0.1.84 → qubx-0.1.86}/README.md +6 -2
  3. {qubx-0.1.84 → qubx-0.1.86}/build.py +24 -16
  4. {qubx-0.1.84 → qubx-0.1.86}/pyproject.toml +2 -2
  5. {qubx-0.1.84 → qubx-0.1.86}/src/qubx/_nb_magic.py +14 -8
  6. {qubx-0.1.84 → qubx-0.1.86}/src/qubx/core/series.pxd +7 -3
  7. {qubx-0.1.84 → qubx-0.1.86}/src/qubx/core/series.pyx +33 -3
  8. {qubx-0.1.84 → qubx-0.1.86}/src/qubx/core/utils.pyx +1 -1
  9. qubx-0.1.86/src/qubx/math/__init__.py +1 -0
  10. {qubx-0.1.84 → qubx-0.1.86}/src/qubx/math/stats.py +21 -4
  11. {qubx-0.1.84 → qubx-0.1.86}/src/qubx/pandaz/ta.py +438 -376
  12. qubx-0.1.86/src/qubx/ta/indicators.pyx +680 -0
  13. {qubx-0.1.84 → qubx-0.1.86}/src/qubx/utils/charting/mpl_helpers.py +304 -243
  14. qubx-0.1.84/src/qubx/math/__init__.py +0 -1
  15. qubx-0.1.84/src/qubx/ta/indicators.pyx +0 -258
  16. {qubx-0.1.84 → qubx-0.1.86}/src/qubx/__init__.py +0 -0
  17. {qubx-0.1.84 → qubx-0.1.86}/src/qubx/core/__init__.py +0 -0
  18. {qubx-0.1.84 → qubx-0.1.86}/src/qubx/core/account.py +0 -0
  19. {qubx-0.1.84 → qubx-0.1.86}/src/qubx/core/basics.py +0 -0
  20. {qubx-0.1.84 → qubx-0.1.86}/src/qubx/core/helpers.py +0 -0
  21. {qubx-0.1.84 → qubx-0.1.86}/src/qubx/core/loggers.py +0 -0
  22. {qubx-0.1.84 → qubx-0.1.86}/src/qubx/core/lookups.py +0 -0
  23. {qubx-0.1.84 → qubx-0.1.86}/src/qubx/core/strategy.py +0 -0
  24. {qubx-0.1.84 → qubx-0.1.86}/src/qubx/data/readers.py +0 -0
  25. {qubx-0.1.84 → qubx-0.1.86}/src/qubx/impl/ccxt_connector.py +0 -0
  26. {qubx-0.1.84 → qubx-0.1.86}/src/qubx/impl/ccxt_customizations.py +0 -0
  27. {qubx-0.1.84 → qubx-0.1.86}/src/qubx/impl/ccxt_trading.py +0 -0
  28. {qubx-0.1.84 → qubx-0.1.86}/src/qubx/impl/ccxt_utils.py +0 -0
  29. {qubx-0.1.84 → qubx-0.1.86}/src/qubx/pandaz/__init__.py +0 -0
  30. {qubx-0.1.84 → qubx-0.1.86}/src/qubx/pandaz/utils.py +0 -0
  31. {qubx-0.1.84 → qubx-0.1.86}/src/qubx/ta/__init__.py +0 -0
  32. {qubx-0.1.84 → qubx-0.1.86}/src/qubx/trackers/__init__.py +0 -0
  33. {qubx-0.1.84 → qubx-0.1.86}/src/qubx/trackers/rebalancers.py +0 -0
  34. {qubx-0.1.84 → qubx-0.1.86}/src/qubx/utils/__init__.py +0 -0
  35. {qubx-0.1.84 → qubx-0.1.86}/src/qubx/utils/_pyxreloader.py +0 -0
  36. {qubx-0.1.84 → qubx-0.1.86}/src/qubx/utils/marketdata/binance.py +0 -0
  37. {qubx-0.1.84 → qubx-0.1.86}/src/qubx/utils/misc.py +0 -0
  38. {qubx-0.1.84 → qubx-0.1.86}/src/qubx/utils/runner.py +0 -0
  39. {qubx-0.1.84 → qubx-0.1.86}/src/qubx/utils/time.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: Qubx
3
- Version: 0.1.84
3
+ Version: 0.1.86
4
4
  Summary: Qubx - quantitative trading framework
5
5
  Home-page: https://github.com/dmarienko/Qubx
6
6
  Author: Dmitry Marienko
@@ -48,7 +48,11 @@ Description-Content-Type: text/markdown
48
48
  ⠀⠀⠱⣜⣀⣀⣈⣦⠃⠀⠀⠀
49
49
 
50
50
  ```
51
- ### How to run live trading (Only Binance spot tested)
51
+
52
+ ## Installation
53
+ > pip install qubx
54
+
55
+ ## How to run live trading (Only Binance spot tested)
52
56
  1. cd experiments/
53
57
  2. Edit strategy config file (zero_test.yaml). Testing strategy is just doing flip / flop trading once per minute (trading_allowed should be set for trading)
54
58
  3. Modify accounts config file under ./configs/.env and provide your API binance credentials (see example in example-accounts.cfg):
@@ -61,6 +65,6 @@ base_currency = USDT
61
65
  4. Run in console (-j key if want to run under jupyter console)
62
66
 
63
67
  ```
64
- > python -P ..\src\qubx\utils\runner.py configs\zero_test.yaml -a configs\.env -j
68
+ > python ..\src\qubx\utils\runner.py configs\zero_test.yaml -a configs\.env -j
65
69
  ```
66
70
 
@@ -8,7 +8,11 @@
8
8
  ⠀⠀⠱⣜⣀⣀⣈⣦⠃⠀⠀⠀
9
9
 
10
10
  ```
11
- ### How to run live trading (Only Binance spot tested)
11
+
12
+ ## Installation
13
+ > pip install qubx
14
+
15
+ ## How to run live trading (Only Binance spot tested)
12
16
  1. cd experiments/
13
17
  2. Edit strategy config file (zero_test.yaml). Testing strategy is just doing flip / flop trading once per minute (trading_allowed should be set for trading)
14
18
  3. Modify accounts config file under ./configs/.env and provide your API binance credentials (see example in example-accounts.cfg):
@@ -21,5 +25,5 @@ base_currency = USDT
21
25
  4. Run in console (-j key if want to run under jupyter console)
22
26
 
23
27
  ```
24
- > python -P ..\src\qubx\utils\runner.py configs\zero_test.yaml -a configs\.env -j
28
+ > python ..\src\qubx\utils\runner.py configs\zero_test.yaml -a configs\.env -j
25
29
  ```
@@ -103,12 +103,13 @@ def _build_extensions() -> list[Extension]:
103
103
  Extension(
104
104
  name=str(pyx.relative_to(".")).replace(os.path.sep, ".")[:-4],
105
105
  sources=[str(pyx)],
106
- include_dirs=[np.get_include()], #, *RUST_INCLUDES],
106
+ include_dirs=[np.get_include()], # , *RUST_INCLUDES],
107
107
  define_macros=define_macros,
108
108
  language="c",
109
109
  extra_link_args=extra_link_args,
110
110
  extra_compile_args=extra_compile_args,
111
- ) for pyx in itertools.chain(Path("src/qubx").rglob("*.pyx"))
111
+ )
112
+ for pyx in itertools.chain(Path("src/qubx").rglob("*.pyx"))
112
113
  ]
113
114
 
114
115
 
@@ -159,7 +160,9 @@ def _strip_unneeded_symbols() -> None:
159
160
  elif platform.system() == "Darwin":
160
161
  strip_cmd = ["strip", "-x", so]
161
162
  else:
162
- raise RuntimeError(f"Cannot strip symbols for platform {platform.system()}")
163
+ raise RuntimeError(
164
+ f"Cannot strip symbols for platform {platform.system()}"
165
+ )
163
166
  subprocess.run(
164
167
  strip_cmd, # type: ignore [arg-type] # noqa
165
168
  check=True,
@@ -176,7 +179,7 @@ def build() -> None:
176
179
  # _build_rust_libs()
177
180
  # _copy_rust_dylibs_to_project()
178
181
 
179
- if True: #not PYO3_ONLY:
182
+ if True: # not PYO3_ONLY:
180
183
  # Create C Extensions to feed into cythonize()
181
184
  extensions = _build_extensions()
182
185
  distribution = _build_distribution(extensions)
@@ -185,7 +188,7 @@ def build() -> None:
185
188
  print("Compiling C extension modules...")
186
189
  cmd: build_ext = build_ext(distribution)
187
190
  # if PARALLEL_BUILD:
188
- # cmd.parallel = os.cpu_count()
191
+ # cmd.parallel = os.cpu_count()
189
192
  cmd.ensure_finalized()
190
193
  cmd.run()
191
194
 
@@ -198,18 +201,21 @@ def build() -> None:
198
201
  _strip_unneeded_symbols()
199
202
 
200
203
 
204
+ RED, BLUE, GREEN, YLW, RES = "\033[31m", "\033[36m", "\033[32m", "\033[33m", "\033[0m"
201
205
  if __name__ == "__main__":
202
206
  qubx_platform = toml.load("pyproject.toml")["tool"]["poetry"]["version"]
203
- print("\033[36m")
207
+ print(BLUE)
204
208
  print("=====================================================================")
205
209
  print(f"Qubx Builder {qubx_platform}")
206
- print("=====================================================================\033[0m")
207
- print(f"System: {platform.system()} {platform.machine()}")
208
- # print(f"Clang: {_get_clang_version()}")
209
- # print(f"Rust: {_get_rustc_version()}")
210
- print(f"Python: {platform.python_version()}")
211
- print(f"Cython: {cython_compiler_version}")
212
- print(f"NumPy: {np.__version__}\n")
210
+ print(
211
+ "=====================================================================\033[0m"
212
+ )
213
+ print(f"System: {GREEN}{platform.system()} {platform.machine()}{RES}")
214
+ # print(f"Clang: {GREEN}{_get_clang_version()}{RES}")
215
+ # print(f"Rust: {GREEN}{_get_rustc_version()}{RES}")
216
+ print(f"Python: {GREEN}{platform.python_version()}{RES}")
217
+ print(f"Cython: {GREEN}{cython_compiler_version}{RES}")
218
+ print(f"NumPy: {GREEN}{np.__version__}{RES}\n")
213
219
 
214
220
  print(f"BUILD_MODE={BUILD_MODE}")
215
221
  print(f"BUILD_DIR={BUILD_DIR}")
@@ -222,8 +228,10 @@ if __name__ == "__main__":
222
228
  print("Starting build...")
223
229
  ts_start = datetime.datetime.now(datetime.timezone.utc)
224
230
  build()
225
- print(f"Build time: {datetime.datetime.now(datetime.timezone.utc) - ts_start}")
226
- print("\033[32m" + "Build completed" + "\033[0m")
231
+ print(
232
+ f"Build time: {YLW}{datetime.datetime.now(datetime.timezone.utc) - ts_start}{RES}"
233
+ )
234
+ print(GREEN + "Build completed" + RES)
227
235
 
228
236
  # # See if Cython is installed
229
237
  # try:
@@ -262,4 +270,4 @@ if __name__ == "__main__":
262
270
  # include_path=[np.get_include()]
263
271
  # ),
264
272
  # 'cmdclass': {'build_ext': build_ext}
265
- # })
273
+ # })
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "Qubx"
3
- version = "0.1.84"
3
+ version = "0.1.86"
4
4
  description = "Qubx - quantitative trading framework"
5
5
  authors = ["Dmitry Marienko <dmitry@gmail.com>"]
6
6
  readme = "README.md"
@@ -52,7 +52,7 @@ pytest = "^7.1.3"
52
52
  ipykernel = "^6.29.4"
53
53
 
54
54
  [build-system]
55
- requires = ["poetry-core", "setuptools", "numpy>=1.26.3", "cython==3.0.8", "toml>=0.10.2",]
55
+ requires = ["poetry-core", "setuptools", "numpy>=1.26.3", "cython==3.0.8", "toml>=0.10.2"]
56
56
  build-backend = "poetry.core.masonry.api"
57
57
 
58
58
  [tool.poetry.build]
@@ -1,7 +1,6 @@
1
1
  """"
2
2
  Here stuff we want to have in every Jupyter notebook after calling %qube magic
3
3
  """
4
- import importlib_metadata
5
4
 
6
5
  import qubx
7
6
  from qubx.utils import runtime_env
@@ -15,11 +14,19 @@ def np_fmt_short():
15
14
 
16
15
  def np_fmt_reset():
17
16
  # reset default np printing options
18
- np.set_printoptions(edgeitems=3, infstr='inf', linewidth=75, nanstr='nan', precision=8,
19
- suppress=False, threshold=1000, formatter=None)
17
+ np.set_printoptions(
18
+ edgeitems=3,
19
+ infstr="inf",
20
+ linewidth=75,
21
+ nanstr="nan",
22
+ precision=8,
23
+ suppress=False,
24
+ threshold=1000,
25
+ formatter=None,
26
+ )
20
27
 
21
28
 
22
- if runtime_env() in ['notebook', 'shell']:
29
+ if runtime_env() in ["notebook", "shell"]:
23
30
 
24
31
  # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
25
32
  # -- all imports below will appear in notebook after calling %%alphalab magic ---
@@ -39,19 +46,18 @@ if runtime_env() in ['notebook', 'shell']:
39
46
  # - - - - Learn stuff - - - -
40
47
  # - - - - Charting stuff - - - -
41
48
  from matplotlib import pyplot as plt
42
- from qubx.utils.charting.mpl_helpers import fig, subplot, sbp
49
+ from qubx.utils.charting.mpl_helpers import fig, subplot, sbp, plot_trends, ohlc_plot
43
50
 
44
51
  # - - - - Utils - - - -
45
52
  from qubx.pandaz.utils import scols, srows, ohlc_resample, continuous_periods, generate_equal_date_ranges
46
53
 
47
54
  # - setup short numpy output format
48
55
  np_fmt_short()
49
-
56
+
50
57
  # - add project home to system path
51
58
  add_project_to_system_path()
52
59
 
53
60
  # show logo first time
54
- if not hasattr(qubx.QubxMagics, '__already_initialized__'):
61
+ if not hasattr(qubx.QubxMagics, "__already_initialized__"):
55
62
  setattr(qubx.QubxMagics, "__already_initialized__", True)
56
63
  logo()
57
-
@@ -15,7 +15,7 @@ cdef class TimeSeries:
15
15
  cdef public long long timeframe
16
16
  cdef public Indexed times
17
17
  cdef public Indexed values
18
- cdef float max_series_length
18
+ cdef public float max_series_length
19
19
  cdef unsigned short _is_new_item
20
20
  cdef public str name
21
21
  cdef dict indicators # it's used for indicators caching
@@ -28,8 +28,12 @@ cdef class TimeSeries:
28
28
 
29
29
 
30
30
  cdef class Indicator(TimeSeries):
31
- cdef TimeSeries series
32
- cdef TimeSeries parent
31
+ cdef public TimeSeries series
32
+ cdef public TimeSeries parent
33
+
34
+
35
+ cdef class IndicatorOHLC(Indicator):
36
+ pass
33
37
 
34
38
 
35
39
  cdef class RollingSum:
@@ -142,6 +142,14 @@ cdef class TimeSeries:
142
142
  def __len__(self) -> int:
143
143
  return len(self.times)
144
144
 
145
+ def loc(self, str t):
146
+ _t = np.datetime64(t, 'ns').item()
147
+ ix = int(np.searchsorted(self.times.values, _t, side='right'))
148
+ ix = min(ix, len(self.values))
149
+ if ix == 0:
150
+ raise ValueError(f"Time {t} not found in {self.name} !")
151
+ return np.datetime64(self.times.values[ix - 1], 'ns'), self.values.values[ix - 1]
152
+
145
153
  def _on_attach_indicator(self, indicator: Indicator, indicator_input: TimeSeries):
146
154
  self.calculation_order.append((
147
155
  id(indicator_input), indicator, id(indicator)
@@ -169,7 +177,7 @@ cdef class TimeSeries:
169
177
  # - disable first notification because first item may be incomplete
170
178
  self._is_new_item = False
171
179
 
172
- elif time - self.times[0] >= self.timeframe:
180
+ elif (_dt := time - self.times[0]) >= self.timeframe:
173
181
  # - add new item
174
182
  self._add_new_item(item_start_time, value)
175
183
 
@@ -186,6 +194,8 @@ cdef class TimeSeries:
186
194
 
187
195
  return self._is_new_item
188
196
  else:
197
+ if _dt < 0:
198
+ raise ValueError(f"Attempt to update past data at {time_to_str(time)} !")
189
199
  self._update_last_item(item_start_time, value)
190
200
 
191
201
  # - update indicators by new data
@@ -300,6 +310,9 @@ def _wrap_indicator(series: TimeSeries, clz, *args, **kwargs):
300
310
 
301
311
 
302
312
  cdef class Indicator(TimeSeries):
313
+ """
314
+ Basic class for indicator that can be attached to TimeSeries
315
+ """
303
316
 
304
317
  def __init__(self, str name, TimeSeries series):
305
318
  if not name:
@@ -309,7 +322,7 @@ cdef class Indicator(TimeSeries):
309
322
  self.name = name
310
323
 
311
324
  # - we need to make a empty copy and fill it
312
- self.series = TimeSeries(series.name, series.timeframe, series.max_series_length)
325
+ self.series = self._instantiate_base_series(series.name, series.timeframe, series.max_series_length)
313
326
  self.parent = series
314
327
 
315
328
  # - notify the parent series that indicator has been attached
@@ -318,6 +331,9 @@ cdef class Indicator(TimeSeries):
318
331
  # - recalculate indicator on data as if it would being streamed
319
332
  self._initial_data_recalculate(series)
320
333
 
334
+ def _instantiate_base_series(self, str name, long long timeframe, float max_series_length):
335
+ return TimeSeries(name, timeframe, max_series_length)
336
+
321
337
  def _on_attach_indicator(self, indicator: Indicator, indicator_input: TimeSeries):
322
338
  self.parent._on_attach_indicator(indicator, indicator_input)
323
339
 
@@ -345,6 +361,17 @@ cdef class Indicator(TimeSeries):
345
361
  return _wrap_indicator(series, clz, *args, **kwargs)
346
362
 
347
363
 
364
+ cdef class IndicatorOHLC(Indicator):
365
+ """
366
+ Extension of indicator class to be used for OHLCV series
367
+ """
368
+ def _instantiate_base_series(self, str name, long long timeframe, float max_series_length):
369
+ return OHLCV(name, timeframe, max_series_length)
370
+
371
+ def calculate(self, long long time, Bar value, short new_item_started) -> object:
372
+ raise ValueError("Indicator must implement calculate() method")
373
+
374
+
348
375
  cdef class Lag(Indicator):
349
376
  cdef int period
350
377
 
@@ -729,7 +756,7 @@ cdef class OHLCV(TimeSeries):
729
756
  # Here we disable first notification because first item may be incomplete
730
757
  self._is_new_item = False
731
758
 
732
- elif time - self.times[0] >= self.timeframe:
759
+ elif (_dt := time - self.times[0]) >= self.timeframe:
733
760
  b = Bar(bar_start_time, price, price, price, price, volume, bvolume)
734
761
 
735
762
  # - add new item
@@ -740,6 +767,9 @@ cdef class OHLCV(TimeSeries):
740
767
 
741
768
  return self._is_new_item
742
769
  else:
770
+ if _dt < 0:
771
+ raise ValueError(f"Attempt to update past data at {time_to_str(time)} !")
772
+
743
773
  self._update_last_item(bar_start_time, self[0].update(price, volume, bvolume))
744
774
 
745
775
  # - update indicators by new data
@@ -50,5 +50,5 @@ cpdef recognize_timeframe(timeframe):
50
50
  tf = np.int64(timeframe.item().total_seconds() * NS)
51
51
 
52
52
  else:
53
- raise ValueError('Unknown timeframe type !')
53
+ raise ValueError(f'Unknown timeframe type: {timeframe} !')
54
54
  return tf
@@ -0,0 +1 @@
1
+ from .stats import compare_to_norm, percentile_rank, kde
@@ -30,13 +30,30 @@ def compare_to_norm(xs, xranges=None):
30
30
  fit = stats.norm.pdf(sorted(xs), _m, _s)
31
31
 
32
32
  sbp(12, 1)
33
- plt.plot(sorted(xs), fit, 'r--', lw=2, label='N(%.2f, %.2f)' % (_m, _s))
34
- plt.legend(loc='upper right')
33
+ plt.plot(sorted(xs), fit, "r--", lw=2, label="N(%.2f, %.2f)" % (_m, _s))
34
+ plt.legend(loc="upper right")
35
35
 
36
- sns.kdeplot(xs, color='g', label='Data', shade=True)
36
+ sns.kdeplot(xs, color="g", label="Data", fill=True)
37
37
  if xranges is not None and len(xranges) > 1:
38
38
  plt.xlim(xranges)
39
- plt.legend(loc='upper right')
39
+ plt.legend(loc="upper right")
40
40
 
41
41
  sbp(12, 2)
42
42
  stats.probplot(xs, dist="norm", sparams=(_m, _s), plot=plt)
43
+
44
+
45
+ def kde(array, cut_down=True, bw_method="scott"):
46
+ """
47
+ Kernel dense estimation
48
+ """
49
+ from scipy.stats import gaussian_kde
50
+
51
+ if cut_down:
52
+ bins, counts = np.unique(array, return_counts=True)
53
+ f_mean = counts.mean()
54
+ f_above_mean = bins[counts > f_mean]
55
+ if len(f_above_mean) > 0:
56
+ bounds = [f_above_mean.min(), f_above_mean.max()]
57
+ array = array[np.bitwise_and(bounds[0] < array, array < bounds[1])]
58
+
59
+ return gaussian_kde(array, bw_method=bw_method)