seolpyo-mplchart 0.0.61__py3-none-any.whl → 0.1.0.1__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.
Potentially problematic release.
This version of seolpyo-mplchart might be problematic. Click here for more details.
- seolpyo_mplchart/__init__.py +74 -3
- seolpyo_mplchart/cursor.py +9 -17
- seolpyo_mplchart/draw.py +65 -63
- seolpyo_mplchart/slider.py +39 -84
- {seolpyo_mplchart-0.0.61.dist-info → seolpyo_mplchart-0.1.0.1.dist-info}/METADATA +1 -1
- seolpyo_mplchart-0.1.0.1.dist-info/RECORD +13 -0
- {seolpyo_mplchart-0.0.61.dist-info → seolpyo_mplchart-0.1.0.1.dist-info}/WHEEL +1 -1
- seolpyo_mplchart-0.0.61.dist-info/RECORD +0 -13
- {seolpyo_mplchart-0.0.61.dist-info → seolpyo_mplchart-0.1.0.1.dist-info}/top_level.txt +0 -0
seolpyo_mplchart/__init__.py
CHANGED
|
@@ -10,10 +10,75 @@ from pathlib import Path
|
|
|
10
10
|
from typing import Literal
|
|
11
11
|
|
|
12
12
|
import matplotlib.pyplot as plt
|
|
13
|
+
from matplotlib.figure import Figure
|
|
13
14
|
import pandas as pd
|
|
14
15
|
|
|
15
16
|
|
|
16
|
-
from .slider import Chart
|
|
17
|
+
from .slider import Chart as CM
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
'pd',
|
|
22
|
+
'plt',
|
|
23
|
+
|
|
24
|
+
'Chart',
|
|
25
|
+
|
|
26
|
+
'sample',
|
|
27
|
+
'show',
|
|
28
|
+
'close',
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class Chart(CM):
|
|
33
|
+
r"""
|
|
34
|
+
You can see the guidance document:
|
|
35
|
+
Korean: https://white.seolpyo.com/entry/147/
|
|
36
|
+
English: https://white.seolpyo.com/entry/148/
|
|
37
|
+
|
|
38
|
+
Variables:
|
|
39
|
+
unit_price, unit_volume: unit for price and volume. default ('원', '주').
|
|
40
|
+
|
|
41
|
+
figsize: figure size if you use plt.show(). default (12, 6).
|
|
42
|
+
ratio_ax_slider, ratio_ax_legend, ratio_ax_price, ratio_ax_volume: Axes ratio. default (3, 2, 18, 5).
|
|
43
|
+
adjust: figure adjust. default dict(top=0.95, bottom=0.05, left=0.01, right=0.93, wspace=0, hspace=0).
|
|
44
|
+
slider_top: ax_slider is located at the top or bottom. default True.
|
|
45
|
+
color_background: color of background. default '#fafafa'.
|
|
46
|
+
color_grid: color of grid. default '#d0d0d0'.
|
|
47
|
+
|
|
48
|
+
df: stock data.
|
|
49
|
+
date: date column key. default 'date'
|
|
50
|
+
Open, high, low, close: price column key. default ('open', 'high', 'low', 'close')
|
|
51
|
+
volume: volume column key. default 'volume'
|
|
52
|
+
|
|
53
|
+
label_ma: moving average legend label format. default '{}일선'
|
|
54
|
+
list_ma: Decide how many days to draw the moving average line. default (5, 20, 60, 120, 240)
|
|
55
|
+
list_macolor: Color the moving average line. If the number of colors is greater than the moving average line, black is applied. default ('darkred', 'fuchsia', 'olive', 'orange', 'navy', 'darkmagenta', 'limegreen', 'darkcyan',)
|
|
56
|
+
|
|
57
|
+
candle_on_ma: Decide whether to draw candles on the moving average line. default True
|
|
58
|
+
color_sliderline: Color of closing price line in ax_slider. default 'k'
|
|
59
|
+
color_navigatorline: Color of left and right dividing lines in selected area. default '#1e78ff'
|
|
60
|
+
color_navigator: Color of unselected area. default 'k'
|
|
61
|
+
|
|
62
|
+
color_up: The color of the candle. When the closing price is greater than the opening price. default '#fe3032'
|
|
63
|
+
color_down: The color of the candle. When the opening price is greater than the opening price. default '#0095ff'
|
|
64
|
+
color_flat: The color of the candle. WWhen the closing price is the same as the opening price. default 'k'
|
|
65
|
+
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'
|
|
66
|
+
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'
|
|
67
|
+
colors_volume: The color of the volume bar. default '#1f77b4'
|
|
68
|
+
|
|
69
|
+
lineKwargs: Options applied to horizontal and vertical lines drawn along the mouse position. default dict(edgecolor='k', linewidth=1, linestyle='-')
|
|
70
|
+
textboxKwargs: Options that apply to the information text box. dufault dict(boxstyle='round', facecolor='w')
|
|
71
|
+
|
|
72
|
+
fraction: Decide whether to express information as a fraction. default False
|
|
73
|
+
candleformat: Candle information text format. default '{}\n\n종가: {}\n등락률: {}\n대비: {}\n시가: {}({})\n고가: {}({})\n저가: {}({})\n거래량: {}({})'
|
|
74
|
+
volumeformat: Volume information text format. default '{}\n\n거래량 : {}\n거래량증가율: {}'
|
|
75
|
+
digit_price, digit_volume: Number of decimal places expressed in informational text. default (0, 0)
|
|
76
|
+
|
|
77
|
+
min_distance: Minimum number of candles that can be selected with the slider. default 30
|
|
78
|
+
simpler: Decide whether to display candles simply when moving the chart. default False
|
|
79
|
+
limit_volume: Maximum number of volume bars drawn when moving the chart. default 2_000
|
|
80
|
+
"""
|
|
81
|
+
pass
|
|
17
82
|
|
|
18
83
|
|
|
19
84
|
_name = {'samsung', 'apple'}
|
|
@@ -37,7 +102,7 @@ def sample(name: Literal['samsung', 'apple']='samsung'):
|
|
|
37
102
|
c.volumeformat = '{}\n\nvolume: {}\nvolume rate: {}'
|
|
38
103
|
c.set_data(df)
|
|
39
104
|
show()
|
|
40
|
-
|
|
105
|
+
close()
|
|
41
106
|
return
|
|
42
107
|
|
|
43
108
|
|
|
@@ -45,5 +110,11 @@ def show():
|
|
|
45
110
|
return plt.show()
|
|
46
111
|
|
|
47
112
|
|
|
113
|
+
def close(fig: int|str|Figure|None='all'):
|
|
114
|
+
return plt.close(fig)
|
|
115
|
+
|
|
116
|
+
|
|
48
117
|
if __name__ == '__main__':
|
|
49
|
-
sample('apple')
|
|
118
|
+
sample('apple')
|
|
119
|
+
|
|
120
|
+
|
seolpyo_mplchart/cursor.py
CHANGED
|
@@ -11,9 +11,6 @@ from .utils import float_to_str
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class Mixin:
|
|
14
|
-
def create_background(self):
|
|
15
|
-
"This function works befor canvas.copy_from_bbox()."
|
|
16
|
-
return
|
|
17
14
|
def on_draw(self, e):
|
|
18
15
|
"This function works if draw event active."
|
|
19
16
|
return
|
|
@@ -64,12 +61,13 @@ class CollectionMixin(DrawMixin):
|
|
|
64
61
|
_set_key = {'rate', 'compare', 'rate_open', 'rate_high', 'rate_low', 'rate_volume',}
|
|
65
62
|
|
|
66
63
|
class DataMixin(CollectionMixin):
|
|
67
|
-
def _generate_data(self, df
|
|
64
|
+
def _generate_data(self, df, sort_df=True, calc_ma=True):
|
|
68
65
|
for i in ['date', 'Open', 'high', 'low', 'close', 'volume']:
|
|
69
66
|
v = getattr(self, i)
|
|
70
67
|
if v in _set_key: raise Exception(f'you can not set "self.{i}" value in {_set_key}.\nself.{i}={v!r}')
|
|
71
68
|
|
|
72
|
-
super()._generate_data(df)
|
|
69
|
+
super()._generate_data(df, sort_df, calc_ma)
|
|
70
|
+
df = self.df
|
|
73
71
|
|
|
74
72
|
df['rate'] = ((df[self.close] - df[self.close].shift(1)) / df[self.close] * 100).__round__(2).fillna(0)
|
|
75
73
|
df['compare'] = (df[self.close] - df[self.close].shift(1)).fillna(0)
|
|
@@ -77,6 +75,8 @@ class DataMixin(CollectionMixin):
|
|
|
77
75
|
df['rate_high'] = ((df[self.high] - df[self.close].shift(1)) / df[self.close] * 100).__round__(2).fillna(0)
|
|
78
76
|
df['rate_low'] = ((df[self.low] - df[self.close].shift(1)) / df[self.close] * 100).__round__(2).fillna(0)
|
|
79
77
|
df['rate_volume'] = ((df[self.volume] - df[self.volume].shift(1)) / df[self.volume].shift(1) * 100).__round__(2).fillna(0)
|
|
78
|
+
|
|
79
|
+
self.df = df
|
|
80
80
|
return
|
|
81
81
|
|
|
82
82
|
def set_text_coordante(self, vmin, vmax, pmin, pmax, volmax):
|
|
@@ -123,8 +123,8 @@ class LineMixin(DataMixin):
|
|
|
123
123
|
self.canvas.blit()
|
|
124
124
|
return
|
|
125
125
|
|
|
126
|
-
def
|
|
127
|
-
super().
|
|
126
|
+
def _set_data(self, df: pd.DataFrame, sort_df=True, calc_ma=True, change_lim=True):
|
|
127
|
+
super()._set_data(df, sort_df, calc_ma, change_lim)
|
|
128
128
|
|
|
129
129
|
self.vmin, self.vmax = (self.xmin, self.xmax)
|
|
130
130
|
return
|
|
@@ -246,8 +246,8 @@ class InfoMixin(LineMixin):
|
|
|
246
246
|
volumeformat = '{}\n\n거래량 : {}\n거래량증가율: {}'
|
|
247
247
|
digit_price, digit_volume = (0, 0)
|
|
248
248
|
|
|
249
|
-
def
|
|
250
|
-
super().
|
|
249
|
+
def _set_data(self, df: pd.DataFrame, sort_df=True, calc_ma=True, change_lim=True):
|
|
250
|
+
super()._set_data(df, sort_df, calc_ma, change_lim)
|
|
251
251
|
|
|
252
252
|
# 슬라이더 날짜 텍스트 y 위치
|
|
253
253
|
y = self._slider_ymax - (self._slider_ymax - self._slider_ymin) / 6
|
|
@@ -395,10 +395,6 @@ class CursorMixin(InfoMixin):
|
|
|
395
395
|
|
|
396
396
|
|
|
397
397
|
class Chart(CursorMixin, CM, Mixin):
|
|
398
|
-
def _generate_data(self, df):
|
|
399
|
-
super()._generate_data(df)
|
|
400
|
-
return self.generate_data(df)
|
|
401
|
-
|
|
402
398
|
def _on_draw(self, e):
|
|
403
399
|
super()._on_draw(e)
|
|
404
400
|
return self.on_draw(e)
|
|
@@ -407,10 +403,6 @@ class Chart(CursorMixin, CM, Mixin):
|
|
|
407
403
|
self.on_pick(e)
|
|
408
404
|
return super()._on_pick(e)
|
|
409
405
|
|
|
410
|
-
def _draw_artist(self):
|
|
411
|
-
super()._draw_artist()
|
|
412
|
-
return self.create_background()
|
|
413
|
-
|
|
414
406
|
def _blit(self):
|
|
415
407
|
super()._blit()
|
|
416
408
|
return self.on_blit()
|
seolpyo_mplchart/draw.py
CHANGED
|
@@ -9,10 +9,6 @@ from .base import Base
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class Mixin:
|
|
12
|
-
def generate_data(self, df):
|
|
13
|
-
"This function works after data generate process is done."
|
|
14
|
-
return
|
|
15
|
-
|
|
16
12
|
def on_blit(self):
|
|
17
13
|
"This function works after cavas.blit()."
|
|
18
14
|
return
|
|
@@ -27,7 +23,7 @@ class Mixin:
|
|
|
27
23
|
return
|
|
28
24
|
|
|
29
25
|
|
|
30
|
-
_set_key = {'x', 'left', 'right', 'top', 'bottom',}
|
|
26
|
+
_set_key = {'zero', 'x', 'left', 'right', 'top', 'bottom',}
|
|
31
27
|
|
|
32
28
|
class DataMixin(Base):
|
|
33
29
|
df: pd.DataFrame
|
|
@@ -48,7 +44,7 @@ class DataMixin(Base):
|
|
|
48
44
|
color_down_up = 'w'
|
|
49
45
|
colors_volume = '#1f77b4'
|
|
50
46
|
|
|
51
|
-
def _generate_data(self, df: pd.DataFrame):
|
|
47
|
+
def _generate_data(self, df: pd.DataFrame, sort_df=True, calc_ma=True):
|
|
52
48
|
for i in ['date', 'Open', 'high', 'low', 'close', 'volume']:
|
|
53
49
|
k: str = getattr(self, i)
|
|
54
50
|
if k in _set_key: raise Exception(f'you can not set "self.{i}" value in {_set_key}.\nself.{i}={k!r}')
|
|
@@ -56,7 +52,13 @@ class DataMixin(Base):
|
|
|
56
52
|
dtype = df[k].dtype
|
|
57
53
|
if not isinstance(dtype, (np.dtypes.Float64DType, np.dtypes.Int64DType, np.dtypes.Float32DType, np.dtypes.Int32DType)): raise TypeError(f'Data column type must be "float64" or "int64" or "float32" or "int32".(excluding "date" column)\ndf[{k!r}].dtype={dtype!r}')
|
|
58
54
|
|
|
59
|
-
|
|
55
|
+
# DataFrame 정렬
|
|
56
|
+
if sort_df:
|
|
57
|
+
df = df.sort_values([self.date]).reset_index()
|
|
58
|
+
|
|
59
|
+
if not self.list_ma: self.list_ma = tuple()
|
|
60
|
+
if calc_ma:
|
|
61
|
+
for i in self.list_ma: df[f'ma{i}'] = df[self.close].rolling(i).mean()
|
|
60
62
|
|
|
61
63
|
candlewidth_half = 0.3
|
|
62
64
|
volumewidth_half = 0.36
|
|
@@ -72,7 +74,7 @@ class DataMixin(Base):
|
|
|
72
74
|
df['bottom'] = np.where(df[self.close] < df[self.Open], df[self.close], df[self.Open])
|
|
73
75
|
|
|
74
76
|
# 양봉
|
|
75
|
-
df.loc[:, ['facecolor', 'edgecolor']] = (self.color_up, self.color_up)
|
|
77
|
+
df.loc[:, ['zero', 'facecolor', 'edgecolor']] = (0, self.color_up, self.color_up)
|
|
76
78
|
if self.color_up != self.color_down:
|
|
77
79
|
# 음봉
|
|
78
80
|
df.loc[df[self.close] < df[self.Open], ['facecolor', 'edgecolor']] = (self.color_down, self.color_down)
|
|
@@ -89,7 +91,6 @@ class DataMixin(Base):
|
|
|
89
91
|
self.df = df
|
|
90
92
|
return
|
|
91
93
|
|
|
92
|
-
|
|
93
94
|
class CollectionMixin(DataMixin):
|
|
94
95
|
color_sliderline = 'k'
|
|
95
96
|
|
|
@@ -123,57 +124,55 @@ class CollectionMixin(DataMixin):
|
|
|
123
124
|
|
|
124
125
|
return
|
|
125
126
|
|
|
126
|
-
def _get_candlesegment(self, s: pd.Series):
|
|
127
|
-
v = s.values
|
|
128
|
-
segment = (
|
|
129
|
-
(v[0], v[3]), # 심지 상단
|
|
130
|
-
(v[0], v[5]), # 몸통 상단
|
|
131
|
-
(v[1], v[5]), # 몸통 상단 좌측
|
|
132
|
-
(v[1], v[6]), # 몸통 하단 좌측
|
|
133
|
-
(v[0], v[6]), # 몸통 하단
|
|
134
|
-
(v[0], v[4]), # 심지 하단
|
|
135
|
-
(v[0], v[6]), # 몸통 하단
|
|
136
|
-
(v[2], v[6]), # 몸통 하단 우측
|
|
137
|
-
(v[2], v[5]), # 몸통 상단 우측
|
|
138
|
-
(v[0], v[5]), # 몸통 상단
|
|
139
|
-
)
|
|
140
|
-
return segment
|
|
141
|
-
|
|
142
|
-
def _get_volumesegment(self, s: pd.Series):
|
|
143
|
-
v = s.values
|
|
144
|
-
segment = (
|
|
145
|
-
(v[0], 0), # 몸통 하단 좌측
|
|
146
|
-
(v[0], v[2]), # 몸통 상단 좌측
|
|
147
|
-
(v[1], v[2]), # 몸통 상단 우측
|
|
148
|
-
(v[1], 0), # 몸통 하단 우측
|
|
149
|
-
)
|
|
150
|
-
return segment
|
|
151
|
-
|
|
152
127
|
def _set_collection(self):
|
|
153
|
-
|
|
154
|
-
|
|
128
|
+
candleseg = self.df[[
|
|
129
|
+
'x', self.high,
|
|
130
|
+
'x', 'top',
|
|
131
|
+
'left', 'top',
|
|
132
|
+
'left', 'bottom',
|
|
133
|
+
'x', 'bottom',
|
|
134
|
+
'x', self.low,
|
|
135
|
+
'x', 'bottom',
|
|
136
|
+
'right', 'bottom',
|
|
137
|
+
'right', 'top',
|
|
138
|
+
'x', 'top',
|
|
139
|
+
]].values
|
|
140
|
+
candleseg = candleseg.reshape(candleseg.shape[0], 10, 2)
|
|
141
|
+
|
|
142
|
+
self.candlecollection.set_segments(candleseg)
|
|
143
|
+
self.candlecollection.set_facecolor(self.df['facecolor'].values)
|
|
144
|
+
self.candlecollection.set_edgecolor(self.df['edgecolor'].values)
|
|
145
|
+
|
|
146
|
+
volseg = self.df[[
|
|
147
|
+
'left', 'zero',
|
|
148
|
+
'left', self.volume,
|
|
149
|
+
'right', self.volume,
|
|
150
|
+
'right', 'zero',
|
|
151
|
+
]].values
|
|
152
|
+
volseg = volseg.reshape(volseg.shape[0], 4, 2)
|
|
153
|
+
|
|
154
|
+
self.volumecollection.set_segments(volseg)
|
|
155
155
|
|
|
156
156
|
self._set_macollection()
|
|
157
157
|
|
|
158
158
|
# 가격이동평균선
|
|
159
|
-
|
|
159
|
+
maseg = reversed(self._masegment.values())
|
|
160
160
|
colors, widths = ([], [])
|
|
161
161
|
for i in reversed(self._macolors.values()): (colors.append(i), widths.append(1))
|
|
162
|
-
self.macollection.set_segments(
|
|
162
|
+
self.macollection.set_segments(maseg)
|
|
163
163
|
self.macollection.set_edgecolor(colors)
|
|
164
164
|
|
|
165
165
|
# 슬라이더 선형차트
|
|
166
|
-
|
|
166
|
+
keys = []
|
|
167
|
+
for i in reversed(self.list_ma):
|
|
168
|
+
keys.append('x')
|
|
169
|
+
keys.append(f'ma{i}')
|
|
170
|
+
sliderseg = self.df[keys + ['x', self.close]].values
|
|
171
|
+
sliderseg = sliderseg.reshape(sliderseg.shape[0], self.list_ma.__len__()+1, 2).swapaxes(0, 1)
|
|
167
172
|
(colors.append(self.color_sliderline), widths.append(1.8))
|
|
168
|
-
self.slidercollection.set_segments(
|
|
173
|
+
self.slidercollection.set_segments(sliderseg)
|
|
169
174
|
self.slidercollection.set_edgecolor(colors)
|
|
170
175
|
self.slidercollection.set_linewidth(widths)
|
|
171
|
-
|
|
172
|
-
self.candlecollection.set_segments(self.df['candlesegment'])
|
|
173
|
-
self.candlecollection.set_facecolor(self.df['facecolor'].values)
|
|
174
|
-
self.candlecollection.set_edgecolor(self.df['edgecolor'].values)
|
|
175
|
-
|
|
176
|
-
self.volumecollection.set_segments(self.df['volumesegment'])
|
|
177
176
|
return
|
|
178
177
|
|
|
179
178
|
def _set_macollection(self):
|
|
@@ -188,7 +187,10 @@ class CollectionMixin(DataMixin):
|
|
|
188
187
|
try: c = self.list_macolor[n]
|
|
189
188
|
except: c = self.color_sliderline
|
|
190
189
|
self._macolors[i] = c
|
|
191
|
-
|
|
190
|
+
# seg = self.df['x', f'ma{i}'].values
|
|
191
|
+
seg = self.df.loc[self.df[f'ma{i}'] != np.nan, ['x', f'ma{i}']].values
|
|
192
|
+
# print(f'{seg[:5]=}')
|
|
193
|
+
self._masegment[i] = seg
|
|
192
194
|
|
|
193
195
|
handles.append(Line2D([0, 1], [0, 1], color=c, linewidth=5, label=i))
|
|
194
196
|
labels.append(self.label_ma.format(i))
|
|
@@ -300,25 +302,29 @@ class BackgroundMixin(CollectionMixin):
|
|
|
300
302
|
self.canvas.renderer.restore_region(self.background)
|
|
301
303
|
return
|
|
302
304
|
|
|
303
|
-
|
|
304
305
|
class DrawMixin(BackgroundMixin):
|
|
305
|
-
def set_data(self, df: pd.DataFrame):
|
|
306
|
-
self.
|
|
306
|
+
def set_data(self, df: pd.DataFrame, sort_df=True, calc_ma=True, change_lim=True):
|
|
307
|
+
self._set_data(df, sort_df, calc_ma, change_lim)
|
|
308
|
+
return self.df
|
|
309
|
+
|
|
310
|
+
def _set_data(self, df: pd.DataFrame, sort_df=True, calc_ma=True, change_lim=True):
|
|
311
|
+
self._generate_data(df, sort_df, calc_ma)
|
|
307
312
|
self._set_collection()
|
|
308
|
-
self._draw_collection()
|
|
313
|
+
self._draw_collection(change_lim)
|
|
309
314
|
return
|
|
310
315
|
|
|
311
|
-
def _draw_collection(self):
|
|
316
|
+
def _draw_collection(self, change_lim=True):
|
|
312
317
|
xmax = self.df['x'].values[-1] + 1
|
|
313
318
|
|
|
314
319
|
xspace = xmax / 40
|
|
315
320
|
self.xmin, self.xmax = (-xspace, xmax+xspace)
|
|
316
321
|
# 슬라이더 xlim
|
|
317
322
|
self.ax_slider.set_xlim(self.xmin, self.xmax)
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
323
|
+
if change_lim:
|
|
324
|
+
# 주가 xlim
|
|
325
|
+
self.ax_price.set_xlim(self.xmin, self.xmax)
|
|
326
|
+
# 거래량 xlim
|
|
327
|
+
self.ax_volume.set_xlim(self.xmin, self.xmax)
|
|
322
328
|
|
|
323
329
|
ymin, ymax = (self.df[self.low].min(), self.df[self.high].max())
|
|
324
330
|
ysub = (ymax - ymin) / 15
|
|
@@ -329,19 +335,15 @@ class DrawMixin(BackgroundMixin):
|
|
|
329
335
|
|
|
330
336
|
# 주가 ylim
|
|
331
337
|
self._price_ymin, self._price_ymax = (ymin-ysub, ymax+ysub)
|
|
332
|
-
self.ax_price.set_ylim(self._price_ymin, self._price_ymax)
|
|
338
|
+
if change_lim: self.ax_price.set_ylim(self._price_ymin, self._price_ymax)
|
|
333
339
|
|
|
334
340
|
# 거래량 ylim
|
|
335
341
|
self._vol_ymax = self.df[self.volume].max() * 1.2
|
|
336
|
-
self.ax_volume.set_ylim(0, self._vol_ymax)
|
|
342
|
+
if change_lim: self.ax_volume.set_ylim(0, self._vol_ymax)
|
|
337
343
|
return
|
|
338
344
|
|
|
339
345
|
|
|
340
346
|
class Chart(DrawMixin, Mixin):
|
|
341
|
-
def _generate_data(self, df):
|
|
342
|
-
super()._generate_data(df)
|
|
343
|
-
return self.generate_data(self.df)
|
|
344
|
-
|
|
345
347
|
def _on_draw(self, e):
|
|
346
348
|
super()._on_draw(e)
|
|
347
349
|
return self.on_draw(e)
|
seolpyo_mplchart/slider.py
CHANGED
|
@@ -21,6 +21,10 @@ class Mixin(CursorMixin):
|
|
|
21
21
|
def on_release(self, e):
|
|
22
22
|
"This function works if mouse button release event active."
|
|
23
23
|
return
|
|
24
|
+
def draw_artist(self):
|
|
25
|
+
"This function works before canvas.blit()."
|
|
26
|
+
return
|
|
27
|
+
|
|
24
28
|
|
|
25
29
|
class NavgatorMixin(Mixin):
|
|
26
30
|
min_distance = 30
|
|
@@ -39,8 +43,8 @@ class NavgatorMixin(Mixin):
|
|
|
39
43
|
self.ax_slider.add_artist(self.navigator)
|
|
40
44
|
return
|
|
41
45
|
|
|
42
|
-
def
|
|
43
|
-
super().
|
|
46
|
+
def _set_data(self, df: pd.DataFrame, sort_df=True, calc_ma=True, change_lim=True):
|
|
47
|
+
super()._set_data(df, sort_df, calc_ma, False)
|
|
44
48
|
|
|
45
49
|
# 네비게이터 라인 선택 영역
|
|
46
50
|
xsub = self.xmax - self.xmin
|
|
@@ -152,13 +156,12 @@ class BackgroundMixin(NavgatorMixin):
|
|
|
152
156
|
|
|
153
157
|
|
|
154
158
|
class DrawMixin(BackgroundMixin):
|
|
155
|
-
def
|
|
156
|
-
super().
|
|
159
|
+
def _set_data(self, df: pd.DataFrame, sort_df=True, calc_ma=True, change_lim=True):
|
|
160
|
+
super()._set_data(df, sort_df, calc_ma, change_lim)
|
|
157
161
|
|
|
158
162
|
# 네비게이터 높이 설정
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
self._ymiddle = ysub / 2
|
|
163
|
+
ysub = self._slider_ymax - self._slider_ymin
|
|
164
|
+
self._ymiddle = self._slider_ymax - ysub / 2
|
|
162
165
|
self.navigator.set_linewidth((ysub, 5))
|
|
163
166
|
return
|
|
164
167
|
|
|
@@ -252,8 +255,8 @@ class LimMixin(DrawMixin):
|
|
|
252
255
|
self._lim()
|
|
253
256
|
|
|
254
257
|
self._restore_region(empty=True)
|
|
255
|
-
self.
|
|
256
|
-
self.
|
|
258
|
+
self._copy_bbox()
|
|
259
|
+
self._restore_region()
|
|
257
260
|
self._blit()
|
|
258
261
|
return
|
|
259
262
|
|
|
@@ -315,6 +318,7 @@ class LimMixin(DrawMixin):
|
|
|
315
318
|
class SimpleMixin(LimMixin):
|
|
316
319
|
simpler = False
|
|
317
320
|
limit_volume = 1_500
|
|
321
|
+
default_left, default_right = (180, 10)
|
|
318
322
|
|
|
319
323
|
def __init__(self, *args, **kwargs):
|
|
320
324
|
super().__init__(*args, **kwargs)
|
|
@@ -330,35 +334,34 @@ class SimpleMixin(LimMixin):
|
|
|
330
334
|
self.ax_volume.add_collection(self.blitvolume)
|
|
331
335
|
return
|
|
332
336
|
|
|
333
|
-
def
|
|
334
|
-
super().
|
|
337
|
+
def _set_data(self, df: pd.DataFrame, sort_df=True, calc_ma=True, change_lim=True):
|
|
338
|
+
super()._set_data(df, sort_df, calc_ma, False)
|
|
335
339
|
|
|
336
|
-
seg = self.df[['x', self.high, self.low]].
|
|
340
|
+
seg = self.df[['x', self.high, 'x', self.low]].values
|
|
341
|
+
seg = seg.reshape(seg.shape[0], 2, 2)
|
|
337
342
|
self.blitcandle.set_segments(seg)
|
|
338
343
|
self.blitcandle.set_edgecolor(self.df['edgecolor'])
|
|
339
|
-
self.priceline.set_verts(pd.array([self.df[['x', self.close]].apply(tuple, axis=1).to_list()]))
|
|
340
344
|
|
|
341
|
-
|
|
345
|
+
pseg = self.df[['x', self.close]].values
|
|
346
|
+
self.priceline.set_verts(pseg.reshape(1, *pseg.shape))
|
|
347
|
+
|
|
342
348
|
l = self.df.__len__()
|
|
343
349
|
if l < self.limit_volume:
|
|
344
|
-
volseg = self.df.loc[:, ['x', self.volume]].
|
|
350
|
+
volseg = self.df.loc[:, ['x', 'zero', 'x', self.volume]].values
|
|
345
351
|
else:
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
index = self.df.index[-1]
|
|
357
|
-
if index < 120: self._navcoordinate = (int(self.xmin)-1, int(self.xmax)+1)
|
|
358
|
-
else: self._navcoordinate = (index-80, index+10)
|
|
352
|
+
v = self.df[['x', 'zero', 'x', self.volume]].sort_values([self.volume], axis=0, ascending=False)
|
|
353
|
+
volseg = v[:self.limit_volume].values
|
|
354
|
+
|
|
355
|
+
self.blitvolume.set_segments(volseg.reshape(volseg.shape[0], 2, 2))
|
|
356
|
+
|
|
357
|
+
if change_lim:
|
|
358
|
+
index = self.df.index[-1]
|
|
359
|
+
if index < self.default_left + self.default_right: self._navcoordinate = (int(self.xmin)-1, int(self.xmax)+1)
|
|
360
|
+
else: self._navcoordinate = (index-self.default_left, index+self.default_right)
|
|
361
|
+
|
|
359
362
|
self._set_navigator(*self._navcoordinate)
|
|
360
363
|
self._lim()
|
|
361
|
-
return
|
|
364
|
+
return
|
|
362
365
|
|
|
363
366
|
def _draw_blit_artist(self):
|
|
364
367
|
renderer = self.canvas.renderer
|
|
@@ -435,8 +438,8 @@ class ClickMixin(SimpleMixin):
|
|
|
435
438
|
self.is_click_chart = False
|
|
436
439
|
|
|
437
440
|
self._restore_region(empty=True)
|
|
438
|
-
self.
|
|
439
|
-
self.
|
|
441
|
+
self._copy_bbox()
|
|
442
|
+
self._restore_region()
|
|
440
443
|
self._blit()
|
|
441
444
|
return
|
|
442
445
|
|
|
@@ -503,58 +506,6 @@ class SliderMixin(ClickMixin):
|
|
|
503
506
|
|
|
504
507
|
|
|
505
508
|
class Chart(SliderMixin, CM, Mixin):
|
|
506
|
-
r"""
|
|
507
|
-
You can see the guidance document:
|
|
508
|
-
Korean: https://white.seolpyo.com/entry/147/
|
|
509
|
-
English: https://white.seolpyo.com/entry/148/
|
|
510
|
-
|
|
511
|
-
Variables:
|
|
512
|
-
unit_price, unit_volume: unit for price and volume. default ('원', '주').
|
|
513
|
-
|
|
514
|
-
figsize: figure size if you use plt.show(). default (12, 6).
|
|
515
|
-
ratio_ax_slider, ratio_ax_legend, ratio_ax_price, ratio_ax_volume: Axes ratio. default (3, 2, 18, 5).
|
|
516
|
-
adjust: figure adjust. default dict(top=0.95, bottom=0.05, left=0.01, right=0.93, wspace=0, hspace=0).
|
|
517
|
-
slider_top: ax_slider is located at the top or bottom. default True.
|
|
518
|
-
color_background: color of background. default '#fafafa'.
|
|
519
|
-
color_grid: color of grid. default '#d0d0d0'.
|
|
520
|
-
|
|
521
|
-
df: stock data.
|
|
522
|
-
date: date column key. default 'date'
|
|
523
|
-
Open, high, low, close: price column key. default ('open', 'high', 'low', 'close')
|
|
524
|
-
volume: volume column key. default 'volume'
|
|
525
|
-
|
|
526
|
-
label_ma: moving average legend label format. default '{}일선'
|
|
527
|
-
list_ma: Decide how many days to draw the moving average line. default (5, 20, 60, 120, 240)
|
|
528
|
-
list_macolor: Color the moving average line. If the number of colors is greater than the moving average line, black is applied. default ('darkred', 'fuchsia', 'olive', 'orange', 'navy', 'darkmagenta', 'limegreen', 'darkcyan',)
|
|
529
|
-
|
|
530
|
-
candle_on_ma: Decide whether to draw candles on the moving average line. default True
|
|
531
|
-
color_sliderline: Color of closing price line in ax_slider. default 'k'
|
|
532
|
-
color_navigatorline: Color of left and right dividing lines in selected area. default '#1e78ff'
|
|
533
|
-
color_navigator: Color of unselected area. default 'k'
|
|
534
|
-
|
|
535
|
-
color_up: The color of the candle. When the closing price is greater than the opening price. default '#fe3032'
|
|
536
|
-
color_down: The color of the candle. When the opening price is greater than the opening price. default '#0095ff'
|
|
537
|
-
color_flat: The color of the candle. WWhen the closing price is the same as the opening price. default 'k'
|
|
538
|
-
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'
|
|
539
|
-
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'
|
|
540
|
-
colors_volume: The color of the volume bar. default '#1f77b4'
|
|
541
|
-
|
|
542
|
-
lineKwargs: Options applied to horizontal and vertical lines drawn along the mouse position. default dict(edgecolor='k', linewidth=1, linestyle='-')
|
|
543
|
-
textboxKwargs: Options that apply to the information text box. dufault dict(boxstyle='round', facecolor='w')
|
|
544
|
-
|
|
545
|
-
fraction: Decide whether to express information as a fraction. default False
|
|
546
|
-
candleformat: Candle information text format. default '{}\n\n종가: {}\n등락률: {}\n대비: {}\n시가: {}({})\n고가: {}({})\n저가: {}({})\n거래량: {}({})'
|
|
547
|
-
volumeformat: Volume information text format. default '{}\n\n거래량 : {}\n거래량증가율: {}'
|
|
548
|
-
digit_price, digit_volume: Number of decimal places expressed in informational text. default (0, 0)
|
|
549
|
-
|
|
550
|
-
min_distance: Minimum number of candles that can be selected with the slider. default 30
|
|
551
|
-
simpler: Decide whether to display candles simply when moving the chart. default False
|
|
552
|
-
limit_volume: Maximum number of volume bars drawn when moving the chart. default 2_000
|
|
553
|
-
"""
|
|
554
|
-
def _generate_data(self, df):
|
|
555
|
-
super()._generate_data(df)
|
|
556
|
-
return self.generate_data(self.df)
|
|
557
|
-
|
|
558
509
|
def _on_draw(self, e):
|
|
559
510
|
super()._on_draw(e)
|
|
560
511
|
return self.on_draw(e)
|
|
@@ -565,7 +516,10 @@ class Chart(SliderMixin, CM, Mixin):
|
|
|
565
516
|
|
|
566
517
|
def _draw_artist(self):
|
|
567
518
|
super()._draw_artist()
|
|
568
|
-
return self.
|
|
519
|
+
return self.draw_artist()
|
|
520
|
+
def _draw_blit_artist(self):
|
|
521
|
+
super()._draw_blit_artist()
|
|
522
|
+
return self.draw_artist()
|
|
569
523
|
|
|
570
524
|
def _blit(self):
|
|
571
525
|
super()._blit()
|
|
@@ -597,3 +551,4 @@ if __name__ == '__main__':
|
|
|
597
551
|
t2 = time() - t
|
|
598
552
|
print(f'{t2=}')
|
|
599
553
|
plt.show()
|
|
554
|
+
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: seolpyo-mplchart
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.1.0.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
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
seolpyo_mplchart/__init__.py,sha256=HITw-PVvJA4AHOO_gOAsbBxNvEbou-JLtivnKA_dn0A,5157
|
|
2
|
+
seolpyo_mplchart/base.py,sha256=vQ4OOBm3nGwjJ4wjDLaD_3LGxYzlP6AWpI6SZrZiwnQ,3600
|
|
3
|
+
seolpyo_mplchart/cursor.py,sha256=_Pzg8WvfOpZYzN5uNLO9LH2wrRB_gZTNGkqmzsjaaPc,17309
|
|
4
|
+
seolpyo_mplchart/draw.py,sha256=yl813StbNuWC0_3QfS9ECvc0Z5buaIudsqR_qW8d3f4,13021
|
|
5
|
+
seolpyo_mplchart/slider.py,sha256=K-vPj2dsSyXZDELrgn6Ry5VqORgUc65gS2SsTFm5TmE,19725
|
|
6
|
+
seolpyo_mplchart/test.py,sha256=cW2hoaVbRtoSXlpmA4i1BKHBjI3-FAqYq__kryxkrC8,1007
|
|
7
|
+
seolpyo_mplchart/utils.py,sha256=-8cq4-WwiqKQxtwu3NPxOVTDDvoWH28tu4OTWr4hPTg,1208
|
|
8
|
+
seolpyo_mplchart/data/apple.txt,sha256=0izAfweu1lLsC0IwVthdVlo9reG8KGbKGTSX5knI5Zc,1380864
|
|
9
|
+
seolpyo_mplchart/data/samsung.txt,sha256=UejaSkbzr4E5K3lkelCT0yJiWUPfmViBEaTyoXyphIs,2476424
|
|
10
|
+
seolpyo_mplchart-0.1.0.1.dist-info/METADATA,sha256=goxKxwzreRSlKSiPffJSYfG-9prsp1586ZPuf9pFbnU,2178
|
|
11
|
+
seolpyo_mplchart-0.1.0.1.dist-info/WHEEL,sha256=A3WOREP4zgxI0fKrHUG8DC8013e3dK3n7a6HDbcEIwE,91
|
|
12
|
+
seolpyo_mplchart-0.1.0.1.dist-info/top_level.txt,sha256=KgqFn7rKWize7OjMaTCHxKm9ie6vqnyb5c8fN7y_tSo,17
|
|
13
|
+
seolpyo_mplchart-0.1.0.1.dist-info/RECORD,,
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
seolpyo_mplchart/__init__.py,sha256=bsfjo70OG4go5ZNaFziLpXPMZ9wWSGQdx1ujuaGBzCQ,1289
|
|
2
|
-
seolpyo_mplchart/base.py,sha256=vQ4OOBm3nGwjJ4wjDLaD_3LGxYzlP6AWpI6SZrZiwnQ,3600
|
|
3
|
-
seolpyo_mplchart/cursor.py,sha256=JI4Fg935unIrisr_n5oIA7gxqVuXaX5-US7n34CADyg,17377
|
|
4
|
-
seolpyo_mplchart/draw.py,sha256=ai8dpiz1ot4L9fphA3SRKhWMhNcWzvHUvQnZYo5p94I,12963
|
|
5
|
-
seolpyo_mplchart/slider.py,sha256=dXfjTfwEYCcnVDHwn59u8-tiz8aB3HssiOLfuctp4tk,23159
|
|
6
|
-
seolpyo_mplchart/test.py,sha256=cW2hoaVbRtoSXlpmA4i1BKHBjI3-FAqYq__kryxkrC8,1007
|
|
7
|
-
seolpyo_mplchart/utils.py,sha256=-8cq4-WwiqKQxtwu3NPxOVTDDvoWH28tu4OTWr4hPTg,1208
|
|
8
|
-
seolpyo_mplchart/data/apple.txt,sha256=0izAfweu1lLsC0IwVthdVlo9reG8KGbKGTSX5knI5Zc,1380864
|
|
9
|
-
seolpyo_mplchart/data/samsung.txt,sha256=UejaSkbzr4E5K3lkelCT0yJiWUPfmViBEaTyoXyphIs,2476424
|
|
10
|
-
seolpyo_mplchart-0.0.61.dist-info/METADATA,sha256=ahfx_SZU85fOOUODMLSjQQfnKwDBq2TQZrap-eiLl4U,2177
|
|
11
|
-
seolpyo_mplchart-0.0.61.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
|
12
|
-
seolpyo_mplchart-0.0.61.dist-info/top_level.txt,sha256=KgqFn7rKWize7OjMaTCHxKm9ie6vqnyb5c8fN7y_tSo,17
|
|
13
|
-
seolpyo_mplchart-0.0.61.dist-info/RECORD,,
|
|
File without changes
|