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,1183 @@
1
+ """
2
+ Misc graphics handy utilitites to be used in interactive analysis
3
+ """
4
+
5
+ import colorsys
6
+ import datetime
7
+
8
+ import matplotlib
9
+ import matplotlib.colors as mc
10
+ import matplotlib.pyplot as plt
11
+ import matplotlib.ticker as mticker
12
+ import numpy as np
13
+ import pandas as pd
14
+ import statsmodels.tsa.stattools as st
15
+ from cycler import cycler
16
+ from matplotlib import colors as mcolors
17
+ from matplotlib import pyplot as plt
18
+ from matplotlib.collections import LineCollection, PolyCollection
19
+ from matplotlib.dates import date2num, num2date
20
+ from matplotlib.lines import TICKLEFT, TICKRIGHT, Line2D
21
+ from matplotlib.patches import Rectangle
22
+ from matplotlib.transforms import Affine2D
23
+
24
+ from qubx.utils.misc import Struct
25
+ from qubx.utils.time import infer_series_frequency
26
+
27
+ DARK_MATLPLOT_THEME = [
28
+ ("backend", "module://matplotlib_inline.backend_inline"),
29
+ ("interactive", True),
30
+ ("lines.color", "#5050f0"),
31
+ ("text.color", "#d0d0d0"),
32
+ ("axes.facecolor", "#000000"),
33
+ ("axes.edgecolor", "#404040"),
34
+ ("axes.grid", True),
35
+ ("axes.labelsize", "large"),
36
+ ("axes.labelcolor", "green"),
37
+ (
38
+ "axes.prop_cycle",
39
+ cycler(
40
+ "color",
41
+ [
42
+ "#08F7FE",
43
+ "#00ff41",
44
+ "#FE53BB",
45
+ "#F5D300",
46
+ "#449AcD",
47
+ "g",
48
+ "#f62841",
49
+ "y",
50
+ "#088487",
51
+ "#E24A33",
52
+ "#f01010",
53
+ ],
54
+ ),
55
+ ),
56
+ ("legend.fontsize", "small"),
57
+ ("legend.fancybox", False),
58
+ ("legend.edgecolor", "#305030"),
59
+ ("legend.shadow", False),
60
+ ("lines.antialiased", True),
61
+ ("lines.linewidth", 0.8), # reduced line width
62
+ ("patch.linewidth", 0.5),
63
+ ("patch.antialiased", True),
64
+ ("xtick.color", "#909090"),
65
+ ("ytick.color", "#909090"),
66
+ ("xtick.labelsize", "large"),
67
+ ("ytick.labelsize", "large"),
68
+ ("grid.color", "#404040"),
69
+ ("grid.linestyle", "--"),
70
+ ("grid.linewidth", 0.5),
71
+ ("grid.alpha", 0.8),
72
+ ("figure.figsize", [12.0, 5.0]),
73
+ ("figure.dpi", 80.0),
74
+ ("figure.facecolor", "#050505"),
75
+ ("figure.edgecolor", (1, 1, 1, 0)),
76
+ ("figure.subplot.bottom", 0.125),
77
+ ("savefig.facecolor", "#000000"),
78
+ ]
79
+
80
+ LIGHT_MATPLOT_THEME = [
81
+ ("backend", "module://matplotlib_inline.backend_inline"),
82
+ ("interactive", True),
83
+ ("lines.color", "#101010"),
84
+ ("text.color", "#303030"),
85
+ ("lines.antialiased", True),
86
+ ("lines.linewidth", 1),
87
+ ("patch.linewidth", 0.5),
88
+ ("patch.facecolor", "#348ABD"),
89
+ ("patch.edgecolor", "#eeeeee"),
90
+ ("patch.antialiased", True),
91
+ ("axes.facecolor", "#fafafa"),
92
+ ("axes.edgecolor", "#d0d0d0"),
93
+ ("axes.linewidth", 1),
94
+ ("axes.titlesize", "x-large"),
95
+ ("axes.labelsize", "large"),
96
+ ("axes.labelcolor", "#555555"),
97
+ ("axes.axisbelow", True),
98
+ ("axes.grid", True),
99
+ (
100
+ "axes.prop_cycle",
101
+ cycler(
102
+ "color",
103
+ [
104
+ "#6792E0",
105
+ "#27ae60",
106
+ "#c44e52",
107
+ "#975CC3",
108
+ "#ff914d",
109
+ "#77BEDB",
110
+ "#303030",
111
+ "#4168B7",
112
+ "#93B851",
113
+ "#e74c3c",
114
+ "#bc89e0",
115
+ "#ff711a",
116
+ "#3498db",
117
+ "#6C7A89",
118
+ ],
119
+ ),
120
+ ),
121
+ ("legend.fontsize", "small"),
122
+ ("legend.fancybox", False),
123
+ ("xtick.color", "#707070"),
124
+ ("ytick.color", "#707070"),
125
+ ("grid.color", "#606060"),
126
+ ("grid.linestyle", "--"),
127
+ ("grid.linewidth", 0.5),
128
+ ("grid.alpha", 0.3),
129
+ ("figure.figsize", [8.0, 5.0]),
130
+ ("figure.dpi", 80.0),
131
+ ("figure.facecolor", "#ffffff"),
132
+ ("figure.edgecolor", "#ffffff"),
133
+ ("figure.subplot.bottom", 0.1),
134
+ ]
135
+
136
+
137
+ def fig(w=16, h=5, dpi=96, facecolor=None, edgecolor=None, num=None):
138
+ """
139
+ Simple helper for creating figure
140
+ """
141
+ return plt.figure(num=num, figsize=(w, h), dpi=dpi, facecolor=facecolor, edgecolor=edgecolor)
142
+
143
+
144
+ def subplot(shape, loc, rowspan=2, colspan=1):
145
+ """
146
+ Some handy grid splitting for plots. Example for 2x2:
147
+
148
+ >>> subplot(22, 1); plt.plot([-1,2,-3])
149
+ >>> subplot(22, 2); plt.plot([1,2,3])
150
+ >>> subplot(22, 3); plt.plot([1,2,3])
151
+ >>> subplot(22, 4); plt.plot([3,-2,1])
152
+
153
+ same as following
154
+
155
+ >>> subplot((2,2), (0,0)); plt.plot([-1,2,-3])
156
+ >>> subplot((2,2), (0,1)); plt.plot([1,2,3])
157
+ >>> subplot((2,2), (1,0)); plt.plot([1,2,3])
158
+ >>> subplot((2,2), (1,1)); plt.plot([3,-2,1])
159
+
160
+ :param shape: scalar (like matlab subplot) or tuple
161
+ :param loc: scalar (like matlab subplot) or tuple
162
+ :param rowspan: rows spanned
163
+ :param colspan: columns spanned
164
+ """
165
+ isscalar = lambda x: not isinstance(x, (list, tuple, dict, np.ndarray))
166
+
167
+ if isscalar(shape):
168
+ if 0 < shape < 100:
169
+ shape = (max(shape // 10, 1), max(shape % 10, 1))
170
+ else:
171
+ raise ValueError("Wrong scalar value for shape. It should be in range (1...99)")
172
+
173
+ if isscalar(loc):
174
+ nm = max(shape[0], 1) * max(shape[1], 1)
175
+ if 0 < loc <= nm:
176
+ x = (loc - 1) // shape[1]
177
+ y = loc - 1 - shape[1] * x
178
+ loc = (x, y)
179
+ else:
180
+ raise ValueError("Wrong scalar value for location. It should be in range (1...%d)" % nm)
181
+
182
+ return plt.subplot2grid(shape, loc=loc, rowspan=rowspan, colspan=colspan)
183
+
184
+
185
+ def sbp(shape, loc, r=1, c=1):
186
+ """
187
+ Just shortcut for subplot(...) function
188
+
189
+ :param shape: scalar (like matlab subplot) or tuple
190
+ :param loc: scalar (like matlab subplot) or tuple
191
+ :param r: rows spanned
192
+ :param c: columns spanned
193
+ :return:
194
+ """
195
+ return subplot(shape, loc, rowspan=r, colspan=c)
196
+
197
+
198
+ def vline(ax, x, c, lw=1, ls="--"):
199
+ x = pd.to_datetime(x) if isinstance(x, str) else x
200
+ if not isinstance(ax, (list, tuple)):
201
+ ax = [ax]
202
+ for a in ax:
203
+ a.axvline(x, 0, 1, c=c, lw=1, linestyle=ls)
204
+
205
+
206
+ def hline(*zs, mirror=True):
207
+ [plt.axhline(z, ls="--", c="r", lw=0.5) for z in zs]
208
+ if mirror:
209
+ [plt.axhline(-z, ls="--", c="r", lw=0.5) for z in zs]
210
+
211
+
212
+ def ellips(ax, x, y, c="r", r=2.5, lw=2, ls="-"):
213
+ """
214
+ Draw ellips annotation on specified plot at (x,y) point
215
+ """
216
+ from matplotlib.patches import Ellipse
217
+
218
+ x = pd.to_datetime(x) if isinstance(x, str) else x
219
+ w, h = (r, r) if np.isscalar(r) else (r[0], r[1])
220
+ ax.add_artist(Ellipse(xy=[x, y], width=w, height=h, angle=0, fill=False, color=c, lw=lw, ls=ls))
221
+
222
+
223
+ def set_mpl_theme(theme: str):
224
+ import plotly.io as pio
225
+
226
+ if "dark" in theme.lower():
227
+ pio.templates.default = "plotly_dark"
228
+
229
+ # fmt: off
230
+ pio.templates[pio.templates.default]["layout"]["plot_bgcolor"] = "rgb(5,5,5)"
231
+ pio.templates[pio.templates.default]["layout"]["paper_bgcolor"] = "rgb(5,5,5)"
232
+ pio.templates[pio.templates.default]["layout"]["xaxis"]["gridcolor"] = "#171717"
233
+ pio.templates[pio.templates.default]["layout"]["yaxis"]["gridcolor"] = "#171717"
234
+ pio.templates[pio.templates.default]["layout"]["font"]["color"] = "#d0d0d0"
235
+ pio.templates[pio.templates.default]["layout"]["colorway"] = [
236
+ "#08F7FE", "#00ff41", "#FE53BB", "#F5D300", "#449AcD",
237
+ "green", "#f62841", "yellow", "#088487", "#E24A33", "#f01010",
238
+ ]
239
+ # fmt: on
240
+
241
+ for k, v in DARK_MATLPLOT_THEME:
242
+ matplotlib.rcParams[k] = v
243
+
244
+ elif "light" in theme.lower():
245
+ pio.templates.default = "plotly_white"
246
+ for k, v in LIGHT_MATPLOT_THEME:
247
+ matplotlib.rcParams[k] = v
248
+
249
+
250
+ def adjust_lightness(color, amount=0.5):
251
+ try:
252
+ c = mc.cnames[color]
253
+ except: # noqa: E722
254
+ c = color
255
+ c = colorsys.rgb_to_hls(*mc.to_rgb(c))
256
+ return colorsys.hls_to_rgb(c[0], max(0, min(1, amount * c[1])), c[2])
257
+
258
+
259
+ def plot_day_summary_oclh(ax, quotes, ticksize=3, colorup="k", colordown="r"):
260
+ """Plots day summary
261
+
262
+ Represent the time, open, close, high, low as a vertical line
263
+ ranging from low to high. The left tick is the open and the right
264
+ tick is the close.
265
+
266
+ Parameters
267
+ ----------
268
+ ax : `Axes`
269
+ an `Axes` instance to plot to
270
+ quotes : sequence of (time, open, close, high, low, ...) sequences
271
+ data to plot. time must be in float date format - see date2num
272
+ ticksize : int
273
+ open/close tick marker in points
274
+ colorup : color
275
+ the color of the lines where close >= open
276
+ colordown : color
277
+ the color of the lines where close < open
278
+
279
+ Returns
280
+ -------
281
+ lines : list
282
+ list of tuples of the lines added (one tuple per quote)
283
+ """
284
+ return _plot_day_summary(ax, quotes, ticksize=ticksize, colorup=colorup, colordown=colordown, ochl=True)
285
+
286
+
287
+ def plot_day_summary_ohlc(ax, quotes, ticksize=3, colorup="k", colordown="r"):
288
+ """Plots day summary
289
+
290
+ Represent the time, open, high, low, close as a vertical line
291
+ ranging from low to high. The left tick is the open and the right
292
+ tick is the close.
293
+
294
+ Parameters
295
+ ----------
296
+ ax : `Axes`
297
+ an `Axes` instance to plot to
298
+ quotes : sequence of (time, open, high, low, close, ...) sequences
299
+ data to plot. time must be in float date format - see date2num
300
+ ticksize : int
301
+ open/close tick marker in points
302
+ colorup : color
303
+ the color of the lines where close >= open
304
+ colordown : color
305
+ the color of the lines where close < open
306
+
307
+ Returns
308
+ -------
309
+ lines : list
310
+ list of tuples of the lines added (one tuple per quote)
311
+ """
312
+ return _plot_day_summary(ax, quotes, ticksize=ticksize, colorup=colorup, colordown=colordown, ochl=False)
313
+
314
+
315
+ def _plot_day_summary(ax, quotes, ticksize=3, colorup="k", colordown="r", ochl=True):
316
+ """Plots day summary
317
+
318
+
319
+ Represent the time, open, high, low, close as a vertical line
320
+ ranging from low to high. The left tick is the open and the right
321
+ tick is the close.
322
+
323
+ Parameters
324
+ ----------
325
+ ax : `Axes`
326
+ an `Axes` instance to plot to
327
+ quotes : sequence of quote sequences
328
+ data to plot. time must be in float date format - see date2num
329
+ (time, open, high, low, close, ...) vs
330
+ (time, open, close, high, low, ...)
331
+ set by `ochl`
332
+ ticksize : int
333
+ open/close tick marker in points
334
+ colorup : color
335
+ the color of the lines where close >= open
336
+ colordown : color
337
+ the color of the lines where close < open
338
+ ochl: bool
339
+ argument to select between ochl and ohlc ordering of quotes
340
+
341
+ Returns
342
+ -------
343
+ lines : list
344
+ list of tuples of the lines added (one tuple per quote)
345
+ """
346
+ # unfortunately this has a different return type than plot_day_summary2_*
347
+ lines = []
348
+ for q in quotes:
349
+ if ochl:
350
+ t, open, close, high, low = q[:5]
351
+ else:
352
+ t, open, high, low, close = q[:5]
353
+
354
+ if close >= open:
355
+ color = colorup
356
+ else:
357
+ color = colordown
358
+
359
+ vline = Line2D(
360
+ xdata=(t, t),
361
+ ydata=(low, high),
362
+ color=color,
363
+ antialiased=False, # no need to antialias vert lines
364
+ )
365
+
366
+ oline = Line2D(
367
+ xdata=(t, t),
368
+ ydata=(open, open),
369
+ color=color,
370
+ antialiased=False,
371
+ marker=TICKLEFT,
372
+ markersize=ticksize,
373
+ )
374
+
375
+ cline = Line2D(
376
+ xdata=(t, t), ydata=(close, close), color=color, antialiased=False, markersize=ticksize, marker=TICKRIGHT
377
+ )
378
+
379
+ lines.extend((vline, oline, cline))
380
+ ax.add_line(vline)
381
+ ax.add_line(oline)
382
+ ax.add_line(cline)
383
+
384
+ ax.autoscale_view()
385
+
386
+ return lines
387
+
388
+
389
+ def candlestick_ochl(ax, quotes, width=0.2, colorup="k", colordown="r", alpha=1.0):
390
+ """
391
+ Plot the time, open, close, high, low as a vertical line ranging
392
+ from low to high. Use a rectangular bar to represent the
393
+ open-close span. If close >= open, use colorup to color the bar,
394
+ otherwise use colordown
395
+
396
+ Parameters
397
+ ----------
398
+ ax : `Axes`
399
+ an Axes instance to plot to
400
+ quotes : sequence of (time, open, close, high, low, ...) sequences
401
+ As long as the first 5 elements are these values,
402
+ the record can be as long as you want (e.g., it may store volume).
403
+
404
+ time must be in float days format - see date2num
405
+
406
+ width : float
407
+ fraction of a day for the rectangle width
408
+ colorup : color
409
+ the color of the rectangle where close >= open
410
+ colordown : color
411
+ the color of the rectangle where close < open
412
+ alpha : float
413
+ the rectangle alpha level
414
+
415
+ Returns
416
+ -------
417
+ ret : tuple
418
+ returns (lines, patches) where lines is a list of lines
419
+ added and patches is a list of the rectangle patches added
420
+
421
+ """
422
+ return _candlestick(ax, quotes, width=width, colorup=colorup, colordown=colordown, alpha=alpha, ochl=True)
423
+
424
+
425
+ def candlestick_ohlc(ax, quotes, width=0.2, colorup="k", colordown="r", alpha=1.0):
426
+ """
427
+ Plot the time, open, high, low, close as a vertical line ranging
428
+ from low to high. Use a rectangular bar to represent the
429
+ open-close span. If close >= open, use colorup to color the bar,
430
+ otherwise use colordown
431
+
432
+ Parameters
433
+ ----------
434
+ ax : `Axes`
435
+ an Axes instance to plot to
436
+ quotes : sequence of (time, open, high, low, close, ...) sequences
437
+ As long as the first 5 elements are these values,
438
+ the record can be as long as you want (e.g., it may store volume).
439
+
440
+ time must be in float days format - see date2num
441
+
442
+ width : float
443
+ fraction of a day for the rectangle width
444
+ colorup : color
445
+ the color of the rectangle where close >= open
446
+ colordown : color
447
+ the color of the rectangle where close < open
448
+ alpha : float
449
+ the rectangle alpha level
450
+
451
+ Returns
452
+ -------
453
+ ret : tuple
454
+ returns (lines, patches) where lines is a list of lines
455
+ added and patches is a list of the rectangle patches added
456
+
457
+ """
458
+ return _candlestick(ax, quotes, width=width, colorup=colorup, colordown=colordown, alpha=alpha, ochl=False)
459
+
460
+
461
+ def _candlestick(ax, quotes, width=0.2, colorup="k", colordown="r", alpha=1.0, ochl=True):
462
+ """
463
+ Plot the time, open, high, low, close as a vertical line ranging
464
+ from low to high. Use a rectangular bar to represent the
465
+ open-close span. If close >= open, use colorup to color the bar,
466
+ otherwise use colordown
467
+
468
+ Parameters
469
+ ----------
470
+ ax : `Axes`
471
+ an Axes instance to plot to
472
+ quotes : sequence of quote sequences
473
+ data to plot. time must be in float date format - see date2num
474
+ (time, open, high, low, close, ...) vs
475
+ (time, open, close, high, low, ...)
476
+ set by `ochl`
477
+ width : float
478
+ fraction of a day for the rectangle width
479
+ colorup : color
480
+ the color of the rectangle where close >= open
481
+ colordown : color
482
+ the color of the rectangle where close < open
483
+ alpha : float
484
+ the rectangle alpha level
485
+ ochl: bool
486
+ argument to select between ochl and ohlc ordering of quotes
487
+
488
+ Returns
489
+ -------
490
+ ret : tuple
491
+ returns (lines, patches) where lines is a list of lines
492
+ added and patches is a list of the rectangle patches added
493
+
494
+ """
495
+
496
+ OFFSET = width / 2.0
497
+
498
+ lines = []
499
+ patches = []
500
+ for q in quotes:
501
+ if ochl:
502
+ t, open, close, high, low = q[:5]
503
+ else:
504
+ t, open, high, low, close = q[:5]
505
+
506
+ if close >= open:
507
+ color = colorup
508
+ edgecolor = adjust_lightness(color, 1.5)
509
+ lower = open
510
+ height = close - open
511
+ else:
512
+ color = colordown
513
+ edgecolor = adjust_lightness(color, 1.2)
514
+ lower = close
515
+ height = open - close
516
+
517
+ vline = Line2D(
518
+ xdata=(t, t),
519
+ ydata=(low, high),
520
+ # color=adjust_lightness(color, 1.3),
521
+ color=color,
522
+ linewidth=1,
523
+ antialiased=True,
524
+ )
525
+
526
+ rect = Rectangle(
527
+ xy=(t - OFFSET, lower),
528
+ width=width,
529
+ height=height,
530
+ facecolor=color,
531
+ # edgecolor=color,
532
+ # facecolor=adjust_lightness(color, 1.3),
533
+ edgecolor=edgecolor,
534
+ lw=0.75,
535
+ )
536
+ rect.set_alpha(alpha)
537
+
538
+ lines.append(vline)
539
+ patches.append(rect)
540
+ ax.add_line(vline)
541
+ ax.add_patch(rect)
542
+ ax.autoscale_view()
543
+
544
+ return lines, patches
545
+
546
+
547
+ def _check_input(opens, closes, highs, lows, miss=-1):
548
+ """Checks that *opens*, *highs*, *lows* and *closes* have the same length.
549
+ NOTE: this code assumes if any value open, high, low, close is
550
+ missing (*-1*) they all are missing
551
+
552
+ Parameters
553
+ ----------
554
+ ax : `Axes` an Axes instance to plot to
555
+ opens : sequence of opening values
556
+ highs : sequence of high values
557
+ lows : sequence of low values
558
+ closes : sequence of closing values
559
+ miss : identifier of the missing data
560
+
561
+ Raises
562
+ ------
563
+ ValueError
564
+ if the input sequences don't have the same length
565
+ """
566
+
567
+ def _missing(sequence, miss=-1):
568
+ """Returns the index in *sequence* of the missing data, identified by
569
+ *miss*
570
+
571
+ Parameters
572
+ ----------
573
+ sequence :
574
+ sequence to evaluate
575
+ miss :
576
+ identifier of the missing data
577
+
578
+ Returns
579
+ -------
580
+ where_miss: numpy.ndarray
581
+ indices of the missing data
582
+ """
583
+ return np.where(np.array(sequence) == miss)[0]
584
+
585
+ same_length = len(opens) == len(highs) == len(lows) == len(closes)
586
+ _missopens = _missing(opens)
587
+ same_missing = (
588
+ (_missopens == _missing(highs)).all()
589
+ and (_missopens == _missing(lows)).all()
590
+ and (_missopens == _missing(closes)).all()
591
+ )
592
+
593
+ if not (same_length and same_missing):
594
+ msg = (
595
+ "*opens*, *highs*, *lows* and *closes* must have the same"
596
+ " length. NOTE: this code assumes if any value open, high,"
597
+ " low, close is missing (*-1*) they all must be missing."
598
+ )
599
+ raise ValueError(msg)
600
+
601
+
602
+ def plot_day_summary2_ochl(ax, opens, closes, highs, lows, ticksize=4, colorup="k", colordown="r"):
603
+ """Represent the time, open, close, high, low, as a vertical line
604
+ ranging from low to high. The left tick is the open and the right
605
+ tick is the close.
606
+
607
+ Parameters
608
+ ----------
609
+ ax : `Axes`
610
+ an Axes instance to plot to
611
+ opens : sequence of opening values
612
+ closes : sequence of closing values
613
+ highs : sequence of high values
614
+ lows : sequence of low values
615
+ ticksize : int
616
+ size of open and close ticks in points
617
+ colorup : color
618
+ the color of the lines where close >= open
619
+ colordown : color
620
+ the color of the lines where close < open
621
+
622
+ Returns
623
+ -------
624
+ ret : list
625
+ a list of lines added to the axes
626
+ """
627
+
628
+ return plot_day_summary2_ohlc(ax, opens, highs, lows, closes, ticksize, colorup, colordown)
629
+
630
+
631
+ def plot_day_summary2_ohlc(ax, opens, highs, lows, closes, ticksize=4, colorup="k", colordown="r"):
632
+ """Represent the time, open, high, low, close as a vertical line
633
+ ranging from low to high. The left tick is the open and the right
634
+ tick is the close.
635
+ *opens*, *highs*, *lows* and *closes* must have the same length.
636
+ NOTE: this code assumes if any value open, high, low, close is
637
+ missing (*-1*) they all are missing
638
+
639
+ Parameters
640
+ ----------
641
+ ax : `Axes`
642
+ an Axes instance to plot to
643
+ opens : sequence
644
+ sequence of opening values
645
+ highs : sequence
646
+ sequence of high values
647
+ lows : sequence
648
+ sequence of low values
649
+ closes : sequence
650
+ sequence of closing values
651
+ ticksize : int
652
+ size of open and close ticks in points
653
+ colorup : color
654
+ the color of the lines where close >= open
655
+ colordown : color
656
+ the color of the lines where close < open
657
+
658
+ Returns
659
+ -------
660
+ ret : list
661
+ a list of lines added to the axes
662
+ """
663
+
664
+ _check_input(opens, highs, lows, closes)
665
+
666
+ rangeSegments = [((i, low), (i, high)) for i, low, high in zip(range(len(lows)), lows, highs) if low != -1]
667
+
668
+ # the ticks will be from ticksize to 0 in points at the origin and
669
+ # we'll translate these to the i, close location
670
+ openSegments = [((-ticksize, 0), (0, 0))]
671
+
672
+ # the ticks will be from 0 to ticksize in points at the origin and
673
+ # we'll translate these to the i, close location
674
+ closeSegments = [((0, 0), (ticksize, 0))]
675
+
676
+ offsetsOpen = [(i, open) for i, open in zip(range(len(opens)), opens) if open != -1]
677
+
678
+ offsetsClose = [(i, close) for i, close in zip(range(len(closes)), closes) if close != -1]
679
+
680
+ scale = ax.figure.dpi * (1.0 / 72.0)
681
+
682
+ tickTransform = Affine2D().scale(scale, 0.0)
683
+
684
+ colorup = mcolors.to_rgba(colorup)
685
+ colordown = mcolors.to_rgba(colordown)
686
+ colord = {True: colorup, False: colordown}
687
+ colors = [colord[open < close] for open, close in zip(opens, closes) if open != -1 and close != -1]
688
+
689
+ useAA = (0,) # use tuple here
690
+ lw = (1,) # and here
691
+ rangeCollection = LineCollection(
692
+ rangeSegments,
693
+ colors=colors,
694
+ linewidths=lw,
695
+ antialiaseds=useAA,
696
+ )
697
+
698
+ openCollection = LineCollection(
699
+ openSegments,
700
+ colors=colors,
701
+ antialiaseds=useAA,
702
+ linewidths=lw,
703
+ offsets=offsetsOpen,
704
+ transOffset=ax.transData,
705
+ )
706
+ openCollection.set_transform(tickTransform)
707
+
708
+ closeCollection = LineCollection(
709
+ closeSegments,
710
+ colors=colors,
711
+ antialiaseds=useAA,
712
+ linewidths=lw,
713
+ offsets=offsetsClose,
714
+ transOffset=ax.transData,
715
+ )
716
+ closeCollection.set_transform(tickTransform)
717
+
718
+ minpy, maxx = (0, len(rangeSegments))
719
+ miny = min([low for low in lows if low != -1])
720
+ maxy = max([high for high in highs if high != -1])
721
+ corners = (minpy, miny), (maxx, maxy)
722
+ ax.update_datalim(corners)
723
+ ax.autoscale_view()
724
+
725
+ # add these last
726
+ ax.add_collection(rangeCollection)
727
+ ax.add_collection(openCollection)
728
+ ax.add_collection(closeCollection)
729
+ return rangeCollection, openCollection, closeCollection
730
+
731
+
732
+ def candlestick2_ochl(ax, opens, closes, highs, lows, width=4, colorup="k", colordown="r", alpha=0.75):
733
+ """Represent the open, close as a bar line and high low range as a
734
+ vertical line.
735
+
736
+ Preserves the original argument order.
737
+
738
+
739
+ Parameters
740
+ ----------
741
+ ax : `Axes`
742
+ an Axes instance to plot to
743
+ opens : sequence
744
+ sequence of opening values
745
+ closes : sequence
746
+ sequence of closing values
747
+ highs : sequence
748
+ sequence of high values
749
+ lows : sequence
750
+ sequence of low values
751
+ width : int
752
+ size of open and close ticks in points
753
+ colorup : color
754
+ the color of the lines where close >= open
755
+ colordown : color
756
+ the color of the lines where close < open
757
+ alpha : float
758
+ bar transparency
759
+
760
+ Returns
761
+ -------
762
+ ret : tuple
763
+ (lineCollection, barCollection)
764
+ """
765
+
766
+ return candlestick2_ohlc(
767
+ ax, opens, highs, lows, closes, width=width, colorup=colorup, colordown=colordown, alpha=alpha
768
+ )
769
+
770
+
771
+ def candlestick2_ohlc(ax, opens, highs, lows, closes, width=4, colorup="k", colordown="r", alpha=0.75):
772
+ """Represent the open, close as a bar line and high low range as a
773
+ vertical line.
774
+
775
+ NOTE: this code assumes if any value open, low, high, close is
776
+ missing they all are missing
777
+
778
+
779
+ Parameters
780
+ ----------
781
+ ax : `Axes`
782
+ an Axes instance to plot to
783
+ opens : sequence
784
+ sequence of opening values
785
+ highs : sequence
786
+ sequence of high values
787
+ lows : sequence
788
+ sequence of low values
789
+ closes : sequence
790
+ sequence of closing values
791
+ width : int
792
+ size of open and close ticks in points
793
+ colorup : color
794
+ the color of the lines where close >= open
795
+ colordown : color
796
+ the color of the lines where close < open
797
+ alpha : float
798
+ bar transparency
799
+
800
+ Returns
801
+ -------
802
+ ret : tuple
803
+ (lineCollection, barCollection)
804
+ """
805
+
806
+ _check_input(opens, highs, lows, closes)
807
+
808
+ delta = width / 2.0
809
+ barVerts = [
810
+ ((i - delta, open), (i - delta, close), (i + delta, close), (i + delta, open))
811
+ for i, open, close in zip(range(len(opens)), opens, closes)
812
+ if open != -1 and close != -1
813
+ ]
814
+
815
+ rangeSegments = [((i, low), (i, high)) for i, low, high in zip(range(len(lows)), lows, highs) if low != -1]
816
+
817
+ colorup = mcolors.to_rgba(colorup, alpha)
818
+ colordown = mcolors.to_rgba(colordown, alpha)
819
+ colord = {True: colorup, False: colordown}
820
+ colors = [colord[open < close] for open, close in zip(opens, closes) if open != -1 and close != -1]
821
+
822
+ useAA = (0,) # use tuple here
823
+ lw = (0.5,) # and here
824
+ rangeCollection = LineCollection(
825
+ rangeSegments,
826
+ colors=colors,
827
+ linewidths=lw,
828
+ antialiaseds=useAA,
829
+ )
830
+
831
+ barCollection = PolyCollection(
832
+ barVerts,
833
+ facecolors=colors,
834
+ edgecolors=colors,
835
+ antialiaseds=useAA,
836
+ linewidths=lw,
837
+ )
838
+
839
+ minx, maxx = 0, len(rangeSegments)
840
+ miny = min([low for low in lows if low != -1])
841
+ maxy = max([high for high in highs if high != -1])
842
+
843
+ corners = (minx, miny), (maxx, maxy)
844
+ ax.update_datalim(corners)
845
+ ax.autoscale_view()
846
+
847
+ # add these last
848
+ ax.add_collection(rangeCollection)
849
+ ax.add_collection(barCollection)
850
+ return rangeCollection, barCollection
851
+
852
+
853
+ def volume_overlay(ax, opens, closes, volumes, colorup="g", colordown="r", width=4, alpha=1.0):
854
+ """Add a volume overlay to the current axes. The opens and closes
855
+ are used to determine the color of the bar. -1 is missing. If a
856
+ value is missing on one it must be missing on all
857
+
858
+ Parameters
859
+ ----------
860
+ ax : `Axes`
861
+ an Axes instance to plot to
862
+ opens : sequence
863
+ a sequence of opens
864
+ closes : sequence
865
+ a sequence of closes
866
+ volumes : sequence
867
+ a sequence of volumes
868
+ width : int
869
+ the bar width in points
870
+ colorup : color
871
+ the color of the lines where close >= open
872
+ colordown : color
873
+ the color of the lines where close < open
874
+ alpha : float
875
+ bar transparency
876
+
877
+ Returns
878
+ -------
879
+ ret : `barCollection`
880
+ The `barrCollection` added to the axes
881
+
882
+ """
883
+
884
+ colorup = mcolors.to_rgba(colorup, alpha)
885
+ colordown = mcolors.to_rgba(colordown, alpha)
886
+ colord = {True: colorup, False: colordown}
887
+ colors = [colord[open < close] for open, close in zip(opens, closes) if open != -1 and close != -1]
888
+
889
+ delta = width / 2.0
890
+ bars = [((i - delta, 0), (i - delta, v), (i + delta, v), (i + delta, 0)) for i, v in enumerate(volumes) if v != -1]
891
+
892
+ barCollection = PolyCollection(
893
+ bars,
894
+ facecolors=colors,
895
+ edgecolors=((0, 0, 0, 1),),
896
+ antialiaseds=(0,),
897
+ linewidths=(0.5,),
898
+ )
899
+
900
+ ax.add_collection(barCollection)
901
+ corners = (0, 0), (len(bars), max(volumes))
902
+ ax.update_datalim(corners)
903
+ ax.autoscale_view()
904
+
905
+ # add these last
906
+ return barCollection
907
+
908
+
909
+ def volume_overlay2(ax, closes, volumes, colorup="g", colordown="r", width=4, alpha=1.0):
910
+ """
911
+ Add a volume overlay to the current axes. The closes are used to
912
+ determine the color of the bar. -1 is missing. If a value is
913
+ missing on one it must be missing on all
914
+
915
+ nb: first point is not displayed - it is used only for choosing the
916
+ right color
917
+
918
+
919
+ Parameters
920
+ ----------
921
+ ax : `Axes`
922
+ an Axes instance to plot to
923
+ closes : sequence
924
+ a sequence of closes
925
+ volumes : sequence
926
+ a sequence of volumes
927
+ width : int
928
+ the bar width in points
929
+ colorup : color
930
+ the color of the lines where close >= open
931
+ colordown : color
932
+ the color of the lines where close < open
933
+ alpha : float
934
+ bar transparency
935
+
936
+ Returns
937
+ -------
938
+ ret : `barCollection`
939
+ The `barrCollection` added to the axes
940
+
941
+ """
942
+
943
+ return volume_overlay(ax, closes[:-1], closes[1:], volumes[1:], colorup, colordown, width, alpha)
944
+
945
+
946
+ def volume_overlay3(ax, quotes, colorup="g", colordown="r", width=4, alpha=1.0):
947
+ """Add a volume overlay to the current axes. quotes is a list of (d,
948
+ open, high, low, close, volume) and close-open is used to
949
+ determine the color of the bar
950
+
951
+ Parameters
952
+ ----------
953
+ ax : `Axes`
954
+ an Axes instance to plot to
955
+ quotes : sequence of (time, open, high, low, close, ...) sequences
956
+ data to plot. time must be in float date format - see date2num
957
+ width : int
958
+ the bar width in points
959
+ colorup : color
960
+ the color of the lines where close1 >= close0
961
+ colordown : color
962
+ the color of the lines where close1 < close0
963
+ alpha : float
964
+ bar transparency
965
+
966
+ Returns
967
+ -------
968
+ ret : `barCollection`
969
+ The `barrCollection` added to the axes
970
+
971
+
972
+ """
973
+
974
+ colorup = mcolors.to_rgba(colorup, alpha)
975
+ colordown = mcolors.to_rgba(colordown, alpha)
976
+ colord = {True: colorup, False: colordown}
977
+
978
+ dates, opens, highs, lows, closes, volumes = list(zip(*quotes))
979
+ colors = [
980
+ colord[close1 >= close0] for close0, close1 in zip(closes[:-1], closes[1:]) if close0 != -1 and close1 != -1
981
+ ]
982
+ colors.insert(0, colord[closes[0] >= opens[0]])
983
+
984
+ right = width / 2.0
985
+ left = -width / 2.0
986
+
987
+ bars = [((left, 0), (left, volume), (right, volume), (right, 0)) for d, open, high, low, close, volume in quotes]
988
+
989
+ sx = ax.figure.dpi * (1.0 / 72.0) # scale for points
990
+ sy = ax.bbox.height / ax.viewLim.height
991
+
992
+ barTransform = Affine2D().scale(sx, sy)
993
+
994
+ dates = [d for d, open, high, low, close, volume in quotes]
995
+ offsetsBars = [(d, 0) for d in dates]
996
+
997
+ useAA = (0,) # use tuple here
998
+ lw = (0.5,) # and here
999
+ barCollection = PolyCollection(
1000
+ bars,
1001
+ facecolors=colors,
1002
+ edgecolors=((0, 0, 0, 1),),
1003
+ antialiaseds=useAA,
1004
+ linewidths=lw,
1005
+ offsets=offsetsBars,
1006
+ transOffset=ax.transData,
1007
+ )
1008
+ barCollection.set_transform(barTransform)
1009
+
1010
+ minpy, maxx = (min(dates), max(dates))
1011
+ miny = 0
1012
+ maxy = max([volume for d, open, high, low, close, volume in quotes])
1013
+ corners = (minpy, miny), (maxx, maxy)
1014
+ ax.update_datalim(corners)
1015
+ # print 'datalim', ax.dataLim.bounds
1016
+ # print 'viewlim', ax.viewLim.bounds
1017
+
1018
+ ax.add_collection(barCollection)
1019
+ ax.autoscale_view()
1020
+
1021
+ return barCollection
1022
+
1023
+
1024
+ def index_bar(ax, vals, facecolor="b", edgecolor="l", width=4, alpha=1.0):
1025
+ """Add a bar collection graph with height vals (-1 is missing).
1026
+
1027
+ Parameters
1028
+ ----------
1029
+ ax : `Axes`
1030
+ an Axes instance to plot to
1031
+ vals : sequence
1032
+ a sequence of values
1033
+ facecolor : color
1034
+ the color of the bar face
1035
+ edgecolor : color
1036
+ the color of the bar edges
1037
+ width : int
1038
+ the bar width in points
1039
+ alpha : float
1040
+ bar transparency
1041
+
1042
+ Returns
1043
+ -------
1044
+ ret : `barCollection`
1045
+ The `barrCollection` added to the axes
1046
+
1047
+ """
1048
+
1049
+ facecolors = (mcolors.to_rgba(facecolor, alpha),)
1050
+ edgecolors = (mcolors.to_rgba(edgecolor, alpha),)
1051
+
1052
+ right = width / 2.0
1053
+ left = -width / 2.0
1054
+
1055
+ bars = [((left, 0), (left, v), (right, v), (right, 0)) for v in vals if v != -1]
1056
+
1057
+ sx = ax.figure.dpi * (1.0 / 72.0) # scale for points
1058
+ sy = ax.bbox.height / ax.viewLim.height
1059
+
1060
+ barTransform = Affine2D().scale(sx, sy)
1061
+
1062
+ offsetsBars = [(i, 0) for i, v in enumerate(vals) if v != -1]
1063
+
1064
+ barCollection = PolyCollection(
1065
+ bars,
1066
+ facecolors=facecolors,
1067
+ edgecolors=edgecolors,
1068
+ antialiaseds=(0,),
1069
+ linewidths=(0.5,),
1070
+ offsets=offsetsBars,
1071
+ transOffset=ax.transData,
1072
+ )
1073
+ barCollection.set_transform(barTransform)
1074
+
1075
+ minpy, maxx = (0, len(offsetsBars))
1076
+ miny = 0
1077
+ maxy = max([v for v in vals if v != -1])
1078
+ corners = (minpy, miny), (maxx, maxy)
1079
+ ax.update_datalim(corners)
1080
+ ax.autoscale_view()
1081
+
1082
+ # add these last
1083
+ ax.add_collection(barCollection)
1084
+ return barCollection
1085
+
1086
+
1087
+ def _to_mdate_index(x):
1088
+ return date2num(x.index.to_pydatetime()), x
1089
+
1090
+
1091
+ def ohlc_plot(ohlc: pd.DataFrame, width=0, colorup="#209040", colordown="#e02020", fmt=None, autofmt=False):
1092
+ """
1093
+ Plot OHLC data frame
1094
+
1095
+ :param ohlc: index, 'open', 'high', 'low', 'close' columns
1096
+ :param width: used bar width
1097
+ :param colorup: color of growing bar
1098
+ :param colordown: color of declining bar
1099
+ :param autofmt: true if needed to aoutoformatting time labels
1100
+ :param fmt: format string for timescale
1101
+ :return: axis
1102
+ """
1103
+ ohlc_f = ohlc.filter(["open", "high", "low", "close"])
1104
+ if ohlc_f.shape[1] != 4:
1105
+ raise ValueError("DataFrame ohlc must contain 'open', 'high', 'low', 'close' columns !")
1106
+
1107
+ _freq = pd.Timedelta(infer_series_frequency(ohlc_f))
1108
+
1109
+ # customization of the axis
1110
+ f = plt.gcf()
1111
+ ax = plt.gca()
1112
+ ax.xaxis.set_ticks_position("bottom")
1113
+ ax.yaxis.set_ticks_position("left")
1114
+ ax.tick_params(axis="both", direction="out", width=2, length=8, labelsize=8, pad=8)
1115
+
1116
+ tms, _ = _to_mdate_index(ohlc_f)
1117
+ t_data = np.vstack((np.array(tms), ohlc_f.values.T)).T
1118
+
1119
+ # auto width
1120
+ if width <= 0:
1121
+ width = max(1, _freq.total_seconds() * 0.7) / 24 / 60 / 60
1122
+
1123
+ reshaped_data = np.hstack((np.reshape(t_data[:, 0], (-1, 1)), t_data[:, 1:]))
1124
+ candlestick_ohlc(ax, reshaped_data, width=width, colorup=colorup, colordown=colordown)
1125
+
1126
+ is_eod = _freq >= datetime.timedelta(1)
1127
+ if is_eod:
1128
+ ax.xaxis.set_major_locator(mticker.MaxNLocator(15))
1129
+ fmt = "%d-%b-%y" if fmt is None else fmt
1130
+ else:
1131
+ ax.xaxis.set_major_locator(mticker.MaxNLocator(20))
1132
+ fmt = "%H:%M" if fmt is None else fmt
1133
+
1134
+ ticks_loc = ax.get_xticks().tolist()
1135
+ ax.xaxis.set_major_locator(mticker.FixedLocator(ticks_loc))
1136
+ ax.set_xticklabels([datetime.date.strftime(num2date(x), fmt) for x in ax.get_xticks()])
1137
+
1138
+ if autofmt:
1139
+ f.autofmt_xdate()
1140
+
1141
+ # - don't want to see grid lines on the top
1142
+ ax.set_axisbelow(True)
1143
+
1144
+ return ax
1145
+
1146
+
1147
+ def plot_trends(trends: pd.DataFrame | Struct, uc="w--", dc="m--", lw=2, ms=6, fmt="%H:%M"):
1148
+ """
1149
+ Plot find_movements function output as trend lines on chart
1150
+
1151
+ >>> from qube.quantitative.ta.swings.swings_splitter import find_movements
1152
+ >>>
1153
+ >>> tx = pd.Series(np.random.randn(500).cumsum() + 100, index=pd.date_range('2000-01-01', periods=500))
1154
+ >>> trends = find_movements(tx, np.inf, use_prev_movement_size_for_percentage=False,
1155
+ >>> pcntg=0.02,
1156
+ >>> t_window=np.inf, drop_weekends_crossings=False,
1157
+ >>> drop_out_of_market=False, result_as_frame=True, silent=True)
1158
+ >>> plot_trends(trends)
1159
+
1160
+ :param trends: find_movements output
1161
+ :param uc: up trends line spec ('w--')
1162
+ :param dc: down trends line spec ('c--')
1163
+ :param lw: line weight (0.7)
1164
+ :param ms: trends reversals marker size (5)
1165
+ :param fmt: time format (default is '%H:%M')
1166
+ """
1167
+ if isinstance(trends, Struct) and hasattr(trends, "trends"):
1168
+ trends = trends.trends
1169
+
1170
+ if isinstance(trends, pd.DataFrame):
1171
+ if not trends.empty:
1172
+ u, d = trends.UpTrends.dropna(), trends.DownTrends.dropna()
1173
+ plt.plot([u.index, u.end], [u.start_price, u.end_price], uc, lw=lw, marker="o", markersize=ms)
1174
+ plt.plot([d.index, d.end], [d.start_price, d.end_price], dc, lw=lw, marker="o", markersize=ms)
1175
+
1176
+ import datetime
1177
+
1178
+ from matplotlib.dates import num2date
1179
+
1180
+ ax = plt.gca()
1181
+ ax.set_xticks(ax.get_xticks(), labels=[datetime.date.strftime(num2date(x), fmt) for x in ax.get_xticks()])
1182
+ else:
1183
+ raise ValueError("trends must be a DataFrame or Struct with 'trends' attribute")