Skip to content

Commit 497dc04

Browse files
committed
add candlestick chart
- add candlestick chart and tests - TF.create_candlestick uses fill and grouped legends to create the candlestick outline and the shaded body - (also oops forgot some :rtypes: in ohlc docs)
1 parent 070b372 commit 497dc04

File tree

2 files changed

+568
-15
lines changed

2 files changed

+568
-15
lines changed

plotly/tests/test_core/test_tools/test_trace_factory.py

Lines changed: 238 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -102,37 +102,46 @@ def test_more_kwargs(self):
102102
self.assertEqual(quiver, expected_quiver)
103103

104104

105-
class TestOHLC(TestCase):
105+
class TestFinanceCharts(TestCase):
106106

107107
def test_unequal_ohlc_length(self):
108108

109109
# check: PlotlyError if open, high, low, close are not the same length
110-
# for TraceFactory.ohlc_increase and TraceFactory.ohlc_decrease
110+
# for TraceFactory.create_ohlc and TraceFactory.create_candlestick
111111

112112
kwargs = {'open': [1], 'high': [1, 3],
113113
'low': [1, 2], 'close': [1, 2],
114114
'direction': ['increasing']}
115115
self.assertRaises(PlotlyError, tls.TraceFactory.create_ohlc, **kwargs)
116+
self.assertRaises(PlotlyError, tls.TraceFactory.create_candlestick,
117+
**kwargs)
116118

117119
kwargs = {'open': [1, 2], 'high': [1, 2, 3],
118120
'low': [1, 2], 'close': [1, 2],
119121
'direction': ['decreasing']}
120122
self.assertRaises(PlotlyError, tls.TraceFactory.create_ohlc, **kwargs)
123+
self.assertRaises(PlotlyError, tls.TraceFactory.create_candlestick,
124+
**kwargs)
121125

122126
kwargs = {'open': [1, 2], 'high': [2, 3],
123127
'low': [0], 'close': [1, 3],
124128
'direction': ['increasing']}
125129
self.assertRaises(PlotlyError, tls.TraceFactory.create_ohlc, **kwargs)
130+
self.assertRaises(PlotlyError, tls.TraceFactory.create_candlestick,
131+
**kwargs)
126132

127133
kwargs = {'open': [1, 2], 'high': [2, 3],
128134
'low': [1, 2], 'close': [1],
129135
'direction': ['decreasing']}
130136
self.assertRaises(PlotlyError, tls.TraceFactory.create_ohlc, **kwargs)
137+
self.assertRaises(PlotlyError, tls.TraceFactory.create_candlestick,
138+
**kwargs)
131139

132140
def test_direction_arg(self):
133141

134142
# check: PlotlyError if direction is not defined as "increasing" or
135-
# "decreasing"
143+
# "decreasing" for TraceFactory.create_ohlc and
144+
# TraceFactory.create_candlestick
136145

137146
kwargs = {'open': [1, 4], 'high': [1, 5],
138147
'low': [1, 2], 'close': [1, 2],
@@ -141,6 +150,10 @@ def test_direction_arg(self):
141150
"direction must be defined as "
142151
"'increasing' or 'decreasing'",
143152
tls.TraceFactory.create_ohlc, **kwargs)
153+
self.assertRaisesRegexp(PlotlyError,
154+
"direction must be defined as "
155+
"'increasing' or 'decreasing'",
156+
tls.TraceFactory.create_candlestick, **kwargs)
144157

145158
kwargs = {'open': [1, 2], 'high': [1, 3],
146159
'low': [1, 2], 'close': [1, 2],
@@ -149,6 +162,10 @@ def test_direction_arg(self):
149162
"direction must be defined as "
150163
"'increasing' or 'decreasing'",
151164
tls.TraceFactory.create_ohlc, **kwargs)
165+
self.assertRaisesRegexp(PlotlyError,
166+
"direction must be defined as "
167+
"'increasing' or 'decreasing'",
168+
tls.TraceFactory.create_candlestick, **kwargs)
152169

153170
def test_high_highest_value(self):
154171

@@ -168,13 +185,21 @@ def test_high_highest_value(self):
168185
"is entered in O-H-L-C order",
169186
tls.TraceFactory.create_ohlc,
170187
**kwargs)
188+
self.assertRaisesRegexp(PlotlyError, "Oops! Looks like some of "
189+
"your high values are less "
190+
"the corresponding open, "
191+
"low, or close values. "
192+
"Double check that your data "
193+
"is entered in O-H-L-C order",
194+
tls.TraceFactory.create_candlestick,
195+
**kwargs)
171196

172197
def test_low_lowest_value(self):
173198

174199
# check: PlotlyError if the "low" value is greater than the
175200
# corresponding open, high, or close value because if the "low" value
176201
# is not the lowest (or equal) then the data may have been entered
177-
# incorrectly
202+
# incorrectly.
178203

179204
# create_ohlc_increase
180205
kwargs = {'open': [2, 3], 'high': [4, 6],
@@ -189,6 +214,15 @@ def test_low_lowest_value(self):
189214
"is entered in O-H-L-C order",
190215
tls.TraceFactory.create_ohlc,
191216
**kwargs)
217+
self.assertRaisesRegexp(PlotlyError,
218+
"Oops! Looks like some of "
219+
"your low values are greater "
220+
"than the corresponding high"
221+
", open, or close values. "
222+
"Double check that your data "
223+
"is entered in O-H-L-C order",
224+
tls.TraceFactory.create_candlestick,
225+
**kwargs)
192226

193227
def test_one_ohlc_increase(self):
194228

@@ -322,3 +356,203 @@ def test_ohlc_increase_and_decrease(self):
322356

323357
self.assertEqual(ohlc_data, expected_ohlc_data)
324358

359+
def test_one_candlestick_increase(self):
360+
361+
# This should create one "increase" (i.e. close > open) candlestick
362+
363+
candl_inc = tls.TraceFactory.create_candlestick(open=[33.0],
364+
high=[33.2],
365+
low=[32.7],
366+
close=[33.1],
367+
direction="increasing")
368+
369+
expected_candl_inc = [
370+
{'name': 'Increasing',
371+
'x': [0.8, 0.8, None],
372+
'y': (33.1, 33.0, None),
373+
'type': 'scatter',
374+
'showlegend': False,
375+
'mode': 'lines',
376+
'legendgroup': 'Increasing',
377+
'line': {'color': 'rgb(44, 160, 44)'}},
378+
{'name': 'Increasing',
379+
'x': [1.2, 1.2, None],
380+
'y': (33.1, 33.0, None),
381+
'type': 'scatter',
382+
'fill': 'tonextx',
383+
'showlegend': False,
384+
'line': {'color': 'rgb(44, 160, 44)'},
385+
'legendgroup': 'Increasing',
386+
'mode': 'lines'},
387+
{'name': 'Increasing',
388+
'x': [1, 1, None, 1.2, 0.8, None, 0.8, 1.2, None],
389+
'y': [33.2, 32.7, None, 33.1, 33.1, None, 33.0, 33.0, None],
390+
'text': ('High', 'Low', None,
391+
'Close', 'Close', None,
392+
'Open', 'Open', None),
393+
'type': 'scatter',
394+
'mode': 'lines',
395+
'legendgroup': 'Increasing',
396+
'line': {'color': 'rgb(44, 160, 44)'}}
397+
]
398+
self.assertEqual(candl_inc, expected_candl_inc)
399+
400+
def test_one_candlestick_decrease(self):
401+
402+
# This should create one "decrease" (i.e. close < open) ohlc stick
403+
404+
candl_dec = tls.TraceFactory.create_candlestick(open=[33.3],
405+
high=[33.3],
406+
low=[32.7],
407+
close=[32.9],
408+
direction="decreasing")
409+
410+
expected_candl_dec = [
411+
{'name': 'Decreasing',
412+
'x': [0.8, 0.8, None],
413+
'y': (32.9, 33.3, None),
414+
'type': 'scatter',
415+
'showlegend': False,
416+
'mode': 'lines',
417+
'legendgroup': 'Decreasing',
418+
'line': {'color': 'rgb(214, 39, 40)'}},
419+
{'name': 'Decreasing',
420+
'x': [1.2, 1.2, None],
421+
'y': (32.9, 33.3, None),
422+
'type': 'scatter',
423+
'fill': 'tonextx',
424+
'showlegend': False,
425+
'line': {'color': 'rgb(214, 39, 40)'},
426+
'legendgroup': 'Decreasing', 'mode': 'lines'},
427+
{'name': 'Decreasing',
428+
'x': [1, 1, None, 1.2, 0.8, None, 0.8, 1.2, None],
429+
'y': [33.3, 32.7, None, 32.9, 32.9, None, 33.3, 33.3, None],
430+
'text': ('High', 'Low', None,
431+
'Close', 'Close', None,
432+
'Open', 'Open', None),
433+
'type': 'scatter',
434+
'mode': 'lines',
435+
'legendgroup': 'Decreasing',
436+
'line': {'color': 'rgb(214, 39, 40)'}}]
437+
self.assertEqual(candl_dec, expected_candl_dec)
438+
439+
def test_candlestick_increase_and_decrease(self):
440+
441+
# This should add multiple increasing and decreasing candlesticks
442+
# and check that what we expect (i.e. the data and kwargs)
443+
# is resulting from data = candl_inc data.extend(candl_dec)
444+
445+
o = [3.3, 4, 9.3, 8.9, 4.9, 9, 2.9, 5]
446+
h = [7, 6.4, 10, 10, 10, 14.6, 12, 7]
447+
l = [3, 2, 7, 3, 2, 2, 1.1, 2.3]
448+
c = [3.3, 6.3, 10, 9, 9.2, 3, 2.9, 6.1]
449+
450+
candl_inc = tls.TraceFactory.create_candlestick(o, h, l, c,
451+
direction='increasing',
452+
name='positive')
453+
candl_dec = tls.TraceFactory.create_candlestick(o, h, l, c,
454+
direction='decreasing',
455+
name='negative')
456+
candl_data = candl_inc
457+
candl_data.extend(candl_dec)
458+
459+
expected_candl_data = [
460+
{'name': 'positive', 'x': [1.8, 1.8, None],
461+
'y': (6.3, 4, None), 'type': 'scatter', 'showlegend': False,
462+
'mode': 'lines', 'legendgroup': 'Increasing',
463+
'line': {'color': 'rgb(44, 160, 44)'}},
464+
{'name': 'positive', 'x': [2.2, 2.2, None], 'y': (6.3, 4, None),
465+
'type': 'scatter', 'fill': 'tonextx', 'showlegend': False,
466+
'line': {'color': 'rgb(44, 160, 44)'},
467+
'legendgroup': 'Increasing', 'mode': 'lines'},
468+
{'name': 'positive', 'x': [2.8, 2.8, None], 'y': (10, 9.3, None),
469+
'type': 'scatter', 'showlegend': False, 'mode': 'lines',
470+
'legendgroup': 'Increasing',
471+
'line': {'color': 'rgb(44, 160, 44)'}},
472+
{'name': 'positive', 'x': [3.2, 3.2, None], 'y': (10, 9.3, None),
473+
'type': 'scatter', 'fill': 'tonextx', 'showlegend': False,
474+
'line': {'color': 'rgb(44, 160, 44)'},
475+
'legendgroup': 'Increasing', 'mode': 'lines'},
476+
{'name': 'positive', 'x': [3.8, 3.8, None], 'y': (9, 8.9, None),
477+
'type': 'scatter', 'showlegend': False, 'mode': 'lines',
478+
'legendgroup': 'Increasing',
479+
'line': {'color': 'rgb(44, 160, 44)'}},
480+
{'name': 'positive', 'x': [4.2, 4.2, None], 'y': (9, 8.9, None),
481+
'type': 'scatter', 'fill': 'tonextx', 'showlegend': False,
482+
'line': {'color': 'rgb(44, 160, 44)'},
483+
'legendgroup': 'Increasing', 'mode': 'lines'},
484+
{'name': 'positive', 'x': [4.8, 4.8, None], 'y': (9.2, 4.9, None),
485+
'type': 'scatter', 'showlegend': False, 'mode': 'lines',
486+
'legendgroup': 'Increasing',
487+
'line': {'color': 'rgb(44, 160, 44)'}},
488+
{'name': 'positive', 'x': [5.2, 5.2, None], 'y': (9.2, 4.9, None),
489+
'type': 'scatter', 'fill': 'tonextx', 'showlegend': False,
490+
'line': {'color': 'rgb(44, 160, 44)'},
491+
'legendgroup': 'Increasing', 'mode': 'lines'},
492+
{'name': 'positive', 'x': [7.8, 7.8, None], 'y': (6.1, 5, None),
493+
'type': 'scatter', 'showlegend': False, 'mode': 'lines',
494+
'legendgroup': 'Increasing',
495+
'line': {'color': 'rgb(44, 160, 44)'}},
496+
{'name': 'positive', 'x': [8.2, 8.2, None], 'y': (6.1, 5, None),
497+
'type': 'scatter', 'fill': 'tonextx', 'showlegend': False,
498+
'line': {'color': 'rgb(44, 160, 44)'},
499+
'legendgroup': 'Increasing', 'mode': 'lines'},
500+
{'name': 'positive',
501+
'x': [2, 2, None, 2.2, 1.8, None, 1.8, 2.2, None, 3, 3, None, 3.2,
502+
2.8, None, 2.8, 3.2, None, 4, 4, None, 4.2, 3.8, None, 3.8,
503+
4.2, None, 5, 5, None, 5.2, 4.8, None, 4.8, 5.2, None, 8, 8,
504+
None, 8.2, 7.8, None, 7.8, 8.2, None],
505+
'y': [6.4, 2, None, 6.3, 6.3, None, 4, 4, None, 10, 7, None, 10,
506+
10, None, 9.3, 9.3, None, 10, 3, None, 9, 9, None, 8.9, 8.9,
507+
None, 10, 2, None, 9.2, 9.2, None, 4.9, 4.9, None, 7, 2.3,
508+
None, 6.1, 6.1, None, 5, 5, None],
509+
'text': ('High', 'Low', None, 'Close', 'Close', None,
510+
'Open', 'Open', None, 'High', 'Low', None,
511+
'Close', 'Close', None, 'Open', 'Open', None,
512+
'High', 'Low', None, 'Close', 'Close', None,
513+
'Open', 'Open', None, 'High', 'Low', None,
514+
'Close', 'Close', None, 'Open', 'Open', None,
515+
'High', 'Low', None, 'Close', 'Close', None,
516+
'Open', 'Open', None),
517+
'type': 'scatter', 'mode': 'lines', 'legendgroup': 'Increasing',
518+
'line': {'color': 'rgb(44, 160, 44)'}},
519+
{'name': 'negative', 'x': [0.8, 0.8, None], 'y': (3.3, 3.3, None),
520+
'type': 'scatter', 'showlegend': False, 'mode': 'lines',
521+
'legendgroup': 'Decreasing',
522+
'line': {'color': 'rgb(214, 39, 40)'}},
523+
{'name': 'negative', 'x': [1.2, 1.2, None], 'y': (3.3, 3.3, None),
524+
'type': 'scatter', 'fill': 'tonextx', 'showlegend': False,
525+
'line': {'color': 'rgb(214, 39, 40)'},
526+
'legendgroup': 'Decreasing', 'mode': 'lines'},
527+
{'name': 'negative', 'x': [5.8, 5.8, None], 'y': (3, 9, None),
528+
'type': 'scatter', 'showlegend': False, 'mode': 'lines',
529+
'legendgroup': 'Decreasing',
530+
'line': {'color': 'rgb(214, 39, 40)'}},
531+
{'name': 'negative', 'x': [6.2, 6.2, None], 'y': (3, 9, None),
532+
'type': 'scatter', 'fill': 'tonextx', 'showlegend': False,
533+
'line': {'color': 'rgb(214, 39, 40)'},
534+
'legendgroup': 'Decreasing', 'mode': 'lines'},
535+
{'name': 'negative', 'x': [6.8, 6.8, None], 'y': (2.9, 2.9, None),
536+
'type': 'scatter', 'showlegend': False, 'mode': 'lines',
537+
'legendgroup': 'Decreasing',
538+
'line': {'color': 'rgb(214, 39, 40)'}},
539+
{'name': 'negative', 'x': [7.2, 7.2, None], 'y': (2.9, 2.9, None),
540+
'type': 'scatter', 'fill': 'tonextx', 'showlegend': False,
541+
'line': {'color': 'rgb(214, 39, 40)'},
542+
'legendgroup': 'Decreasing', 'mode': 'lines'},
543+
{'name': 'negative',
544+
'x': [1, 1, None, 1.2, 0.8, None, 0.8, 1.2, None, 6, 6, None,
545+
6.2, 5.8, None, 5.8, 6.2, None, 7, 7, None, 7.2, 6.8, None,
546+
6.8, 7.2, None],
547+
'y': [7, 3, None, 3.3, 3.3, None, 3.3, 3.3, None, 14.6, 2, None,
548+
3, 3, None, 9, 9, None, 12, 1.1, None, 2.9, 2.9, None, 2.9,
549+
2.9, None],
550+
'text': ('High', 'Low', None, 'Close', 'Close', None,
551+
'Open', 'Open', None, 'High', 'Low', None,
552+
'Close', 'Close', None, 'Open', 'Open', None,
553+
'High', 'Low', None, 'Close', 'Close', None,
554+
'Open', 'Open', None), 'type': 'scatter',
555+
'mode': 'lines', 'legendgroup': 'Decreasing',
556+
'line': {'color': 'rgb(214, 39, 40)'}}]
557+
self.assertEqual(candl_data, expected_candl_data)
558+

0 commit comments

Comments
 (0)