seolpyo-mplchart 1.0.1__tar.gz → 1.1.1__tar.gz

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

Potentially problematic release.


This version of seolpyo-mplchart might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: seolpyo-mplchart
3
- Version: 1.0.1
3
+ Version: 1.1.1
4
4
  Summary: Fast candlestick chart using Python. Includes navigator, slider, navigation, and text information display functions
5
5
  Author-email: white-seolpyo <white-seolpyo@naver.com>
6
6
  License: MIT License
@@ -8,7 +8,7 @@ build-backend = "setuptools.build_meta"
8
8
 
9
9
  [project]
10
10
  name = "seolpyo-mplchart"
11
- version = "1.0.1"
11
+ version = "1.1.1"
12
12
  dependencies = [
13
13
  "matplotlib >= 3.7.0",
14
14
  "pandas >= 2.0.0",
@@ -15,7 +15,8 @@ __all__ = [
15
15
  'format_candleinfo_ko', 'format_volumeinfo_ko',
16
16
  'format_candleinfo_en', 'format_volumeinfo_en',
17
17
  'sample', 'switch_backend', 'show', 'close',
18
- 'Chart', 'CursorChart', 'SliderChart',
18
+ 'OnlyChart', 'CursorChart', 'SliderChart',
19
+ 'set_theme',
19
20
  ]
20
21
 
21
22
 
@@ -67,7 +68,7 @@ def close(fig='all'):
67
68
  return plt.close(fig)
68
69
 
69
70
 
70
- class Chart(BaseChart):
71
+ class OnlyChart(BaseChart):
71
72
  r"""
72
73
  You can see the guidance document:
73
74
  Korean: https://white.seolpyo.com/entry/147/
@@ -102,17 +103,21 @@ class Chart(BaseChart):
102
103
  list_macolor: Color the moving average line. If the number of colors is greater than the moving average line, black is applied
103
104
 
104
105
  candle_on_ma: Decide whether to draw candles on the moving average line. default ```True```
106
+ color_navigator_line: Color of left and right dividing lines in selected area
107
+ color_navigator_cover: Color of unselected area.
105
108
 
106
- color_up: The color of the candle. When the closing price is greater than the opening price. default '#fe3032'
107
- color_down: The color of the candle. When the opening price is greater than the opening price. default '#0095ff'
108
- color_flat: The color of the candle. WWhen the closing price is the same as the opening price. default 'k'
109
- color_up_down: The color of the candle. If the closing price is greater than the opening price, but is lower than the previous day's closing price. default 'w'
110
- color_down_up: The color of the candle. If the opening price is greater than the closing price, but is higher than the closing price of the previous day. default 'w'
111
- colors_volume: The color of the volume bar. default '#1f77b4'
109
+ color_priceline: The color of the price line
112
110
 
113
- color_priceline: The color of the price line. default 'k'
111
+ color_up: The color of the candle. When the closing price is greater than the opening price
112
+ color_down: The color of the candle. When the opening price is greater than the opening price
113
+ color_flat: The color of the candle. When the closing price is the same as the opening price
114
+ color_up_down: The color of the candle. If the closing price is greater than the opening price, but is lower than the previous day's closing price
115
+ color_down_up: The color of the candle. If the opening price is greater than the closing price, but is higher than the closing price of the previous day
116
+ colors_volume: The color of the volume bar. default '#1f77b4'
114
117
 
115
- facecolor_volume, edgecolor_volume = The color of the volume. default ('#1f77b4', 'k')
118
+ color_volume_up: The color of the volume. When the closing price is greater than the opening price
119
+ color_volume_down: The color of the volume. When the opening price is greater than the opening price
120
+ color_volume_flatt: The color of the volume. When the closing price is the same as the opening price
116
121
 
117
122
  candle_width_half, volume_width_half: half of the thickness of the candle and volume
118
123
 
@@ -162,17 +167,21 @@ class CursorChart(BaseCursorChart):
162
167
  list_macolor: Color the moving average line. If the number of colors is greater than the moving average line, black is applied
163
168
 
164
169
  candle_on_ma: Decide whether to draw candles on the moving average line. default ```True```
170
+ color_navigator_line: Color of left and right dividing lines in selected area
171
+ color_navigator_cover: Color of unselected area.
165
172
 
166
- color_up: The color of the candle. When the closing price is greater than the opening price. default '#fe3032'
167
- color_down: The color of the candle. When the opening price is greater than the opening price. default '#0095ff'
168
- color_flat: The color of the candle. WWhen the closing price is the same as the opening price. default 'k'
169
- color_up_down: The color of the candle. If the closing price is greater than the opening price, but is lower than the previous day's closing price. default 'w'
170
- color_down_up: The color of the candle. If the opening price is greater than the closing price, but is higher than the closing price of the previous day. default 'w'
171
- colors_volume: The color of the volume bar. default '#1f77b4'
173
+ color_priceline: The color of the price line
172
174
 
173
- color_priceline: The color of the price line. default 'k'
175
+ color_up: The color of the candle. When the closing price is greater than the opening price
176
+ color_down: The color of the candle. When the opening price is greater than the opening price
177
+ color_flat: The color of the candle. When the closing price is the same as the opening price
178
+ color_up_down: The color of the candle. If the closing price is greater than the opening price, but is lower than the previous day's closing price
179
+ color_down_up: The color of the candle. If the opening price is greater than the closing price, but is higher than the closing price of the previous day
180
+ colors_volume: The color of the volume bar. default '#1f77b4'
174
181
 
175
- facecolor_volume, edgecolor_volume = The color of the volume. default ('#1f77b4', 'k')
182
+ color_volume_up: The color of the volume. When the closing price is greater than the opening price
183
+ color_volume_down: The color of the volume. When the opening price is greater than the opening price
184
+ color_volume_flatt: The color of the volume. When the closing price is the same as the opening price
176
185
 
177
186
  candle_width_half, volume_width_half: half of the thickness of the candle and volume
178
187
 
@@ -184,7 +193,7 @@ class CursorChart(BaseCursorChart):
184
193
 
185
194
  fraction: Decide whether to express information as a fraction. default False
186
195
  format_candleinfo: Candle information text format. default '{dt}\n\n종가:  {close}\n등락률: {rate}\n대비:  {compare}\n시가:  {open}({rate_open})\n고가:  {high}({rate_high})\n저가:  {low}({rate_low})\n거래량: {volume}({rate_volume})'
187
- format_volumeinfo: Volume information text format. default '{dt}\n\n거래량   : {volume}\n거래량증가율: {rate_volume}'
196
+ format_volumeinfo: Volume information text format. default '{dt}\n\n거래량:    {volume}\n거래량증가율: {rate_volume}\n대비:     {compare}'
188
197
 
189
198
  limit_candle: Maximum number of candles to draw. default 800
190
199
  limit_wick: Maximum number of candle wicks to draw. default 4,000
@@ -216,7 +225,7 @@ class SliderChart(BaseSliderChart):
216
225
  figsize: Default size when creating a matplotlib window
217
226
  slider_top: ax_slider is located at the top or bottom. default ```True```.
218
227
  ratio_ax_slider, ratio_ax_legend, ratio_ax_price, ratio_ax_volume: Axes ratio
219
- ratio_ax_none: Ratio between volume chart and slider. Used only when slider_top is ```False```
228
+ ratio_ax_none: Ratio between volume chart and slider. Used only when ```self.slider_top``` is ```False```
220
229
  adjust: figure adjust. default ```dict(top=0.95, bottom=0.05, left=0.01, right=0.93, wspace=0, hspace=0)```.
221
230
  color_background: color of background. default '#fafafa'.
222
231
  gridKwargs: kwargs applied to the grid
@@ -232,20 +241,21 @@ class SliderChart(BaseSliderChart):
232
241
  list_macolor: Color the moving average line. If the number of colors is greater than the moving average line, black is applied
233
242
 
234
243
  candle_on_ma: Decide whether to draw candles on the moving average line. default ```True```
235
- color_sliderline: Color of closing price line in ax_slider. default 'k'
236
- color_navigatorline: Color of left and right dividing lines in selected area. default '#1e78ff'
237
- color_navigator: Color of unselected area. default 'k'
238
-
239
- color_up: The color of the candle. When the closing price is greater than the opening price. default '#fe3032'
240
- color_down: The color of the candle. When the opening price is greater than the opening price. default '#0095ff'
241
- color_flat: The color of the candle. WWhen the closing price is the same as the opening price. default 'k'
242
- color_up_down: The color of the candle. If the closing price is greater than the opening price, but is lower than the previous day's closing price. default 'w'
243
- color_down_up: The color of the candle. If the opening price is greater than the closing price, but is higher than the closing price of the previous day. default 'w'
244
- colors_volume: The color of the volume bar. default '#1f77b4'
244
+ color_navigator_line: Color of left and right dividing lines in selected area
245
+ color_navigator_cover: Color of unselected area.
246
+
247
+ color_priceline: The color of the price line
245
248
 
246
- color_priceline: The color of the price line. default 'k'
249
+ color_up: The color of the candle. When the closing price is greater than the opening price
250
+ color_down: The color of the candle. When the opening price is greater than the opening price
251
+ color_flat: The color of the candle. When the closing price is the same as the opening price
252
+ color_up_down: The color of the candle. If the closing price is greater than the opening price, but is lower than the previous day's closing price
253
+ color_down_up: The color of the candle. If the opening price is greater than the closing price, but is higher than the closing price of the previous day
254
+ colors_volume: The color of the volume bar. default '#1f77b4'
247
255
 
248
- facecolor_volume, edgecolor_volume = The color of the volume. default ('#1f77b4', 'k')
256
+ color_volume_up: The color of the volume. When the closing price is greater than the opening price
257
+ color_volume_down: The color of the volume. When the opening price is greater than the opening price
258
+ color_volume_flatt: The color of the volume. When the closing price is the same as the opening price
249
259
 
250
260
  candle_width_half, volume_width_half: half of the thickness of the candle and volume
251
261
 
@@ -257,7 +267,7 @@ class SliderChart(BaseSliderChart):
257
267
 
258
268
  fraction: Decide whether to express information as a fraction. default False
259
269
  format_candleinfo: Candle information text format. default '{dt}\n\n종가:  {close}\n등락률: {rate}\n대비:  {compare}\n시가:  {open}({rate_open})\n고가:  {high}({rate_high})\n저가:  {low}({rate_low})\n거래량: {volume}({rate_volume})'
260
- format_volumeinfo: Volume information text format. default '{dt}\n\n거래량   : {volume}\n거래량증가율: {rate_volume}'
270
+ format_volumeinfo: Volume information text format. default '{dt}\n\n거래량:    {volume}\n거래량증가율: {rate_volume}\n대비:     {compare}'
261
271
 
262
272
  min_distance: Minimum number of candles that can be selected with the slider. default 30
263
273
  limit_candle: Maximum number of candles to draw. default 800
@@ -268,4 +278,75 @@ class SliderChart(BaseSliderChart):
268
278
  color_navigator_line: Navigator divider color. default '#1e78ff'
269
279
  color_navigator_cover: Unselected slider area color. default = 'k'
270
280
  """
271
- pass
281
+ pass
282
+
283
+
284
+ def set_theme(chart: OnlyChart|CursorChart|SliderChart, theme: Literal['light', 'dark']='dark'):
285
+ initialized = hasattr(chart, 'ax_price')
286
+
287
+ if theme == 'dark':
288
+ chart.color_background = '#000000'
289
+ chart.color_tick, chart.color_tick_label = ('w', 'w')
290
+ chart.gridKwargs = {'color': '#FFFFFF'}
291
+
292
+ chart.color_priceline = 'w'
293
+ chart.color_up, chart.color_down = ('#00FF00', '#FF0000')
294
+ chart.color_flat = 'w'
295
+ chart.color_up_down, chart.color_down_up = ('#000000', '#000000')
296
+
297
+ chart.color_volume_up, chart.color_volume_down = ('#32CD32', '#FF4500')
298
+ chart.color_volume_flat = 'w'
299
+
300
+ chart.list_macolor = ('#FFFF00', '#7FFF00', '#00FFFF', '#FFA07A', '#FF00FF')
301
+
302
+ chart.lineKwargs = {'edgecolor': 'w'}
303
+ chart.color_box = 'w'
304
+ chart.textboxKwargs = {'facecolor': 'k', 'edgecolor': 'w'}
305
+ chart.textKwargs = {'color': 'w'}
306
+ chart.color_navigator_cover, chart.color_navigator_line = ('w', '#00FFFF')
307
+
308
+ if initialized:
309
+ chart.change_background_color('k')
310
+ chart.change_tick_color('w')
311
+ chart.change_line_color('w')
312
+ if hasattr(chart, 'navigator'): chart.navigator.set_edgecolor([chart.color_navigator_cover, chart.color_navigator_line])
313
+
314
+ if hasattr(chart, 'df'):
315
+ chart.set_candlecolor, chart.set_volumecolor = (True, True)
316
+ chart._get_color_segment()
317
+ chart.figure.canvas.draw()
318
+
319
+ elif theme == 'light':
320
+ chart.color_background = '#fafafa'
321
+ chart.color_tick, chart.color_tick_label = ('k', 'k')
322
+ chart.gridKwargs = {'color': '#d0d0d0'}
323
+
324
+ chart.color_priceline = 'k'
325
+ chart.color_up, chart.color_down = ('#FF2400', '#1E90FF')
326
+ chart.color_flat = 'k'
327
+ chart.color_up_down, chart.color_down_up = ('w', 'w')
328
+
329
+ chart.color_volume_up, chart.color_volume_down = ('#FF4D4D', '#5CA8F4')
330
+ chart.color_volume_flat = '#A9A9A9'
331
+
332
+ chart.list_macolor = ('#B22222', '#228B22', '#1E90FF', '#FF8C00', '#4B0082')
333
+
334
+ chart.lineKwargs = {'edgecolor': 'k'}
335
+ chart.color_box = 'k'
336
+ chart.textboxKwargs = {'facecolor': 'w', 'edgecolor': 'k'}
337
+ chart.textKwargs = {'color': 'k'}
338
+ chart.color_navigator_cover, chart.color_navigator_line = ('k', '#1e78ff')
339
+
340
+ if initialized:
341
+ chart.change_background_color('#fafafa')
342
+ chart.change_tick_color('k')
343
+ chart.change_line_color('k')
344
+ if hasattr(chart, 'navigator'): chart.navigator.set_edgecolor([chart.color_navigator_cover, chart.color_navigator_line])
345
+
346
+ if hasattr(chart, 'df'):
347
+ chart.set_candlecolor, chart.set_volumecolor = (True, True)
348
+ chart._get_color_segment()
349
+ chart.figure.canvas.draw()
350
+ else: raise ValueError(f'You send wrong arg.\n{theme=}')
351
+
352
+ return chart
@@ -88,7 +88,7 @@ class Base:
88
88
  self.ax_price.yaxis.set_major_formatter(lambda x, _: convert_unit(x, word=self.unit_price, digit=2))
89
89
  self.ax_volume.yaxis.set_major_formatter(lambda x, _: convert_unit(x, word=self.unit_volume, digit=2))
90
90
 
91
- gridKwargs = {'visible': True, 'linewidth': 1, 'color': '#d0d0d0', 'linestyle': '--'}
91
+ gridKwargs = {'visible': True, 'linewidth': 0.7, 'color': '#d0d0d0', 'linestyle': '-', 'dashes': (1, 0)}
92
92
  gridKwargs.update(self.gridKwargs)
93
93
  # 공통 설정
94
94
  for ax in (self.ax_price, self.ax_volume):
@@ -59,6 +59,49 @@ class CollectionMixin(BM):
59
59
  self.ax_volume.add_artist(self.text_volume_info)
60
60
  return
61
61
 
62
+ def change_background_color(self, color):
63
+ super().change_background_color(color)
64
+
65
+ self.text_price.set_backgroundcolor(color)
66
+ self.text_volume.set_backgroundcolor(color)
67
+
68
+ self.text_date_price.set_backgroundcolor(color)
69
+ self.text_date_volume.set_backgroundcolor(color)
70
+
71
+ self.text_price_info.set_backgroundcolor(color)
72
+ self.text_volume_info.set_backgroundcolor(color)
73
+ return
74
+
75
+ def change_text_color(self, color):
76
+ super().change_text_color(color)
77
+
78
+ self.text_price.set_color(color)
79
+ self.text_volume.set_color(color)
80
+
81
+ self.text_date_price.set_color(color)
82
+ self.text_date_volume.set_color(color)
83
+
84
+ self.text_price_info.set_color(color)
85
+ self.text_volume_info.set_color(color)
86
+ return
87
+
88
+ def change_line_color(self, color):
89
+ self.price_crossline.set_edgecolor(color)
90
+ self.volume_crossline.set_edgecolor(color)
91
+
92
+ self.price_box.set_edgecolor(color)
93
+ self.volume_box.set_edgecolor(color)
94
+
95
+ self.text_price.get_bbox_patch().set_edgecolor(color)
96
+ self.text_volume.get_bbox_patch().set_edgecolor(color)
97
+
98
+ self.text_date_price.get_bbox_patch().set_edgecolor(color)
99
+ self.text_date_volume.get_bbox_patch().set_edgecolor(color)
100
+
101
+ self.text_price_info.get_bbox_patch().set_edgecolor(color)
102
+ self.text_volume_info.get_bbox_patch().set_edgecolor(color)
103
+ return
104
+
62
105
 
63
106
  _set_key = {'rate', 'compare', 'rate_open', 'rate_high', 'rate_low', 'rate_volume', '_boxheight', '_boxmin', '_boxmax', '_volumeboxmax',}
64
107
 
@@ -86,7 +129,9 @@ class DataMixin(CollectionMixin):
86
129
  self.df['rate_open'] = ((self.df[self.Open] - self.df['_pre']) / self.df[self.close] * 100).__round__(2).fillna(0)
87
130
  self.df['rate_high'] = ((self.df[self.high] - self.df['_pre']) / self.df[self.close] * 100).__round__(2).fillna(0)
88
131
  self.df['rate_low'] = ((self.df[self.low] - self.df['_pre']) / self.df[self.close] * 100).__round__(2).fillna(0)
89
- if self.volume: self.df['rate_volume'] = ((self.df[self.volume] - self.df[self.volume].shift(1)) / self.df[self.volume].shift(1) * 100).__round__(2).fillna(0)
132
+ if self.volume:
133
+ self.df['compare_volume'] = (self.df[self.volume] - self.df[self.volume].shift(1)).fillna(0)
134
+ self.df['rate_volume'] = (self.df['compare_volume'] / self.df[self.volume].shift(1) * 100).__round__(2).fillna(0)
90
135
 
91
136
  self.df['_boxheight'] = (self.df[self.high] - self.df[self.low]) / 5
92
137
  self.df['_boxmin'] = self.df[self.low] - self.df['_boxheight']
@@ -262,9 +307,9 @@ class LineMixin(EventMixin):
262
307
 
263
308
 
264
309
  format_candleinfo_ko = '{dt}\n\n종가:  {close}\n등락률: {rate}\n대비:  {compare}\n시가:  {open}({rate_open})\n고가:  {high}({rate_high})\n저가:  {low}({rate_low})\n거래량: {volume}({rate_volume})'
265
- format_volumeinfo_ko = '{dt}\n\n거래량   : {volume}\n거래량증가율: {rate_volume}'
310
+ format_volumeinfo_ko = '{dt}\n\n거래량:    {volume}\n거래량증가율: {rate_volume}\n대비:     {compare}'
266
311
  format_candleinfo_en = '{dt}\n\nclose: {close}\nrate: {rate}\ncompare: {compare}\nopen: {open}({rate_open})\nhigh: {high}({rate_high})\nlow: {low}({rate_low})\nvolume: {volume}({rate_volume})'
267
- format_volumeinfo_en = '{dt}\n\nvolume: {volume}\nvolume rate: {rate_volume}'
312
+ format_volumeinfo_en = '{dt}\n\nvolume: {volume}\nvolume rate: {rate_volume}\ncompare: {compare}'
268
313
 
269
314
  class InfoMixin(LineMixin):
270
315
  fraction = False
@@ -382,10 +427,13 @@ class InfoMixin(LineMixin):
382
427
  volume=f'{v:>{self._length_text}}{self.unit_volume}', rate_volume=vr,
383
428
  )
384
429
  elif self.volume:
430
+ compare = self.df['compare_volume'][index]
431
+ com = float_to_str(compare, self.digit_volume, plus=True)
385
432
  text = self.format_volumeinfo.format(
386
433
  dt=dt,
387
434
  volume=f'{v:>{self._length_text}}{self.unit_volume}',
388
435
  rate_volume=f'{vr:>{self._length_text}}%',
436
+ compare=f'{com:>{self._length_text}}{self.unit_volume}',
389
437
  )
390
438
  else: text = ''
391
439
  return text
@@ -398,7 +446,7 @@ class BaseMixin(InfoMixin):
398
446
  class Chart(BaseMixin, Mixin):
399
447
  def _add_collection(self):
400
448
  super()._add_collection()
401
- return self.add_collection()
449
+ return self.add_artist()
402
450
 
403
451
  def _draw_artist(self):
404
452
  super()._draw_artist()
@@ -416,6 +464,21 @@ class Chart(BaseMixin, Mixin):
416
464
  self.on_pick(e)
417
465
  return super()._on_pick(e)
418
466
 
467
+ def _set_candle_segments(self, index_start, index_end):
468
+ super()._set_candle_segments(index_start, index_end)
469
+ self.set_segment(index_start, index_end)
470
+ return
471
+
472
+ def _set_wick_segments(self, index_start, index_end, simpler=False):
473
+ super()._set_wick_segments(index_start, index_end, simpler)
474
+ self.set_segment(index_start, index_end, simpler)
475
+ return
476
+
477
+ def _set_line_segments(self, index_start, index_end, simpler=False, set_ma=True):
478
+ super()._set_line_segments(index_start, index_end, simpler, set_ma)
479
+ self.set_segment(index_start, index_end, simpler, set_ma)
480
+ return
481
+
419
482
  def _on_move(self, e):
420
483
  super()._on_move(e)
421
484
  return self.on_move(e)
@@ -10,7 +10,7 @@ from .base import Base
10
10
 
11
11
 
12
12
  class Mixin:
13
- def add_collection(self):
13
+ def add_artist(self):
14
14
  "This method work when ```__init__()``` run."
15
15
  return
16
16
 
@@ -30,6 +30,10 @@ class Mixin:
30
30
  "If draw pick active, This method work."
31
31
  return
32
32
 
33
+ def set_segment(self, xmin, xmax, simpler=False, set_ma=True):
34
+ "This method work if xlim change."
35
+ return
36
+
33
37
 
34
38
  class CollectionMixin(Base):
35
39
  facecolor_volume, edgecolor_volume = ('#1f77b4', 'k')
@@ -61,6 +65,35 @@ class CollectionMixin(Base):
61
65
  self.figure.add_artist(self.text_watermark)
62
66
  return
63
67
 
68
+ def change_background_color(self, color):
69
+ self.figure.set_facecolor(color)
70
+ self.ax_price.set_facecolor(color)
71
+ self.ax_volume.set_facecolor(color)
72
+ legends = self.ax_legend.get_legend()
73
+ if legends: legends.get_frame().set_facecolor(color)
74
+ return
75
+
76
+ def change_tick_color(self, color):
77
+ for ax in (self.ax_price, self.ax_volume):
78
+ for i in ['top', 'bottom', 'left', 'right']: ax.spines[i].set_color(self.color_tick)
79
+ ax.tick_params(colors=color)
80
+ ax.tick_params(colors=color)
81
+
82
+ legends = self.ax_legend.get_legend()
83
+ if legends: legends.get_frame().set_edgecolor(color)
84
+
85
+ self.change_text_color(color)
86
+ return
87
+
88
+ def change_text_color(self, color):
89
+ self.text_watermark.set_color(color)
90
+ legends = self.ax_legend.get_legend()
91
+ if legends:
92
+ for i in legends.texts: i.set_color(color)
93
+ return
94
+
95
+ def change_line_color(self, color): return
96
+
64
97
 
65
98
  _set_key = {'_x', '_left', '_right', '_volleft', '_volright', '_top', '_bottom', '_pre', '_zero', '_volymax',}
66
99
 
@@ -73,10 +106,15 @@ class DataMixin(CollectionMixin):
73
106
  list_ma = (5, 20, 60, 120, 240)
74
107
 
75
108
  candle_width_half, volume_width_half = (0.24, 0.36)
76
- color_up, color_down = ('#fe3032', '#0095ff')
109
+ color_up, color_down = ('#FF2400', '#1E90FF')
77
110
  color_flat = 'k'
78
111
  color_up_down, color_down_up = ('w', 'w')
79
112
 
113
+ color_volume_up, color_volume_down = ('#FF4D4D', '#5CA8F4')
114
+ color_volume_flat = '#A9A9A9'
115
+
116
+ set_candlecolor, set_volumecolor = (True, True)
117
+
80
118
  def _generate_data(self, df: pd.DataFrame, sort_df, calc_ma, set_candlecolor, set_volumecolor, *_, **__):
81
119
  self._validate_column_key()
82
120
 
@@ -120,30 +158,14 @@ class DataMixin(CollectionMixin):
120
158
  for i in ('facecolor', 'edgecolor', 'volumefacecolor',):
121
159
  if i not in keys:
122
160
  raise Exception(f'"{i}" column not in DataFrame.\nadd column or set set_candlecolor=True.')
123
- else:
124
- # 양봉
125
- df.loc[:, ['facecolor', 'edgecolor']] = (self.color_up, self.color_up)
126
- if self.color_up != self.color_down:
127
- # 음봉
128
- df.loc[df[self.close] < df[self.Open], ['facecolor', 'edgecolor']] = (self.color_down, self.color_down)
129
- if self.color_up != self.color_flat:
130
- # 보합
131
- df.loc[df[self.close] == df[self.Open], ['facecolor', 'edgecolor']] = (self.color_flat, self.color_flat)
132
- if self.color_up != self.color_up_down:
133
- # 양봉(비우기)
134
- df.loc[(df['facecolor'] == self.color_up) & (df[self.close] < df['_pre']), 'facecolor'] = self.color_up_down
135
- if self.color_down != self.color_down_up:
136
- # 음봉(비우기)
137
- df.loc[(df['facecolor'] == self.color_down) & (df['_pre'] < df[self.close]), ['facecolor']] = self.color_down_up
161
+ self.set_candlecolor = set_candlecolor
138
162
 
139
163
  if not set_volumecolor:
140
164
  keys = set(df.keys())
141
165
  for i in ('volumefacecolor', 'volumeedgecolor',):
142
166
  if i not in keys:
143
167
  raise Exception(f'"{i}" column not in DataFrame.\nadd column or set set_volumecolor=True.')
144
- else:
145
- # 거래량
146
- df.loc[:, ['volumefacecolor', 'volumeedgecolor']] = (self.facecolor_volume, self.edgecolor_volume)
168
+ self.set_volumecolor = set_volumecolor
147
169
 
148
170
  self.df = df
149
171
  return
@@ -165,7 +187,7 @@ class SegmentMixin(DataMixin):
165
187
  color_priceline = 'k'
166
188
  format_ma = '{}일선'
167
189
  # https://matplotlib.org/stable/gallery/color/named_colors.html
168
- list_macolor = ('darkred', 'fuchsia', 'olive', 'orange', 'navy', 'darkmagenta', 'limegreen', 'darkcyan',)
190
+ list_macolor = ('#B22222', '#228B22', '#1E90FF', '#FF8C00', '#4B0082')
169
191
 
170
192
  def _get_segments(self):
171
193
  # 캔들 세그먼트
@@ -196,9 +218,6 @@ class SegmentMixin(DataMixin):
196
218
  segment_priceline = segment_wick = self.df[['_x', self.close]].values
197
219
  self.segment_priceline = segment_priceline.reshape(1, *segment_wick.shape)
198
220
 
199
- self.facecolor_candle = self.df['facecolor'].values
200
- self.edgecolor_candle = self.df['edgecolor'].values
201
-
202
221
  if self.volume:
203
222
  # 거래량 바 세그먼트
204
223
  segment_volume = self.df[[
@@ -216,21 +235,49 @@ class SegmentMixin(DataMixin):
216
235
  ]].values
217
236
  self.segment_volume_wick = segment_volume_wick.reshape(segment_volume_wick.shape[0], 2, 2)
218
237
 
219
- self.facecolor_volume = self.df['volumefacecolor'].values
220
- self.edgecolor_volume = self.df['volumeedgecolor'].values
221
-
222
238
  self._get_ma_segment()
239
+ self._get_color_segment()
223
240
  return
224
241
 
225
- def _get_ma_segment(self):
242
+ def _get_color_segment(self):
243
+ if self.set_candlecolor:
244
+ # 양봉
245
+ self.df.loc[:, ['facecolor', 'edgecolor']] = (self.color_up, self.color_up)
246
+ if self.color_up != self.color_down:
247
+ # 음봉
248
+ self.df.loc[self.df[self.close] < self.df[self.Open], ['facecolor', 'edgecolor']] = (self.color_down, self.color_down)
249
+ if self.color_up != self.color_flat and self.color_down != self.color_flat:
250
+ # 보합
251
+ self.df.loc[self.df[self.close] == self.df[self.Open], ['facecolor', 'edgecolor']] = (self.color_flat, self.color_flat)
252
+ if self.color_up != self.color_up_down:
253
+ # 양봉(비우기)
254
+ self.df.loc[(self.df['facecolor'] == self.color_up) & (self.df[self.close] <= self.df['_pre']), 'facecolor'] = self.color_up_down
255
+ if self.color_down != self.color_down_up:
256
+ # 음봉(비우기)
257
+ self.df.loc[(self.df['facecolor'] == self.color_down) & (self.df['_pre'] <= self.df[self.close]), ['facecolor']] = self.color_down_up
258
+
259
+ self.facecolor_candle = self.df['facecolor'].values
260
+ self.edgecolor_candle = self.df['edgecolor'].values
261
+
262
+ if self.set_volumecolor:
263
+ # 거래량
264
+ self.df.loc[:, ['volumefacecolor', 'volumeedgecolor']] = (self.color_volume_up, self.color_volume_up)
265
+ if self.color_up != self.color_down:
266
+ # 전일대비 하락
267
+ self.df.loc[self.df[self.close] < self.df['_pre'], ['volumefacecolor', 'volumeedgecolor']] = (self.color_volume_down, self.color_volume_down)
268
+ if self.color_up != self.color_flat:
269
+ # 전일과 동일
270
+ self.df.loc[self.df[self.close] == self.df['_pre'], ['volumefacecolor', 'volumeedgecolor']] = (self.color_volume_flat, self.color_volume_flat)
271
+
272
+ self.facecolor_volume = self.df['volumefacecolor'].values
273
+ self.edgecolor_volume = self.df['volumeedgecolor'].values
274
+
226
275
  # 기존 legend 제거
227
276
  legends = self.ax_legend.get_legend()
228
277
  if legends: legends.remove()
229
278
 
230
279
  self._visible_ma.clear()
231
280
 
232
- if not self.list_ma: return
233
-
234
281
  list_handle, list_label, list_color = ([], [], [])
235
282
  arr = (0, 1)
236
283
  for n, i in enumerate(self.list_ma):
@@ -242,14 +289,6 @@ class SegmentMixin(DataMixin):
242
289
  list_label.append(self.format_ma.format(i))
243
290
 
244
291
  self._visible_ma.add(i)
245
-
246
- # 주가 차트 가격이동평균선
247
- key_ma = []
248
- for i in reversed(self.list_ma):
249
- key_ma.append('_x')
250
- key_ma.append(f'ma{i}')
251
- segment_ma = self.df[key_ma].values
252
- self.segment_ma = segment_ma.reshape(segment_ma.shape[0], len(self.list_ma), 2).swapaxes(0, 1)
253
292
  self.edgecolor_ma = list(reversed(list_color))
254
293
 
255
294
  # 가격이동평균선 legend 생성
@@ -262,11 +301,24 @@ class SegmentMixin(DataMixin):
262
301
  for i in legends.legend_handles: i.set_picker(5)
263
302
  return
264
303
 
304
+ def _get_ma_segment(self):
305
+ if not self.list_ma: return
306
+
307
+ # 주가 차트 가격이동평균선
308
+ key_ma = []
309
+ for i in reversed(self.list_ma):
310
+ key_ma.append('_x')
311
+ key_ma.append(f'ma{i}')
312
+ segment_ma = self.df[key_ma].values
313
+ self.segment_ma = segment_ma.reshape(segment_ma.shape[0], len(self.list_ma), 2).swapaxes(0, 1)
314
+ return
315
+
265
316
  def _set_segments(self, index_start, index_end, simpler, set_ma):
266
317
  indsub = index_end - index_start
267
318
  if index_start < 0: index_start = 0
268
319
  if index_end < 1: index_end = 1
269
320
 
321
+ index_end += 1
270
322
  if indsub < self.limit_candle:
271
323
  self._set_candle_segments(index_start, index_end)
272
324
  elif indsub < self.limit_wick:
@@ -276,8 +328,6 @@ class SegmentMixin(DataMixin):
276
328
  return
277
329
 
278
330
  def _set_candle_segments(self, index_start, index_end):
279
- index_end += 1
280
-
281
331
  self.collection_candle.set_segments(self.segment_candle[index_start:index_end])
282
332
  self.collection_candle.set_facecolor(self.facecolor_candle[index_start:index_end])
283
333
  self.collection_candle.set_edgecolor(self.edgecolor_candle[index_start:index_end])
@@ -289,12 +339,10 @@ class SegmentMixin(DataMixin):
289
339
  self.collection_volume.set_edgecolor(self.edgecolor_volume[index_start:index_end])
290
340
 
291
341
  self.collection_ma.set_segments(self.segment_ma[:, index_start:index_end])
292
- self.collection_ma.set_edgecolor(list(reversed(self.edgecolor_ma)))
342
+ self.collection_ma.set_edgecolor(self.edgecolor_ma)
293
343
  return
294
344
 
295
345
  def _set_wick_segments(self, index_start, index_end, simpler=False):
296
- index_end += 1
297
-
298
346
  self.collection_candle.set_segments(self.segment_candle_wick[index_start:index_end])
299
347
  self.collection_candle.set_facecolor([])
300
348
  self.collection_candle.set_edgecolor(self.edgecolor_candle[index_start:index_end])
@@ -311,12 +359,10 @@ class SegmentMixin(DataMixin):
311
359
  self.collection_volume.set_edgecolor(self.facecolor_volume[index_start:index_end])
312
360
 
313
361
  self.collection_ma.set_segments(self.segment_ma[:, index_start:index_end])
314
- self.collection_ma.set_edgecolor(list(reversed(self.edgecolor_ma)))
362
+ self.collection_ma.set_edgecolor(self.edgecolor_ma)
315
363
  return
316
364
 
317
365
  def _set_line_segments(self, index_start, index_end, simpler=False, set_ma=True):
318
- index_end += 1
319
-
320
366
  self.collection_candle.set_segments(self.segment_priceline[:, index_start:index_end])
321
367
  self.collection_candle.set_facecolor([])
322
368
  self.collection_candle.set_edgecolor(self.color_priceline)
@@ -335,7 +381,7 @@ class SegmentMixin(DataMixin):
335
381
  if not set_ma: self.collection_ma.set_segments([])
336
382
  else:
337
383
  self.collection_ma.set_segments(self.segment_ma[:, index_start:index_end])
338
- self.collection_ma.set_edgecolor(list(reversed(self.edgecolor_ma)))
384
+ self.collection_ma.set_edgecolor(self.edgecolor_ma)
339
385
  return
340
386
 
341
387
 
@@ -502,7 +548,7 @@ class BaseMixin(BackgroundMixin):
502
548
  class Chart(BaseMixin, Mixin):
503
549
  def _add_collection(self):
504
550
  super()._add_collection()
505
- return self.add_collection()
551
+ return self.add_artist()
506
552
 
507
553
  def _draw_artist(self):
508
554
  super()._draw_artist()
@@ -520,3 +566,18 @@ class Chart(BaseMixin, Mixin):
520
566
  self.on_pick(e)
521
567
  return super()._on_pick(e)
522
568
 
569
+ def _set_candle_segments(self, index_start, index_end):
570
+ super()._set_candle_segments(index_start, index_end)
571
+ self.set_segment(index_start, index_end)
572
+ return
573
+
574
+ def _set_wick_segments(self, index_start, index_end, simpler=False):
575
+ super()._set_wick_segments(index_start, index_end, simpler)
576
+ self.set_segment(index_start, index_end, simpler)
577
+ return
578
+
579
+ def _set_line_segments(self, index_start, index_end, simpler=False, set_ma=True):
580
+ super()._set_line_segments(index_start, index_end, simpler, set_ma)
581
+ self.set_segment(index_start, index_end, simpler, set_ma)
582
+ return
583
+
@@ -63,7 +63,7 @@ class PlotMixin(BM):
63
63
  self.ax_price.yaxis.set_major_formatter(lambda x, _: convert_unit(x, word=self.unit_price, digit=2))
64
64
  self.ax_volume.yaxis.set_major_formatter(lambda x, _: convert_unit(x, word=self.unit_volume, digit=2))
65
65
 
66
- gridKwargs = {'visible': True, 'linewidth': 1, 'color': '#d0d0d0', 'linestyle': '--'}
66
+ gridKwargs = {'visible': True, 'linewidth': 0.5, 'color': '#d0d0d0', 'linestyle': '-', 'dashes': (1, 0)}
67
67
  gridKwargs.update(self.gridKwargs)
68
68
  # 공통 설정
69
69
  for ax in (self.ax_slider, self.ax_price, self.ax_volume):
@@ -100,7 +100,7 @@ class CollectionMixin(PlotMixin):
100
100
  self.ax_slider.add_artist(self.collection_slider)
101
101
 
102
102
  # 슬라이더 네비게이터
103
- self.navigator = LineCollection([], animated=True, edgecolors=[self.color_navigator_cover, self.color_navigator_line], alpha=(0.2, 1.0))
103
+ self.navigator = LineCollection([], animated=True, edgecolors=[self.color_navigator_cover, self.color_navigator_line], alpha=(0.3, 1.0))
104
104
  self.ax_slider.add_artist(self.navigator)
105
105
 
106
106
  lineKwargs = {'edgecolor': 'k', 'linewidth': 1, 'linestyle': '-'}
@@ -131,9 +131,40 @@ class CollectionMixin(PlotMixin):
131
131
  segment_slider = self.df[keys + ['_x', self.close] ].values
132
132
  segment_slider = segment_slider.reshape(segment_slider.shape[0], len(self.list_ma)+1, 2).swapaxes(0, 1)
133
133
  self.collection_slider.set_segments(segment_slider)
134
+ return
135
+
136
+ def _get_color_segment(self):
137
+ super()._get_color_segment()
138
+
134
139
  self.collection_slider.set_edgecolor(self.edgecolor_ma + [self.color_priceline])
135
140
  return
136
141
 
142
+ def change_background_color(self, color):
143
+ super().change_background_color(color)
144
+
145
+ self.ax_slider.set_facecolor(color)
146
+ self.text_slider.set_backgroundcolor(color)
147
+ return
148
+
149
+ def change_tick_color(self, color):
150
+ super().change_tick_color(color)
151
+
152
+ for i in ['top', 'bottom', 'left', 'right']: self.ax_slider.spines[i].set_color(self.color_tick)
153
+ self.ax_slider.tick_params(colors=color)
154
+ return
155
+
156
+ def change_text_color(self, color):
157
+ super().change_text_color(color)
158
+
159
+ self.text_slider.set_color(color)
160
+ return
161
+
162
+ def change_line_color(self, color):
163
+ super().change_line_color(color)
164
+
165
+ self.text_slider.get_bbox_patch().set_edgecolor(color)
166
+ return
167
+
137
168
 
138
169
  class NavigatorMixin(CollectionMixin):
139
170
  def _set_slider_lim(self):
@@ -527,7 +558,7 @@ class BaseMixin(ChartClickMixin):
527
558
  class Chart(BaseMixin, Mixin):
528
559
  def _add_collection(self):
529
560
  super()._add_collection()
530
- return self.add_collection()
561
+ return self.add_artist()
531
562
 
532
563
  def _draw_artist(self):
533
564
  super()._draw_artist()
@@ -545,6 +576,21 @@ class Chart(BaseMixin, Mixin):
545
576
  self.on_pick(e)
546
577
  return super()._on_pick(e)
547
578
 
579
+ def _set_candle_segments(self, index_start, index_end):
580
+ super()._set_candle_segments(index_start, index_end)
581
+ self.set_segment(index_start, index_end)
582
+ return
583
+
584
+ def _set_wick_segments(self, index_start, index_end, simpler=False):
585
+ super()._set_wick_segments(index_start, index_end, simpler)
586
+ self.set_segment(index_start, index_end, simpler)
587
+ return
588
+
589
+ def _set_line_segments(self, index_start, index_end, simpler=False, set_ma=True):
590
+ super()._set_line_segments(index_start, index_end, simpler, set_ma)
591
+ self.set_segment(index_start, index_end, simpler, set_ma)
592
+ return
593
+
548
594
  def _on_move(self, e):
549
595
  super()._on_move(e)
550
596
  return self.on_move(e)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: seolpyo-mplchart
3
- Version: 1.0.1
3
+ Version: 1.1.1
4
4
  Summary: Fast candlestick chart using Python. Includes navigator, slider, navigation, and text information display functions
5
5
  Author-email: white-seolpyo <white-seolpyo@naver.com>
6
6
  License: MIT License