seolpyo-mplchart 2.0.0.3__py3-none-any.whl → 2.1.0.4__py3-none-any.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.
Files changed (58) hide show
  1. seolpyo_mplchart/__init__.py +17 -133
  2. seolpyo_mplchart/_chart/__init__.py +39 -31
  3. seolpyo_mplchart/_chart/base/__init__.py +111 -0
  4. seolpyo_mplchart/_chart/base/a_canvas.py +250 -0
  5. seolpyo_mplchart/_chart/base/b_artist.py +143 -0
  6. seolpyo_mplchart/_chart/base/c_draw.py +100 -0
  7. seolpyo_mplchart/_chart/base/d_segment.py +262 -0
  8. seolpyo_mplchart/_chart/base/e_axis.py +268 -0
  9. seolpyo_mplchart/_chart/base/f_background.py +62 -0
  10. seolpyo_mplchart/_chart/base/g_event.py +66 -0
  11. seolpyo_mplchart/_chart/base/h_data.py +138 -0
  12. seolpyo_mplchart/_chart/base/test.py +58 -0
  13. seolpyo_mplchart/_chart/cursor/__init__.py +125 -0
  14. seolpyo_mplchart/_chart/cursor/b_artist.py +130 -0
  15. seolpyo_mplchart/_chart/cursor/c_draw.py +96 -0
  16. seolpyo_mplchart/_chart/cursor/d_segment.py +359 -0
  17. seolpyo_mplchart/_chart/cursor/e_axis.py +64 -0
  18. seolpyo_mplchart/_chart/cursor/g_event.py +233 -0
  19. seolpyo_mplchart/_chart/cursor/h_data.py +62 -0
  20. seolpyo_mplchart/_chart/cursor/test.py +69 -0
  21. seolpyo_mplchart/_chart/slider/__init__.py +169 -0
  22. seolpyo_mplchart/_chart/slider/a_canvas.py +260 -0
  23. seolpyo_mplchart/_chart/slider/b_artist.py +91 -0
  24. seolpyo_mplchart/_chart/slider/c_draw.py +54 -0
  25. seolpyo_mplchart/_chart/slider/d_segment.py +166 -0
  26. seolpyo_mplchart/_chart/slider/e_axis.py +70 -0
  27. seolpyo_mplchart/_chart/slider/f_background.py +37 -0
  28. seolpyo_mplchart/_chart/slider/g_event.py +353 -0
  29. seolpyo_mplchart/_chart/slider/h_data.py +102 -0
  30. seolpyo_mplchart/_chart/slider/test.py +71 -0
  31. seolpyo_mplchart/_config/ax.py +2 -0
  32. seolpyo_mplchart/_config/candle.py +1 -0
  33. seolpyo_mplchart/_config/cursor.py +4 -0
  34. seolpyo_mplchart/_config/figure.py +4 -4
  35. seolpyo_mplchart/_config/ma.py +2 -0
  36. seolpyo_mplchart/_config/nums.py +67 -0
  37. seolpyo_mplchart/_config/slider/config.py +2 -2
  38. seolpyo_mplchart/_config/slider/figure.py +3 -4
  39. seolpyo_mplchart/_config/slider/nav.py +3 -2
  40. seolpyo_mplchart/_config/unit.py +1 -1
  41. seolpyo_mplchart/_config/volume.py +1 -0
  42. seolpyo_mplchart/_utils/__init__.py +10 -0
  43. seolpyo_mplchart/_utils/nums.py +1 -0
  44. seolpyo_mplchart/_utils/theme/__init__.py +15 -0
  45. seolpyo_mplchart/_utils/theme/dark.py +57 -0
  46. seolpyo_mplchart/_utils/theme/light.py +56 -0
  47. seolpyo_mplchart/_utils/utils.py +28 -0
  48. seolpyo_mplchart/_utils/xl/__init__.py +15 -0
  49. seolpyo_mplchart/_utils/xl/csv.py +46 -0
  50. seolpyo_mplchart/_utils/xl/xlsx.py +49 -0
  51. seolpyo_mplchart/sample/apple.txt +6058 -0
  52. seolpyo_mplchart/sample/samsung.txt +5938 -0
  53. seolpyo_mplchart/test.py +5 -5
  54. {seolpyo_mplchart-2.0.0.3.dist-info → seolpyo_mplchart-2.1.0.4.dist-info}/METADATA +22 -14
  55. seolpyo_mplchart-2.1.0.4.dist-info/RECORD +90 -0
  56. seolpyo_mplchart-2.0.0.3.dist-info/RECORD +0 -50
  57. {seolpyo_mplchart-2.0.0.3.dist-info → seolpyo_mplchart-2.1.0.4.dist-info}/WHEEL +0 -0
  58. {seolpyo_mplchart-2.0.0.3.dist-info → seolpyo_mplchart-2.1.0.4.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,268 @@
1
+ from matplotlib.axes import Axes
2
+ from matplotlib.collections import LineCollection
3
+ from matplotlib.text import Text
4
+ import numpy as np
5
+ import pandas as pd
6
+
7
+ from ..._config import ConfigData
8
+
9
+
10
+ class Base:
11
+ CONFIG: ConfigData
12
+
13
+ key_volume: str
14
+ df: pd.DataFrame
15
+
16
+ index_list: list[int]
17
+
18
+ collection_candle: LineCollection
19
+ collection_volume: LineCollection
20
+ collection_ma: LineCollection
21
+
22
+ segment_candle: np.ndarray
23
+ segment_candle_wick: np.ndarray
24
+ segment_priceline: np.ndarray
25
+ facecolor_candle: np.ndarray
26
+ edgecolor_candle: np.ndarray
27
+
28
+ segment_volume: np.ndarray
29
+ segment_volume_wick: np.ndarray
30
+ facecolor_volume: np.ndarray
31
+ edgecolor_volume: np.ndarray
32
+
33
+ segment_ma: np.ndarray
34
+ edgecolor_ma: np.ndarray
35
+
36
+
37
+ class LimMixin(Base):
38
+ def _get_indices(self, ind_start, *, ind_end):
39
+ "조회 영역에 해당하는 index 가져오기"
40
+ if ind_start < 0:
41
+ ind_start = 0
42
+ if ind_end < 1:
43
+ ind_end = 1
44
+
45
+ if ind_end < ind_start:
46
+ msg = 'ind_end < ind_start'
47
+ msg += f' {ind_start=:,}'
48
+ msg += f' {ind_end=:,}'
49
+ raise Exception(msg)
50
+ return (ind_start, ind_end)
51
+
52
+ def _get_price_ylim(self, ind_start, *, ind_end):
53
+ ymin, ymax = (self.df['low'][ind_start:ind_end].min(), self.df['high'][ind_start:ind_end].max())
54
+
55
+ if ymin == ymax:
56
+ if ymax:
57
+ ymin, ymax = (round(ymax * 0.9, self.CONFIG.UNIT.digit+2), round(ymax * 1.1, self.CONFIG.UNIT.digit+2))
58
+ else:
59
+ ymin, ymax = (-5, 10)
60
+ else:
61
+ height = ymax - ymin
62
+ if height < 15:
63
+ height = 15
64
+
65
+ ymin = ymin - round(height / 20, self.CONFIG.UNIT.digit+2)
66
+ ymax = ymax + round(height / 10, self.CONFIG.UNIT.digit+2)
67
+
68
+ return (ymin, ymax)
69
+
70
+ def _get_volume_ylim(self, ind_start, *, ind_end):
71
+ if not self.key_volume:
72
+ ymax = 1
73
+ else:
74
+ series = self.df['volume'][ind_start:ind_end]
75
+ # print(f'{series=}')
76
+ ymax = series.max()
77
+ height = ymax
78
+ ymax = ymax + round(height / 5, self.CONFIG.UNIT.digit_volume+2)
79
+ if ymax < 1:
80
+ ymax = 1
81
+ # print(f'{ymax=}')
82
+ return (0, ymax)
83
+
84
+
85
+ class MaMixin(Base):
86
+ def _set_ma_collection_segments(self, ind_start, ind_end):
87
+ self.collection_ma.set_segments(self.segment_ma[:, ind_start:ind_end])
88
+ self.collection_ma.set_edgecolor(self.edgecolor_ma)
89
+ return
90
+
91
+
92
+ class VolumeMixin(Base):
93
+ def _set_volume_collection_segments(self, ind_start, ind_end):
94
+ if not self.key_volume:
95
+ self.collection_volume.set_segments([])
96
+ return
97
+
98
+ self.collection_volume.set_segments(self.segment_volume[ind_start:ind_end])
99
+ self.collection_volume.set_linewidth(self.CONFIG.VOLUME.linewidth)
100
+ self.collection_volume.set_facecolor(self.facecolor_volume[ind_start:ind_end])
101
+ self.collection_volume.set_edgecolor(self.edgecolor_volume[ind_start:ind_end])
102
+ return
103
+
104
+ def _set_volume_collection_wick_segments(self, ind_start, ind_end):
105
+ # print(f'{(ind_start, ind_end)=}')
106
+ if not self.key_volume:
107
+ self.collection_volume.set_segments([])
108
+ return
109
+
110
+ seg_volume = self.segment_volume_wick[ind_start:ind_end]
111
+ seg_edgecolor_volume = self.edgecolor_volume[ind_start:ind_end]
112
+
113
+ self.collection_volume.set_segments(seg_volume)
114
+ self.collection_volume.set_linewidth(1.3)
115
+ self.collection_volume.set_facecolor([])
116
+ self.collection_volume.set_edgecolor(seg_edgecolor_volume)
117
+ return
118
+
119
+
120
+ class CandleMixin(Base):
121
+ def _set_candle_collection_segments(self, ind_start, ind_end):
122
+ # print(f'{self.edgecolor_candle[ind_start:ind_end]=}')
123
+ self.collection_candle.set_segments(self.segment_candle[ind_start:ind_end])
124
+ self.collection_candle.set_facecolor(self.facecolor_candle[ind_start:ind_end])
125
+ self.collection_candle.set_edgecolor(self.edgecolor_candle[ind_start:ind_end])
126
+ self.collection_candle.set_linewidth(self.CONFIG.CANDLE.linewidth)
127
+ self.collection_candle.set_antialiased(False)
128
+ return
129
+
130
+ def _set_candle_collection_wick_segments(self, ind_start, ind_end):
131
+ # print(f'{self.edgecolor_candle[ind_start:ind_end]=}')
132
+ self.collection_candle.set_segments(self.segment_candle_wick[ind_start:ind_end])
133
+ self.collection_candle.set_facecolor([])
134
+ self.collection_candle.set_edgecolor(self.edgecolor_candle[ind_start:ind_end])
135
+ self.collection_candle.set_linewidth(self.CONFIG.CANDLE.linewidth * 2)
136
+ self.collection_candle.set_antialiased(False)
137
+ return
138
+
139
+ def set_candle_collection_priceline_segments(self, ind_start, ind_end):
140
+ self.collection_candle.set_segments(self.segment_priceline[:, ind_start:ind_end])
141
+ self.collection_candle.set_facecolor([])
142
+ self.collection_candle.set_edgecolor(self.CONFIG.CANDLE.line_color)
143
+ self.collection_candle.set_linewidth(2)
144
+ self.collection_candle.set_antialiased(True)
145
+ return
146
+
147
+
148
+ class CollectionMixin(CandleMixin, VolumeMixin, MaMixin):
149
+ limit_candle = 400
150
+ limit_wick = 2_000
151
+
152
+ def set_collections(self, ind_start, *, ind_end):
153
+ if ind_start < 0:
154
+ ind_start = 0
155
+ indsub = ind_end - ind_start
156
+ # print(f'{indsub=:,}')
157
+
158
+ if not self.limit_candle or indsub < self.limit_candle:
159
+ # print('candle')
160
+ self._set_candle_collection_segments(ind_start, ind_end=ind_end)
161
+ self._set_volume_collection_segments(ind_start, ind_end=ind_end)
162
+ else:
163
+ self._set_volume_collection_wick_segments(ind_start, ind_end=ind_end)
164
+
165
+ if not self.limit_wick or indsub < self.limit_wick:
166
+ # print('wick')
167
+ self._set_candle_collection_wick_segments(ind_start, ind_end=ind_end)
168
+ else:
169
+ # print('line')
170
+ self.set_candle_collection_priceline_segments(ind_start, ind_end=ind_end)
171
+
172
+ self._set_ma_collection_segments(ind_start, ind_end=ind_end)
173
+ return
174
+
175
+
176
+ class AxisMixin(LimMixin, CollectionMixin):
177
+ limit_candle = 400
178
+ limit_wick = 2_000
179
+
180
+ ax_price: Axes
181
+ ax_volume: Axes
182
+
183
+ vxmin: int
184
+ vxmax: int
185
+ price_ymin: int
186
+ price_ymax: int
187
+ volume_ymax: int
188
+
189
+ def axis(self, xmin, *, xmax):
190
+ "조회 영역 변경"
191
+ # print('base axis')
192
+ self.set_collections(xmin, ind_end=xmax+1)
193
+
194
+ self.vxmin, self.vxmax = (xmin, xmax+1)
195
+ ind_start, ind_end = self._get_indices(xmin, ind_end=xmax)
196
+
197
+ self.price_ymin, self.price_ymax = self._get_price_ylim(ind_start, ind_end=ind_end)
198
+
199
+ # 주가 차트 xlim
200
+ self.ax_price.set_xlim(self.vxmin, self.vxmax)
201
+ # 주가 차트 ylim
202
+ self.ax_price.set_ylim(self.price_ymin, self.price_ymax)
203
+
204
+ # 거래량 차트 xlim
205
+ self.ax_volume.set_xlim(self.vxmin, self.vxmax)
206
+ self.volume_ymax = 1
207
+ if self.key_volume:
208
+ _, self.volume_ymax = self._get_volume_ylim(ind_start, ind_end=ind_end)
209
+ # 거래량 차트 ylim
210
+ self.ax_volume.set_ylim(0, self.volume_ymax)
211
+
212
+ self.set_xtick_labels(xmin, xmax=xmax)
213
+ return
214
+
215
+ def set_xtick_labels(self, xmin, *, xmax):
216
+ # x축에 일부 date 표시하기
217
+ xsub = xmax - xmin
218
+ xmiddle = xmin + (xsub // 2)
219
+ indices = [idx for idx in (xmin, xmiddle, xmax) if 0 <= idx and idx <= self.index_list[-1]]
220
+ # print(f'{xmiddle=}')
221
+ # print(f'{indices=}')
222
+
223
+ m = (xmiddle - xmin) // 2
224
+ ind_end = self.index_list[-1]
225
+ aligns = ['left', 'center', 'center']
226
+ if len(indices) < 2:
227
+ if xmin < 0 and self.index_list[-1] < xmax:
228
+ indices = [0, xmiddle, ind_end]
229
+ else:
230
+ if xmin <= 0:
231
+ if m <= xmax:
232
+ aligns = aligns[-2:]
233
+ indices = [0, xmax]
234
+ else:
235
+ aligns = aligns[-1:]
236
+ indices = [0]
237
+ else:
238
+ if xmin+m <= ind_end:
239
+ aligns = aligns[:2]
240
+ indices = [xmin, ind_end]
241
+ else:
242
+ aligns = aligns[:1]
243
+ indices = [ind_end]
244
+ elif len(indices) < 3:
245
+ if xmin < 0:
246
+ if 0 <= (xmiddle - m):
247
+ indices = [0] + indices
248
+ else:
249
+ aligns = aligns[-2:]
250
+ indices[0] = 0
251
+ else:
252
+ if (xmiddle + m) <= ind_end:
253
+ indices.append(ind_end)
254
+ else:
255
+ aligns = aligns[:2]
256
+ indices[-1] = ind_end
257
+
258
+ date_list = [self.df.iloc[idx]['date'] for idx in indices]
259
+ # 라벨을 노출할 틱 위치, major tick과 겹쳐서 무시되는 것 방지
260
+ self.ax_volume.set_xticks([idx+0.501 for idx in indices], minor=True)
261
+ # 라벨
262
+ self.ax_volume.set_xticklabels(date_list, minor=True)
263
+ labels: list[Text] = self.ax_volume.get_xticklabels(minor=True)
264
+ for label, align in zip(labels, aligns):
265
+ # 라벨 텍스트 정렬
266
+ label.set_horizontalalignment(align)
267
+ return
268
+
@@ -0,0 +1,62 @@
1
+ from .a_canvas import Figure
2
+
3
+
4
+ class Base:
5
+ _background = None
6
+ _background_background = None
7
+
8
+ _creating_background = False
9
+
10
+ figure: Figure
11
+
12
+ draw_chart: callable
13
+ draw_artists: callable
14
+ draw_background: callable
15
+
16
+ def _draw_canvas(self):
17
+ self._background = None
18
+ self._restore_region()
19
+ return
20
+
21
+ def _restore_region(self):
22
+ # print(f'{self._background=}')
23
+ if not self._background:
24
+ self._create_background()
25
+
26
+ self.figure.canvas.renderer.restore_region(self._background)
27
+ return
28
+
29
+ def _restore_region_background(self):
30
+ if not self._background:
31
+ self._create_background()
32
+
33
+ self.figure.canvas.renderer.restore_region(self._background_background)
34
+ return
35
+
36
+ def _create_background(self):
37
+ if self._creating_background:
38
+ return
39
+
40
+ self._creating_background = True
41
+ self._copy_bbox()
42
+ self._creating_background = False
43
+ return
44
+
45
+ def _copy_bbox(self):
46
+ renderer = self.figure.canvas.renderer
47
+
48
+ self.draw_background()
49
+ self._background_background = renderer.copy_from_bbox(self.figure.bbox)
50
+
51
+ self.draw_chart()
52
+ self.draw_artists()
53
+ self._background = renderer.copy_from_bbox(self.figure.bbox)
54
+
55
+ return
56
+
57
+
58
+ class BackgroundMixin(Base):
59
+ _background = None
60
+ _background_background = None
61
+ _creating_background = False
62
+
@@ -0,0 +1,66 @@
1
+ from matplotlib.axes import Axes
2
+ from matplotlib.backend_bases import PickEvent
3
+ from matplotlib.collections import LineCollection
4
+
5
+ from ..._config import ConfigData
6
+
7
+ from .a_canvas import Figure
8
+
9
+
10
+ class Base:
11
+ figure: Figure
12
+
13
+ _draw_canvas: callable
14
+ _set_figure_ratios: callable
15
+
16
+ def on_draw(self, e):
17
+ self._background = None
18
+ self._draw_canvas()
19
+ return
20
+
21
+ def on_resize(self, e):
22
+ self._set_figure_ratios()
23
+ return
24
+
25
+
26
+ class LegendMixin:
27
+ CONFIG: ConfigData
28
+
29
+ figure: Figure
30
+ ax_legend: Axes
31
+ collection_ma: LineCollection
32
+
33
+ def on_pick(self, e):
34
+ self._pick_legend_action(e)
35
+ return
36
+
37
+ def _pick_legend_action(self, e: PickEvent):
38
+ handle = e.artist
39
+ ax = handle.axes
40
+ # print(f'{(ax is self.ax_legend)=}')
41
+ if ax is not self.ax_legend:
42
+ return
43
+
44
+ visible = handle.get_alpha() == 0.2
45
+ handle.set_alpha(1.0 if visible else 0.2)
46
+
47
+ n = int(handle.get_label())
48
+ if visible:
49
+ self._visible_ma = {i for i in self.CONFIG.MA.ma_list if i in self._visible_ma or i == n}
50
+ else:
51
+ self._visible_ma = {i for i in self._visible_ma if i != n}
52
+
53
+ alphas = [(1 if i in self._visible_ma else 0) for i in reversed(self.CONFIG.MA.ma_list)]
54
+ self.collection_ma.set_alpha(alphas)
55
+
56
+ self.figure.canvas.draw()
57
+ return
58
+
59
+
60
+ class EventMixin(Base, LegendMixin):
61
+ def connect_events(self):
62
+ self.figure.canvas.mpl_connect('draw_event', lambda x: self.on_draw(x))
63
+ self.figure.canvas.mpl_connect('pick_event', lambda x: self.on_pick(x))
64
+ self.figure.canvas.mpl_connect('resize_event', lambda x: self.on_resize(x))
65
+ return
66
+
@@ -0,0 +1,138 @@
1
+ from matplotlib.axes import Axes
2
+ import numpy as np
3
+ import pandas as pd
4
+
5
+ from ..._config import ConfigData
6
+
7
+
8
+ class Base:
9
+ CONFIG: ConfigData
10
+ df: pd.DataFrame
11
+
12
+ key_date = 'date'
13
+ key_open, key_high, key_low, key_close = ('open', 'high', 'low', 'close')
14
+ key_volume = 'volume'
15
+
16
+ ax_price: Axes
17
+ index_list: list[int] = []
18
+
19
+ set_segments: callable
20
+ axis: callable
21
+
22
+ def get_default_xlim(self):
23
+ """
24
+ get_default_xlim.
25
+
26
+ space = int(self.index_list[-1] / 20)
27
+
28
+ Returns:
29
+ (int, int): (-space, self.index_list[-1]+space)
30
+ """
31
+ # print(f'{self.index_list[-1]=}')
32
+ space = int(self.index_list[-1] / 20)
33
+ return (-space, self.index_list[-1]+space)
34
+
35
+ def set_variables(self):
36
+ self.index_list.clear()
37
+ self.index_list = self.df.index.tolist()
38
+ self.xmin, self.xmax = (0, self.index_list[-1])
39
+
40
+ self.chart_price_ymax = round(self.df['high'].max() * 1.3, self.CONFIG.UNIT.digit+2)
41
+ if self.key_volume:
42
+ self.chart_volume_ymax = round(self.df['volume'].max() * 1.3, self.CONFIG.UNIT.digit_volume+2)
43
+ else:
44
+ self.chart_volume_ymax = 10
45
+
46
+ if not self.CONFIG.MA.ma_list:
47
+ self.CONFIG.MA.ma_list = []
48
+ else:
49
+ self.CONFIG.MA.ma_list = sorted(self.CONFIG.MA.ma_list)
50
+
51
+ return
52
+
53
+ def set_data(self, df: pd.DataFrame, *, change_xlim=True):
54
+ """
55
+ `if change_xlim`: change xlim with `self.get_default_xlim()` value
56
+
57
+ `if not change_xlim`: Keep the current xlim
58
+ """
59
+ self.df = self._convert_df(df)
60
+
61
+ self._add_columns()
62
+ # print(f'{self.df.columns=}')
63
+
64
+ self.set_variables()
65
+
66
+ self.set_segments()
67
+
68
+ if change_xlim:
69
+ xmin, xmax = self.get_default_xlim()
70
+ self.axis(xmin, xmax=xmax)
71
+ return
72
+
73
+ def _convert_df(self, df: pd.DataFrame):
74
+ keys = {
75
+ self.key_date: 'date',
76
+ self.key_open: 'open',
77
+ self.key_high: 'high',
78
+ self.key_low: 'low',
79
+ self.key_close: 'close',
80
+ self.key_volume: 'volume',
81
+ }
82
+ df.rename(columns=keys, inplace=True)
83
+
84
+ # df column 추출
85
+ if self.key_volume:
86
+ df = df[['date', 'open', 'high', 'low', 'close', 'volume']].copy()
87
+ else:
88
+ df = df[['date', 'open', 'high', 'low', 'close',]].copy()
89
+ df['volume'] = 0
90
+ df.loc[:, 'ymax_volume'] = df['volume'] * 1.2
91
+
92
+ # 오름차순 정렬
93
+ df = df.sort_values(['date'])
94
+ df = df.reset_index(drop=True)
95
+
96
+ return df
97
+
98
+ def _add_columns(self):
99
+ # 전일 종가 추가
100
+ self.df['pre_close'] = self.df['close'].shift(1).fillna(0)
101
+ # 거래정지인 경우 전일종가 적용
102
+ self.df.loc[self.df['close'] == 0, 'close'] = self.df['pre_close']
103
+ # 종가만 유효한 경우 종가로 통일
104
+ self.df.loc[(self.df['close'] != 0) & (self.df['open'] == 0), ['open', 'high', 'low']] = self.df['close']
105
+
106
+ # 가격이동평균선 계산
107
+ for ma in self.CONFIG.MA.ma_list:
108
+ self.df[f'ma{ma}'] = self.df['close'].rolling(ma).mean()
109
+
110
+ # 세그먼트 생성을 위한 column 추가
111
+ self.df['x'] = self.df.index + 0.5
112
+ self.df['left_candle'] = self.df['x'] - self.CONFIG.CANDLE.half_width
113
+ self.df['right_candle'] = self.df['x'] + self.CONFIG.CANDLE.half_width
114
+ self.df['left_volume'] = self.df['x'] - self.CONFIG.VOLUME.half_width
115
+ self.df['right_volume'] = self.df['x'] + self.CONFIG.VOLUME.half_width
116
+ self.df['zero'] = 0
117
+
118
+ self.df['is_up'] = np.where(self.df['open'] < self.df['close'], True, False)
119
+ self.df['top_candle'] = np.where(self.df['is_up'], self.df['close'], self.df['open'])
120
+ self.df['bottom_candle'] = np.where(self.df['is_up'], self.df['open'], self.df['close'])
121
+
122
+ return
123
+
124
+
125
+ class DataMixin(Base):
126
+ key_date = 'date'
127
+ key_open, key_high, key_low, key_close = ('open', 'high', 'low', 'close')
128
+ key_volume = 'volume'
129
+
130
+ index_list: list[int] = []
131
+
132
+ df: pd.DataFrame
133
+
134
+ chart_price_ymax: float
135
+ chart_volume_ymax: float
136
+ xmin: int
137
+ xmax: int
138
+
@@ -0,0 +1,58 @@
1
+ import sys
2
+ from pathlib import Path
3
+ name_pkg = 'seolpyo_mplchart'
4
+ path_pkg = Path(__file__)
5
+ while path_pkg.name != name_pkg:
6
+ path_pkg = path_pkg.parent
7
+ sys.path = [path_pkg.parent.__str__()] + sys.path
8
+
9
+ import json
10
+
11
+ import pandas as pd
12
+ import matplotlib.pyplot as plt
13
+
14
+ from seolpyo_mplchart._utils.theme import set_theme
15
+ from seolpyo_mplchart._chart.base import Chart
16
+
17
+
18
+ path_file = path_pkg / 'sample' / 'samsung.txt'
19
+ with open(path_file, 'r', encoding='utf-8') as txt:
20
+ data = json.load(txt)
21
+ df = pd.DataFrame(data[:])
22
+
23
+
24
+ class C(Chart):
25
+ # limit_wick = 200
26
+ t = 'light'
27
+ # watermark = ''
28
+ def __init__(self):
29
+ super().__init__()
30
+ self.figure.canvas.mpl_connect('button_press_event', lambda x: self.theme(x))
31
+
32
+ def theme(self, e):
33
+ btn = getattr(e, 'button')
34
+ # print(f'{str(btn)=}')
35
+ if str(btn) == '3':
36
+ # print('refresh')
37
+ if self.t == 'light':
38
+ self.t = 'dark'
39
+ else:
40
+ self.t = 'light'
41
+ # print(f'{self.t=}')
42
+ self.CONFIG = set_theme(self.CONFIG, theme=self.t)
43
+ self.refresh()
44
+ return
45
+
46
+
47
+ def run():
48
+ chart = C()
49
+ chart.set_data(df)
50
+ plt.show()
51
+ plt.close()
52
+ return
53
+
54
+
55
+ if __name__ == '__main__':
56
+ run()
57
+
58
+