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.
- qubx/__init__.py +207 -0
- qubx/_nb_magic.py +100 -0
- qubx/backtester/__init__.py +5 -0
- qubx/backtester/account.py +145 -0
- qubx/backtester/broker.py +87 -0
- qubx/backtester/data.py +296 -0
- qubx/backtester/management.py +378 -0
- qubx/backtester/ome.py +296 -0
- qubx/backtester/optimization.py +201 -0
- qubx/backtester/simulated_data.py +558 -0
- qubx/backtester/simulator.py +362 -0
- qubx/backtester/utils.py +780 -0
- qubx/cli/__init__.py +0 -0
- qubx/cli/commands.py +67 -0
- qubx/connectors/ccxt/__init__.py +0 -0
- qubx/connectors/ccxt/account.py +495 -0
- qubx/connectors/ccxt/broker.py +132 -0
- qubx/connectors/ccxt/customizations.py +193 -0
- qubx/connectors/ccxt/data.py +612 -0
- qubx/connectors/ccxt/exceptions.py +17 -0
- qubx/connectors/ccxt/factory.py +93 -0
- qubx/connectors/ccxt/utils.py +307 -0
- qubx/core/__init__.py +0 -0
- qubx/core/account.py +251 -0
- qubx/core/basics.py +850 -0
- qubx/core/context.py +420 -0
- qubx/core/exceptions.py +38 -0
- qubx/core/helpers.py +480 -0
- qubx/core/interfaces.py +1150 -0
- qubx/core/loggers.py +514 -0
- qubx/core/lookups.py +475 -0
- qubx/core/metrics.py +1512 -0
- qubx/core/mixins/__init__.py +13 -0
- qubx/core/mixins/market.py +94 -0
- qubx/core/mixins/processing.py +428 -0
- qubx/core/mixins/subscription.py +203 -0
- qubx/core/mixins/trading.py +88 -0
- qubx/core/mixins/universe.py +270 -0
- qubx/core/series.cpython-312-x86_64-linux-gnu.so +0 -0
- qubx/core/series.pxd +125 -0
- qubx/core/series.pyi +118 -0
- qubx/core/series.pyx +988 -0
- qubx/core/utils.cpython-312-x86_64-linux-gnu.so +0 -0
- qubx/core/utils.pyi +6 -0
- qubx/core/utils.pyx +62 -0
- qubx/data/__init__.py +25 -0
- qubx/data/helpers.py +416 -0
- qubx/data/readers.py +1562 -0
- qubx/data/tardis.py +100 -0
- qubx/gathering/simplest.py +88 -0
- qubx/math/__init__.py +3 -0
- qubx/math/stats.py +129 -0
- qubx/pandaz/__init__.py +23 -0
- qubx/pandaz/ta.py +2757 -0
- qubx/pandaz/utils.py +638 -0
- qubx/resources/instruments/symbols-binance.cm.json +1 -0
- qubx/resources/instruments/symbols-binance.json +1 -0
- qubx/resources/instruments/symbols-binance.um.json +1 -0
- qubx/resources/instruments/symbols-bitfinex.f.json +1 -0
- qubx/resources/instruments/symbols-bitfinex.json +1 -0
- qubx/resources/instruments/symbols-kraken.f.json +1 -0
- qubx/resources/instruments/symbols-kraken.json +1 -0
- qubx/ta/__init__.py +0 -0
- qubx/ta/indicators.cpython-312-x86_64-linux-gnu.so +0 -0
- qubx/ta/indicators.pxd +149 -0
- qubx/ta/indicators.pyi +41 -0
- qubx/ta/indicators.pyx +787 -0
- qubx/trackers/__init__.py +3 -0
- qubx/trackers/abvanced.py +236 -0
- qubx/trackers/composite.py +146 -0
- qubx/trackers/rebalancers.py +129 -0
- qubx/trackers/riskctrl.py +641 -0
- qubx/trackers/sizers.py +235 -0
- qubx/utils/__init__.py +5 -0
- qubx/utils/_pyxreloader.py +281 -0
- qubx/utils/charting/lookinglass.py +1057 -0
- qubx/utils/charting/mpl_helpers.py +1183 -0
- qubx/utils/marketdata/binance.py +284 -0
- qubx/utils/marketdata/ccxt.py +90 -0
- qubx/utils/marketdata/dukas.py +130 -0
- qubx/utils/misc.py +541 -0
- qubx/utils/ntp.py +63 -0
- qubx/utils/numbers_utils.py +7 -0
- qubx/utils/orderbook.py +491 -0
- qubx/utils/plotting/__init__.py +0 -0
- qubx/utils/plotting/dashboard.py +150 -0
- qubx/utils/plotting/data.py +137 -0
- qubx/utils/plotting/interfaces.py +25 -0
- qubx/utils/plotting/renderers/__init__.py +0 -0
- qubx/utils/plotting/renderers/plotly.py +0 -0
- qubx/utils/runner/__init__.py +1 -0
- qubx/utils/runner/_jupyter_runner.pyt +60 -0
- qubx/utils/runner/accounts.py +88 -0
- qubx/utils/runner/configs.py +65 -0
- qubx/utils/runner/runner.py +470 -0
- qubx/utils/time.py +312 -0
- qubx-0.5.7.dist-info/METADATA +105 -0
- qubx-0.5.7.dist-info/RECORD +100 -0
- qubx-0.5.7.dist-info/WHEEL +4 -0
- 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")
|