seolpyo-mplchart 2.0.0.3__py3-none-any.whl → 2.1.0__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 (54) 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 +267 -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 +65 -0
  18. seolpyo_mplchart/_chart/cursor/g_event.py +233 -0
  19. seolpyo_mplchart/_chart/cursor/h_data.py +61 -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/candle.py +1 -0
  32. seolpyo_mplchart/_config/figure.py +3 -4
  33. seolpyo_mplchart/_config/ma.py +2 -0
  34. seolpyo_mplchart/_config/slider/config.py +2 -2
  35. seolpyo_mplchart/_config/slider/figure.py +3 -4
  36. seolpyo_mplchart/_config/slider/nav.py +3 -2
  37. seolpyo_mplchart/_config/volume.py +1 -0
  38. seolpyo_mplchart/_utils/__init__.py +10 -0
  39. seolpyo_mplchart/_utils/nums.py +67 -0
  40. seolpyo_mplchart/_utils/theme/__init__.py +15 -0
  41. seolpyo_mplchart/_utils/theme/dark.py +57 -0
  42. seolpyo_mplchart/_utils/theme/light.py +56 -0
  43. seolpyo_mplchart/_utils/utils.py +28 -0
  44. seolpyo_mplchart/_utils/xl/__init__.py +15 -0
  45. seolpyo_mplchart/_utils/xl/csv.py +46 -0
  46. seolpyo_mplchart/_utils/xl/xlsx.py +49 -0
  47. seolpyo_mplchart/sample/apple.txt +6058 -0
  48. seolpyo_mplchart/sample/samsung.txt +5938 -0
  49. seolpyo_mplchart/test.py +5 -5
  50. {seolpyo_mplchart-2.0.0.3.dist-info → seolpyo_mplchart-2.1.0.dist-info}/METADATA +21 -13
  51. seolpyo_mplchart-2.1.0.dist-info/RECORD +89 -0
  52. seolpyo_mplchart-2.0.0.3.dist-info/RECORD +0 -50
  53. {seolpyo_mplchart-2.0.0.3.dist-info → seolpyo_mplchart-2.1.0.dist-info}/WHEEL +0 -0
  54. {seolpyo_mplchart-2.0.0.3.dist-info → seolpyo_mplchart-2.1.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,143 @@
1
+ from matplotlib.axes import Axes
2
+ from matplotlib.collections import LineCollection
3
+ from matplotlib.lines import Line2D
4
+ from matplotlib.text import Text
5
+
6
+ from ..._config import ConfigData
7
+
8
+
9
+ class WatermarkMixin:
10
+ CONFIG: ConfigData
11
+
12
+ watermark = 'seolpyo mplchart'
13
+
14
+ ax_price: Axes
15
+
16
+ def _add_watermark(self):
17
+ self.artist_watermark = Text(
18
+ x=0.5, y=0.5, text='',
19
+ animated=True,
20
+ horizontalalignment='center', verticalalignment='center',
21
+ transform=self.ax_price.transAxes
22
+ )
23
+ self.ax_price.add_artist(self.artist_watermark)
24
+ return
25
+
26
+ def _set_watermark(self):
27
+ self.artist_watermark.set(animated=True, horizontalalignment='center', verticalalignment='center',)
28
+ self.artist_watermark.set_text(self.watermark)
29
+ self.artist_watermark.set_fontsize(self.CONFIG.FIGURE.WATERMARK.fontsize)
30
+ self.artist_watermark.set_color(self.CONFIG.FIGURE.WATERMARK.color)
31
+ self.artist_watermark.set_alpha(self.CONFIG.FIGURE.WATERMARK.alpha)
32
+ return
33
+
34
+
35
+ class CollectionMixin:
36
+ ax_price: Axes
37
+ ax_volume: Axes
38
+ ax_legend: Axes
39
+
40
+ def _add_collections(self):
41
+ self.collection_candle = LineCollection([], animated=True, linewidths=0.8)
42
+ self.ax_price.add_collection(self.collection_candle)
43
+
44
+ self.collection_ma = LineCollection([], animated=True, linewidths=1)
45
+ self.ax_price.add_collection(self.collection_ma)
46
+
47
+ self.collection_volume = LineCollection([], animated=True, linewidths=1)
48
+ self.ax_volume.add_collection(self.collection_volume)
49
+ return
50
+
51
+ def set_collection_candle(self, segment, facecolors, edgecolors):
52
+ self.collection_candle.set_segments(segment)
53
+ self.collection_candle.set_facecolor(facecolors)
54
+ self.collection_candle.set_edgecolor(edgecolors)
55
+ self.collection_candle.set_transform(self.ax_price.transData)
56
+ return
57
+
58
+ def set_collection_volume(self, segment, facecolors, edgecolors):
59
+ self.collection_volume.set_segments(segment)
60
+ self.collection_volume.set_facecolor(facecolors)
61
+ self.collection_volume.set_edgecolor(edgecolors)
62
+ self.collection_volume.set_transform(self.ax_volume.transData)
63
+ self.collection_volume.set_antialiased(False)
64
+ return
65
+
66
+
67
+ class MaMixin:
68
+ CONFIG: ConfigData
69
+ _visible_ma = set()
70
+
71
+ ax_legend: Axes
72
+ ax_price: Axes
73
+ collection_ma: LineCollection
74
+ _set_figure_ratios: callable
75
+
76
+ def set_collection_ma(self, segment, edgecolors):
77
+ self.collection_ma.set_segments(segment)
78
+ self.collection_ma.set_facecolor([])
79
+ self.collection_ma.set_edgecolor(edgecolors)
80
+ # print(self.collection_ma.get_linewidth())
81
+ self.collection_ma.set_linewidth(self.CONFIG.MA.linewidth)
82
+ self.collection_ma.set_transform(self.ax_price.transData)
83
+ return
84
+
85
+ def _set_legends(self):
86
+ legends = self.ax_legend.get_legend()
87
+ if legends:
88
+ legends.remove()
89
+
90
+ self._visible_ma.clear()
91
+
92
+ label_list, handle_list, edgecolor_list = ([], [], [])
93
+ # Legend Ax에 표시하는 선 segment
94
+ arr = [0, 1]
95
+ for n, ma in enumerate(self.CONFIG.MA.ma_list):
96
+ self._visible_ma.add(ma)
97
+ label_list.append(self.CONFIG.MA.format.format(ma))
98
+ try:
99
+ color = self.CONFIG.MA.color_list[n]
100
+ except:
101
+ color = self.CONFIG.MA.color_default
102
+ edgecolor_list.append(color)
103
+ handle_list.append(Line2D(arr, ydata=arr, color=color, linewidth=5, label=ma))
104
+
105
+ self.set_collection_ma([], edgecolors=edgecolor_list)
106
+
107
+ # 가격이동평균선 legend 생성
108
+ if handle_list:
109
+ legends = self.ax_legend.legend(
110
+ handle_list, label_list, loc='lower left', ncol=self.CONFIG.MA.ncol,
111
+ borderpad=0.55,
112
+ facecolor=self.CONFIG.AX.facecolor, edgecolor=self.CONFIG.AX.TICK.edgecolor,
113
+ labelcolor=self.CONFIG.AX.TICK.fontcolor,
114
+ )
115
+ for handle in legends.legend_handles:
116
+ # legend 클릭시 pick event가 발생할 수 있도록 설정
117
+ handle.set_picker(5)
118
+
119
+ # legend ax 크기 조정
120
+ self._set_figure_ratios()
121
+ return
122
+
123
+
124
+ class ArtistMixin(WatermarkMixin, CollectionMixin, MaMixin):
125
+ artist_watermark: Text
126
+ collection_candle: LineCollection
127
+ collection_volume: LineCollection
128
+ collection_ma: LineCollection
129
+
130
+ _visible_ma: set[int] = set()
131
+
132
+ def add_artists(self):
133
+ self._add_watermark()
134
+ self._set_watermark()
135
+ self._add_collections()
136
+ self._set_legends()
137
+ return
138
+
139
+ def set_artists(self):
140
+ self._set_watermark()
141
+ self._set_legends()
142
+ return
143
+
@@ -0,0 +1,100 @@
1
+ from matplotlib.axes import Axes
2
+ from matplotlib.collections import LineCollection
3
+ from matplotlib.text import Text
4
+
5
+ from .a_canvas import Figure
6
+
7
+
8
+ class Base:
9
+ candle_on_ma = True
10
+
11
+ watermark: str
12
+
13
+ figure: Figure
14
+ ax_legend: Axes
15
+ ax_price: Axes
16
+ ax_volume: Axes
17
+
18
+ artist_watermark: Text
19
+ collection_candle: LineCollection
20
+ collection_volume: LineCollection
21
+ collection_ma: LineCollection
22
+
23
+ def draw_chart(self):
24
+ self._draw_ax_price()
25
+ self._draw_ax_volume()
26
+ self._draw_chart_price()
27
+ self._draw_chart_volume()
28
+ return
29
+
30
+ def _draw_chart_volume(self):
31
+ renderer = self.figure.canvas.renderer
32
+
33
+ self.collection_volume.draw(renderer)
34
+ return
35
+
36
+ def _draw_chart_price(self):
37
+ renderer = self.figure.canvas.renderer
38
+ # print(f'{renderer=}')
39
+
40
+ # print(self.collection_candle.get_segments())
41
+ if self.candle_on_ma:
42
+ self.collection_ma.draw(renderer)
43
+ self.collection_candle.draw(renderer)
44
+ else:
45
+ self.collection_candle.draw(renderer)
46
+ self.collection_ma.draw(renderer)
47
+
48
+ if self.watermark:
49
+ if self.watermark != self.artist_watermark.get_text():
50
+ self.artist_watermark.set_text(self.watermark)
51
+ self.artist_watermark.draw(renderer)
52
+ return
53
+
54
+ def draw_artists(self):
55
+ return
56
+
57
+ def draw_background(self):
58
+ self._draw_ax(self.ax_price)
59
+ self._draw_ax(self.ax_volume)
60
+
61
+ legend = self.ax_legend.get_legend()
62
+ if legend:
63
+ legend.draw(self.figure.canvas.renderer)
64
+ return
65
+
66
+ def _draw_ax(self, ax: Axes):
67
+ renderer = self.figure.canvas.renderer
68
+
69
+ # ax 배경
70
+ ax.patch.draw(renderer)
71
+
72
+ # ax 외곽선
73
+ for spine in ax.spines.values():
74
+ spine.draw(renderer)
75
+
76
+ return
77
+
78
+ def _draw_ax_volume(self):
79
+ renderer = self.figure.canvas.renderer
80
+
81
+ # grid, tick, ticklabel
82
+ ax = self.ax_volume
83
+ for axis in (ax.xaxis, ax.yaxis):
84
+ axis.draw(renderer)
85
+ return
86
+
87
+ def _draw_ax_price(self):
88
+ renderer = self.figure.canvas.renderer
89
+ # print(f'{renderer=}')
90
+
91
+ # grid, tick, ticklabel
92
+ ax = self.ax_price
93
+ for axis in (ax.xaxis, ax.yaxis):
94
+ axis.draw(renderer)
95
+ return
96
+
97
+
98
+ class DrawMixin(Base):
99
+ candle_on_ma = True
100
+
@@ -0,0 +1,262 @@
1
+ import pandas as pd
2
+ import numpy as np
3
+
4
+ from ..._config import ConfigData
5
+
6
+
7
+ class DataMixin:
8
+ df: pd.DataFrame
9
+ CONFIG: ConfigData
10
+
11
+ def add_volume_color_column(self):
12
+ columns = ['facecolor_volume', 'edgecolor_volume']
13
+ face_rise = self.CONFIG.VOLUME.FACECOLOR.rise
14
+ face_fall = self.CONFIG.VOLUME.FACECOLOR.fall
15
+ edge_rise = self.CONFIG.VOLUME.EDGECOLOR.rise
16
+ edge_fall = self.CONFIG.VOLUME.EDGECOLOR.fall
17
+ face_doji = self.CONFIG.VOLUME.FACECOLOR.doji
18
+ edge_doji = self.CONFIG.VOLUME.EDGECOLOR.doji
19
+
20
+ # 주가 상승
21
+ self.df.loc[:, columns] = (face_rise, edge_rise)
22
+ if face_rise != face_fall or edge_rise != edge_fall:
23
+ # 주가 하락
24
+ condition = self.df['close'] < self.df['pre_close']
25
+ self.df.loc[condition, columns] = (face_fall, edge_fall)
26
+ if face_rise != face_doji or edge_rise != edge_doji:
27
+ # 보합
28
+ condition = self.df['close'] == self.df['pre_close']
29
+ self.df.loc[condition, columns] = (edge_doji, edge_doji)
30
+ return
31
+
32
+ def add_candle_color_column(self):
33
+ columns = ['facecolor', 'edgecolor']
34
+ face_bull_rise = self.CONFIG.CANDLE.FACECOLOR.bull_rise
35
+ face_bull_fall = self.CONFIG.CANDLE.FACECOLOR.bull_fall
36
+ face_bear_rise = self.CONFIG.CANDLE.FACECOLOR.bear_rise
37
+ face_bear_fall = self.CONFIG.CANDLE.FACECOLOR.bear_fall
38
+ edge_bull_rise = self.CONFIG.CANDLE.EDGECOLOR.bull_rise
39
+ edge_bull_fall = self.CONFIG.CANDLE.EDGECOLOR.bull_fall
40
+ edge_bear_rise = self.CONFIG.CANDLE.EDGECOLOR.bear_rise
41
+ edge_bear_fall = self.CONFIG.CANDLE.EDGECOLOR.bear_fall
42
+ doji = self.CONFIG.CANDLE.EDGECOLOR.doji
43
+
44
+ # 상승양봉
45
+ self.df.loc[:, columns] = (face_bull_rise, edge_bull_rise)
46
+ if face_bull_rise != face_bear_fall or edge_bull_rise != edge_bear_fall:
47
+ # 하락음봉
48
+ self.df.loc[self.df['close'] < self.df['open'], columns] = (face_bear_fall, edge_bear_fall)
49
+ if face_bull_rise != doji or face_bear_fall != doji or edge_bull_rise != doji or edge_bear_fall != doji:
50
+ # 보합
51
+ self.df.loc[self.df['close'] == self.df['open'], columns] = (doji, doji)
52
+
53
+ if face_bull_rise != face_bull_fall or edge_bull_rise != edge_bull_fall:
54
+ # 하락양봉(비우기)
55
+ self.df.loc[(self.df['facecolor'] == face_bull_rise) & (self.df['close'] <= self.df['pre_close']), columns] = (face_bull_fall, edge_bull_fall)
56
+ if face_bear_fall != face_bear_rise or edge_bear_fall != edge_bear_rise:
57
+ # 상승음봉(비우기)
58
+ self.df.loc[(self.df['facecolor'] == face_bear_fall) & (self.df['pre_close'] <= self.df['close']), columns] = (face_bear_rise, edge_bear_rise)
59
+ return
60
+
61
+
62
+ class VolumeSegmentMixin(DataMixin):
63
+ key_volume: str
64
+
65
+ segment_volume: np.ndarray
66
+ segment_volume_wick: np.ndarray
67
+ facecolor_volume: np.ndarray
68
+ edgecolor_volume: np.ndarray
69
+
70
+ def set_volume_color_segments(self):
71
+ self.add_volume_color_column()
72
+
73
+ self.facecolor_volume = self.df['facecolor_volume'].values
74
+ self.edgecolor_volume = self.df['edgecolor_volume'].values
75
+ return
76
+
77
+ def set_volume_segments(self):
78
+ # 거래량 바 세그먼트
79
+ segment_volume_wick = self.df[[
80
+ 'left_volume', 'zero',
81
+ 'left_volume', 'volume',
82
+ 'right_volume', 'volume',
83
+ 'right_volume', 'zero',
84
+ ]].values
85
+
86
+ self.segment_volume = segment_volume_wick.reshape(segment_volume_wick.shape[0], 4, 2)
87
+
88
+ # 거래량 심지 세그먼트
89
+ segment_volume_wick = self.df[[
90
+ 'x', 'zero',
91
+ 'x', 'volume',
92
+ ]].values
93
+ self.segment_volume_wick = segment_volume_wick.reshape(segment_volume_wick.shape[0], 2, 2)
94
+
95
+ return
96
+
97
+
98
+ class MethodMixin:
99
+ def get_candle_segment(self, *, is_up, x, left, right, top, bottom, high, low):
100
+ """
101
+ get candle segment
102
+
103
+ Args:
104
+ is_up (bool): (True if open < close else False)
105
+ x (float): center of candle
106
+ left (float): left of candle
107
+ right (float): right of candle
108
+ top (float): top of candle(close if `is_up` else open)
109
+ bottom (float): bottom of candle(open if `is_up` else close)
110
+ high (float): top of candle wick
111
+ low (float): bottom of candle wick
112
+
113
+ Returns:
114
+ tuple[tuple[float, float]]: candle segment
115
+ """
116
+ return (
117
+ (x, top),
118
+ (left, top), (left, bottom),
119
+ (x, bottom), (x, low), (x, bottom),
120
+ (right, bottom), (right, top),
121
+ (x, top), (x, high)
122
+ )
123
+
124
+ def get_bar_segment(self, *, is_up, x, left, right, top, bottom, high, low):
125
+ if is_up:
126
+ return (
127
+ (x, top),
128
+ (x, high),
129
+ (x, top),
130
+ (right, top),
131
+ (x, top),
132
+ (x, low),
133
+ (x, bottom),
134
+ (left, bottom),
135
+ (x, bottom),
136
+ )
137
+ return (
138
+ (x, top),
139
+ (x, high),
140
+ (x, top),
141
+ (left, top),
142
+ (x, top),
143
+ (x, low),
144
+ (x, bottom),
145
+ (right, bottom),
146
+ (x, bottom),
147
+ )
148
+
149
+
150
+ class CandleSegmentMixin(MethodMixin, DataMixin):
151
+ segment_candle: np.ndarray
152
+ segment_candle_wick: np.ndarray
153
+ segment_priceline: np.ndarray
154
+ facecolor_candle: np.ndarray
155
+ edgecolor_candle: np.ndarray
156
+
157
+ def set_candle_color_segments(self):
158
+ self.add_candle_color_column()
159
+
160
+ self.facecolor_candle = self.df['facecolor'].values
161
+ self.edgecolor_candle = self.df['edgecolor'].values
162
+ return
163
+
164
+ def set_candle_segments(self):
165
+ # 캔들 세그먼트
166
+ segment_candle = []
167
+ for x, left, right, top, bottom, is_up, high, low in zip(
168
+ self.df['x'].to_numpy().tolist(),
169
+ self.df['left_candle'].to_numpy().tolist(), self.df['right_candle'].to_numpy().tolist(),
170
+ self.df['top_candle'].to_numpy().tolist(), self.df['bottom_candle'].to_numpy().tolist(),
171
+ self.df['is_up'].to_numpy().tolist(),
172
+ self.df['high'].to_numpy().tolist(), self.df['low'].to_numpy().tolist(),
173
+ ):
174
+ segment_candle.append(
175
+ self.get_candle_segment(
176
+ is_up=is_up,
177
+ x=x, left=left, right=right,
178
+ top=top, bottom=bottom,
179
+ high=high, low=low,
180
+ )
181
+ )
182
+
183
+ self.segment_candle = np.array(segment_candle)
184
+ return
185
+
186
+ def _set_candle_segments(self):
187
+ # 심지 세그먼트
188
+ segment_wick = self.df[[
189
+ 'x', 'high',
190
+ 'x', 'low',
191
+ ]].values
192
+ self.segment_candle_wick = segment_wick.reshape(segment_wick.shape[0], 2, 2)
193
+ # 종가 세그먼트
194
+ segment_priceline = segment_wick = self.df[['x', 'close']].values
195
+ self.segment_priceline = segment_priceline.reshape(1, *segment_wick.shape)
196
+ return
197
+
198
+
199
+ class MaSegmentMixin(DataMixin):
200
+ _visible_ma: set
201
+
202
+ segment_ma: np.ndarray
203
+ edgecolor_ma: np.ndarray
204
+
205
+ def set_ma_segments(self):
206
+ # 주가 차트 가격이동평균선
207
+ key_ma = []
208
+ for i in reversed(self.CONFIG.MA.ma_list):
209
+ key_ma.append('x')
210
+ key_ma.append(f'ma{i}')
211
+ if key_ma:
212
+ segment_ma = self.df[key_ma].values
213
+ self.segment_ma = segment_ma.reshape(
214
+ segment_ma.shape[0], len(self.CONFIG.MA.ma_list), 2
215
+ ).swapaxes(0, 1)
216
+ return
217
+
218
+ def _set_ma_color_segments(self):
219
+ # 이평선 색상 가져오기
220
+ edgecolors = []
221
+ for n, _ in enumerate(self.CONFIG.MA.ma_list):
222
+ try:
223
+ c = self.CONFIG.MA.color_list[n]
224
+ except:
225
+ c = self.CONFIG.MA.color_default
226
+ edgecolors.append(c)
227
+
228
+ self.edgecolor_ma = list(reversed(edgecolors))
229
+
230
+ return
231
+
232
+
233
+ class SegmentMixin(CandleSegmentMixin, VolumeSegmentMixin, MaSegmentMixin):
234
+ segment_volume: np.ndarray
235
+ segment_volume_wick: np.ndarray
236
+ facecolor_volume: np.ndarray
237
+ edgecolor_volume: np.ndarray
238
+
239
+ segment_candle: np.ndarray
240
+ segment_candle_wick: np.ndarray
241
+ segment_priceline: np.ndarray
242
+ facecolor_candle: np.ndarray
243
+ edgecolor_candle: np.ndarray
244
+
245
+ segment_ma: np.ndarray
246
+ edgecolor_ma: np.ndarray
247
+
248
+ def set_segments(self):
249
+ self.set_candle_segments()
250
+ self._set_candle_segments()
251
+ self.set_volume_segments()
252
+ self.set_ma_segments()
253
+
254
+ self.set_color_segments()
255
+ return
256
+
257
+ def set_color_segments(self):
258
+ self.set_candle_color_segments()
259
+ self.set_volume_color_segments()
260
+ self._set_ma_color_segments()
261
+ return
262
+