seolpyo-mplchart 0.0.35__py3-none-any.whl → 0.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.
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 +45 -45
- seolpyo_mplchart/draw.py +74 -72
- seolpyo_mplchart/slider.py +80 -127
- {seolpyo_mplchart-0.0.35.dist-info → seolpyo_mplchart-0.1.0.dist-info}/METADATA +4 -4
- seolpyo_mplchart-0.1.0.dist-info/RECORD +13 -0
- {seolpyo_mplchart-0.0.35.dist-info → seolpyo_mplchart-0.1.0.dist-info}/WHEEL +1 -1
- seolpyo_mplchart-0.0.35.dist-info/RECORD +0 -13
- {seolpyo_mplchart-0.0.35.dist-info → seolpyo_mplchart-0.1.0.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,36 @@ 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
|
+
return
|
|
81
|
+
|
|
82
|
+
def set_text_coordante(self, vmin, vmax, pmin, pmax, volmax):
|
|
83
|
+
# 주가, 거래량 텍스트 x 위치
|
|
84
|
+
x_distance = (vmax - vmin) / 30
|
|
85
|
+
self.v0, self.v1 = (vmin + x_distance, vmax - x_distance)
|
|
86
|
+
self.text_price.set_x(self.v0)
|
|
87
|
+
self.text_volume.set_x(self.v0)
|
|
88
|
+
|
|
89
|
+
self.vmin, self.vmax = (vmin, vmax)
|
|
90
|
+
self.vmiddle = vmax - int((vmax - vmin) / 2)
|
|
91
|
+
|
|
92
|
+
psub = pmax - pmin
|
|
93
|
+
self.min_psub = psub / 12
|
|
94
|
+
|
|
95
|
+
# 주가 날짜 텍스트 y 위치
|
|
96
|
+
y = (psub) / 20 + pmin
|
|
97
|
+
self.text_date_price.set_y(y)
|
|
98
|
+
# 주가 정보 y 위치
|
|
99
|
+
y = pmax - (psub) / 20
|
|
100
|
+
self.text_price_info.set_y(y)
|
|
101
|
+
|
|
102
|
+
# 거래량 날짜 텍스트 y 위치
|
|
103
|
+
y = volmax * 0.85
|
|
104
|
+
self.text_date_volume.set_y(y)
|
|
105
|
+
# 거래량 정보 y 위치
|
|
106
|
+
self.text_volume_info.set_y(y)
|
|
107
|
+
|
|
80
108
|
return
|
|
81
109
|
|
|
82
110
|
|
|
@@ -95,8 +123,8 @@ class LineMixin(DataMixin):
|
|
|
95
123
|
self.canvas.blit()
|
|
96
124
|
return
|
|
97
125
|
|
|
98
|
-
def
|
|
99
|
-
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)
|
|
100
128
|
|
|
101
129
|
self.vmin, self.vmax = (self.xmin, self.xmax)
|
|
102
130
|
return
|
|
@@ -172,8 +200,12 @@ class LineMixin(DataMixin):
|
|
|
172
200
|
if self.in_index:
|
|
173
201
|
intx = self.intx
|
|
174
202
|
|
|
175
|
-
|
|
176
|
-
|
|
203
|
+
h = self.df[self.high][intx]
|
|
204
|
+
l = self.df[self.low][intx]
|
|
205
|
+
sub = (h - l) / 2
|
|
206
|
+
if sub < self.min_psub: sub = self.min_psub
|
|
207
|
+
high = h + sub
|
|
208
|
+
low = l - sub
|
|
177
209
|
if high < y or y < low: self._in_candle = False
|
|
178
210
|
else:
|
|
179
211
|
self._in_candle = True
|
|
@@ -214,8 +246,8 @@ class InfoMixin(LineMixin):
|
|
|
214
246
|
volumeformat = '{}\n\n거래량 : {}\n거래량증가율: {}'
|
|
215
247
|
digit_price, digit_volume = (0, 0)
|
|
216
248
|
|
|
217
|
-
def
|
|
218
|
-
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)
|
|
219
251
|
|
|
220
252
|
# 슬라이더 날짜 텍스트 y 위치
|
|
221
253
|
y = self._slider_ymax - (self._slider_ymax - self._slider_ymin) / 6
|
|
@@ -227,31 +259,6 @@ class InfoMixin(LineMixin):
|
|
|
227
259
|
|
|
228
260
|
return
|
|
229
261
|
|
|
230
|
-
def set_text_coordante(self, vmin, vmax, pmin, pmax, volmax):
|
|
231
|
-
# 주가, 거래량 텍스트 x 위치
|
|
232
|
-
x_distance = (vmax - vmin) / 30
|
|
233
|
-
self.v0, self.v1 = (vmin + x_distance, vmax - x_distance)
|
|
234
|
-
self.text_price.set_x(self.v0)
|
|
235
|
-
self.text_volume.set_x(self.v0)
|
|
236
|
-
|
|
237
|
-
self.vmin, self.vmax = (vmin, vmax)
|
|
238
|
-
self.vmiddle = vmax - int((vmax - vmin) / 2)
|
|
239
|
-
|
|
240
|
-
# 주가 날짜 텍스트 y 위치
|
|
241
|
-
y = (pmax - pmin) / 20 + pmin
|
|
242
|
-
self.text_date_price.set_y(y)
|
|
243
|
-
# 주가 정보 y 위치
|
|
244
|
-
y = pmax - (pmax - pmin) / 20
|
|
245
|
-
self.text_price_info.set_y(y)
|
|
246
|
-
|
|
247
|
-
# 거래량 날짜 텍스트 y 위치
|
|
248
|
-
y = volmax * 0.85
|
|
249
|
-
self.text_date_volume.set_y(y)
|
|
250
|
-
# 거래량 정보 y 위치
|
|
251
|
-
self.text_volume_info.set_y(y)
|
|
252
|
-
|
|
253
|
-
return
|
|
254
|
-
|
|
255
262
|
def _slider_move_action(self, e):
|
|
256
263
|
super()._slider_move_action(e)
|
|
257
264
|
|
|
@@ -388,10 +395,6 @@ class CursorMixin(InfoMixin):
|
|
|
388
395
|
|
|
389
396
|
|
|
390
397
|
class Chart(CursorMixin, CM, Mixin):
|
|
391
|
-
def _generate_data(self, df):
|
|
392
|
-
super()._generate_data(df)
|
|
393
|
-
return self.generate_data(df)
|
|
394
|
-
|
|
395
398
|
def _on_draw(self, e):
|
|
396
399
|
super()._on_draw(e)
|
|
397
400
|
return self.on_draw(e)
|
|
@@ -400,10 +403,6 @@ class Chart(CursorMixin, CM, Mixin):
|
|
|
400
403
|
self.on_pick(e)
|
|
401
404
|
return super()._on_pick(e)
|
|
402
405
|
|
|
403
|
-
def _draw_artist(self):
|
|
404
|
-
super()._draw_artist()
|
|
405
|
-
return self.create_background()
|
|
406
|
-
|
|
407
406
|
def _blit(self):
|
|
408
407
|
super()._blit()
|
|
409
408
|
return self.on_blit()
|
|
@@ -424,7 +423,8 @@ if __name__ == '__main__':
|
|
|
424
423
|
file = Path(__file__).parent / 'data/apple.txt'
|
|
425
424
|
with open(file, 'r', encoding='utf-8') as txt:
|
|
426
425
|
data = json.load(txt)
|
|
427
|
-
|
|
426
|
+
n = 2600
|
|
427
|
+
data = data[n:n+100]
|
|
428
428
|
df = pd.DataFrame(data)
|
|
429
429
|
|
|
430
430
|
t = time()
|
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,15 +44,21 @@ 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}')
|
|
55
51
|
if i != 'date':
|
|
56
52
|
dtype = df[k].dtype
|
|
57
|
-
if not isinstance(dtype, np.dtypes.Float64DType): raise TypeError(f'Data column type must be "float64".(excluding "date" column)\ndf[{k!r}].dtype={dtype!r}')
|
|
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
|
|
@@ -66,30 +68,29 @@ class DataMixin(Base):
|
|
|
66
68
|
df['vleft'] = df['x'] - volumewidth_half
|
|
67
69
|
df['vright'] = df['x'] + volumewidth_half
|
|
68
70
|
|
|
69
|
-
df['top'] = np.where(df[
|
|
70
|
-
df['top'] = np.where(df[
|
|
71
|
-
df['bottom'] = np.where(df[
|
|
72
|
-
df['bottom'] = np.where(df[
|
|
71
|
+
df['top'] = np.where(df[self.Open] <= df[self.close], df[self.close], df[self.Open])
|
|
72
|
+
df['top'] = np.where(df[self.close] < df[self.Open], df[self.Open], df[self.close])
|
|
73
|
+
df['bottom'] = np.where(df[self.Open] <= df[self.close], df[self.Open], df[self.close])
|
|
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
|
-
df.loc[df[
|
|
80
|
+
df.loc[df[self.close] < df[self.Open], ['facecolor', 'edgecolor']] = (self.color_down, self.color_down)
|
|
79
81
|
if self.color_up != self.color_flat:
|
|
80
82
|
# 보합
|
|
81
|
-
df.loc[df[
|
|
83
|
+
df.loc[df[self.close] == df[self.Open], ['facecolor', 'edgecolor']] = (self.color_flat, self.color_flat)
|
|
82
84
|
if self.color_up != self.color_up_down:
|
|
83
85
|
# 양봉(비우기)
|
|
84
|
-
df.loc[(df['facecolor'] == self.color_up) & (df[
|
|
86
|
+
df.loc[(df['facecolor'] == self.color_up) & (df[self.close] < df[self.close].shift(1)), 'facecolor'] = self.color_up_down
|
|
85
87
|
if self.color_down != self.color_down_up:
|
|
86
88
|
# 음봉(비우기)
|
|
87
|
-
df.loc[(df['facecolor'] == self.color_down) & (df[
|
|
89
|
+
df.loc[(df['facecolor'] == self.color_down) & (df[self.close].shift(1) < df[self.close]), ['facecolor']] = self.color_down_up
|
|
88
90
|
|
|
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
|
|
@@ -96,9 +100,6 @@ class NavgatorMixin(Mixin):
|
|
|
96
100
|
|
|
97
101
|
if self._navcoordinate[0] == self._navcoordinate[1]:
|
|
98
102
|
self._navcoordinate = (self._navcoordinate[0], self._navcoordinate[1]+self.min_distance)
|
|
99
|
-
|
|
100
|
-
self.background = None
|
|
101
|
-
self._draw()
|
|
102
103
|
return
|
|
103
104
|
|
|
104
105
|
|
|
@@ -108,19 +109,27 @@ class BackgroundMixin(NavgatorMixin):
|
|
|
108
109
|
self._restore_region()
|
|
109
110
|
return
|
|
110
111
|
|
|
111
|
-
def _restore_region(self, with_nav=True):
|
|
112
|
+
def _restore_region(self, with_nav=True, empty=False, empty_with_nav=False):
|
|
112
113
|
if not self.background: self._create_background()
|
|
113
114
|
|
|
114
|
-
if
|
|
115
|
+
if empty: self.canvas.restore_region(self.background_empty)
|
|
116
|
+
elif empty_with_nav: self.canvas.restore_region(self.background_empty_with_nav)
|
|
117
|
+
elif with_nav: self.canvas.restore_region(self.background_with_nav)
|
|
115
118
|
else: self.canvas.renderer.restore_region(self.background)
|
|
116
119
|
return
|
|
117
120
|
|
|
118
121
|
def _copy_bbox(self):
|
|
119
|
-
self.
|
|
120
|
-
self.ax_slider.yaxis.draw(self.canvas.renderer)
|
|
121
|
-
self.slidercollection.draw(self.canvas.renderer)
|
|
122
|
+
renderer = self.canvas.renderer
|
|
122
123
|
|
|
123
|
-
|
|
124
|
+
self.background_empty = renderer.copy_from_bbox(self.fig.bbox)
|
|
125
|
+
|
|
126
|
+
self.ax_slider.xaxis.draw(renderer)
|
|
127
|
+
self.ax_slider.yaxis.draw(renderer)
|
|
128
|
+
self.slidercollection.draw(renderer)
|
|
129
|
+
self.background_empty_with_nav = self.canvas.renderer.copy_from_bbox(self.fig.bbox)
|
|
130
|
+
|
|
131
|
+
self._draw_artist()
|
|
132
|
+
self.background = self.canvas.renderer.copy_from_bbox(self.fig.bbox)
|
|
124
133
|
|
|
125
134
|
self.navigator.draw(self.canvas.renderer)
|
|
126
135
|
self.background_with_nav = self.canvas.renderer.copy_from_bbox(self.fig.bbox)
|
|
@@ -147,26 +156,24 @@ class BackgroundMixin(NavgatorMixin):
|
|
|
147
156
|
|
|
148
157
|
|
|
149
158
|
class DrawMixin(BackgroundMixin):
|
|
150
|
-
def
|
|
151
|
-
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)
|
|
152
161
|
|
|
153
162
|
# 네비게이터 높이 설정
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
self._ymiddle = ysub / 2
|
|
163
|
+
ysub = self._slider_ymax - self._slider_ymin
|
|
164
|
+
self._ymiddle = self._slider_ymax - ysub / 2
|
|
157
165
|
self.navigator.set_linewidth((ysub, 5))
|
|
158
166
|
return
|
|
159
167
|
|
|
160
168
|
def _on_release(self, e: MouseEvent):
|
|
161
|
-
|
|
162
|
-
self.is_click, self.is_move = (False, False)
|
|
163
|
-
|
|
164
|
-
if self._navcoordinate[0] == self._navcoordinate[1]:
|
|
165
|
-
self._navcoordinate = (self._navcoordinate[0], self._navcoordinate[1]+self.min_distance)
|
|
169
|
+
super()._on_release(e)
|
|
166
170
|
self._set_navigator(*self._navcoordinate)
|
|
167
171
|
|
|
168
|
-
self.
|
|
169
|
-
self.
|
|
172
|
+
self._restore_region(empty=True)
|
|
173
|
+
self._creating_background = False
|
|
174
|
+
self._create_background()
|
|
175
|
+
self._restore_region()
|
|
176
|
+
self._blit()
|
|
170
177
|
return
|
|
171
178
|
|
|
172
179
|
def _on_move(self, e: MouseEvent):
|
|
@@ -238,27 +245,6 @@ class DrawMixin(BackgroundMixin):
|
|
|
238
245
|
|
|
239
246
|
|
|
240
247
|
class LimMixin(DrawMixin):
|
|
241
|
-
def _restore_region(self, with_nav=True, empty=False):
|
|
242
|
-
if not self.background: self._create_background()
|
|
243
|
-
|
|
244
|
-
if empty: self.canvas.restore_region(self.background_empty)
|
|
245
|
-
elif with_nav: self.canvas.restore_region(self.background_with_nav)
|
|
246
|
-
else: self.canvas.renderer.restore_region(self.background)
|
|
247
|
-
return
|
|
248
|
-
|
|
249
|
-
def _copy_bbox(self):
|
|
250
|
-
self.ax_slider.xaxis.draw(self.canvas.renderer)
|
|
251
|
-
self.ax_slider.yaxis.draw(self.canvas.renderer)
|
|
252
|
-
self.slidercollection.draw(self.canvas.renderer)
|
|
253
|
-
self.background_empty = self.canvas.renderer.copy_from_bbox(self.fig.bbox)
|
|
254
|
-
|
|
255
|
-
self._draw_artist()
|
|
256
|
-
self.background = self.canvas.renderer.copy_from_bbox(self.fig.bbox)
|
|
257
|
-
|
|
258
|
-
self.navigator.draw(self.canvas.renderer)
|
|
259
|
-
self.background_with_nav = self.canvas.renderer.copy_from_bbox(self.fig.bbox)
|
|
260
|
-
return
|
|
261
|
-
|
|
262
248
|
def _on_release(self, e: MouseEvent):
|
|
263
249
|
if e.inaxes is not self.ax_slider: return
|
|
264
250
|
self.is_click, self.is_move = (False, False)
|
|
@@ -268,12 +254,14 @@ class LimMixin(DrawMixin):
|
|
|
268
254
|
self._set_navigator(*self._navcoordinate)
|
|
269
255
|
self._lim()
|
|
270
256
|
|
|
271
|
-
self.
|
|
272
|
-
self.
|
|
257
|
+
self._restore_region(empty=True)
|
|
258
|
+
self._copy_bbox()
|
|
259
|
+
self._restore_region()
|
|
260
|
+
self._blit()
|
|
273
261
|
return
|
|
274
262
|
|
|
275
263
|
def _on_move(self, e):
|
|
276
|
-
self._restore_region(with_nav=(not self.is_click),
|
|
264
|
+
self._restore_region(with_nav=(not self.is_click), empty_with_nav=self.is_click)
|
|
277
265
|
|
|
278
266
|
self._on_move_action(e)
|
|
279
267
|
|
|
@@ -329,7 +317,8 @@ class LimMixin(DrawMixin):
|
|
|
329
317
|
|
|
330
318
|
class SimpleMixin(LimMixin):
|
|
331
319
|
simpler = False
|
|
332
|
-
limit_volume =
|
|
320
|
+
limit_volume = 1_500
|
|
321
|
+
default_left, default_right = (180, 10)
|
|
333
322
|
|
|
334
323
|
def __init__(self, *args, **kwargs):
|
|
335
324
|
super().__init__(*args, **kwargs)
|
|
@@ -345,35 +334,34 @@ class SimpleMixin(LimMixin):
|
|
|
345
334
|
self.ax_volume.add_collection(self.blitvolume)
|
|
346
335
|
return
|
|
347
336
|
|
|
348
|
-
def
|
|
349
|
-
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)
|
|
350
339
|
|
|
351
|
-
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)
|
|
352
342
|
self.blitcandle.set_segments(seg)
|
|
353
343
|
self.blitcandle.set_edgecolor(self.df['edgecolor'])
|
|
354
|
-
self.priceline.set_verts([self.df[['x', self.close]].apply(tuple, axis=1).tolist()])
|
|
355
344
|
|
|
356
|
-
|
|
345
|
+
pseg = self.df[['x', self.close]].values
|
|
346
|
+
self.priceline.set_verts(pseg.reshape(1, *pseg.shape))
|
|
347
|
+
|
|
357
348
|
l = self.df.__len__()
|
|
358
349
|
if l < self.limit_volume:
|
|
359
|
-
volseg = self.df.loc[:, ['x', self.volume]].
|
|
350
|
+
volseg = self.df.loc[:, ['x', 'zero', 'x', self.volume]].values
|
|
360
351
|
else:
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
index = self.df.index[-1]
|
|
372
|
-
if index < 120: self._navcoordinate = (int(self.xmin)-1, int(self.xmax)+1)
|
|
373
|
-
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
|
+
|
|
374
362
|
self._set_navigator(*self._navcoordinate)
|
|
375
363
|
self._lim()
|
|
376
|
-
return
|
|
364
|
+
return
|
|
377
365
|
|
|
378
366
|
def _draw_blit_artist(self):
|
|
379
367
|
renderer = self.canvas.renderer
|
|
@@ -381,12 +369,20 @@ class SimpleMixin(LimMixin):
|
|
|
381
369
|
self.ax_price.xaxis.draw(renderer)
|
|
382
370
|
self.ax_price.yaxis.draw(renderer)
|
|
383
371
|
|
|
384
|
-
|
|
372
|
+
left, right = self._navcoordinate
|
|
373
|
+
Range = right - left
|
|
374
|
+
if self.simpler:
|
|
375
|
+
if Range < 1_000: self.blitcandle.draw(renderer)
|
|
376
|
+
else: self.priceline.draw(renderer)
|
|
385
377
|
elif self.candle_on_ma:
|
|
386
378
|
self.macollection.draw(renderer)
|
|
387
|
-
self.
|
|
379
|
+
if 2_500 < Range: self.priceline.draw(renderer)
|
|
380
|
+
elif 800 < Range or 9_999 < self.xmax: self.blitcandle.draw(renderer)
|
|
381
|
+
else: self.candlecollection.draw(renderer)
|
|
388
382
|
else:
|
|
389
|
-
self.
|
|
383
|
+
if 2_500 < Range: self.priceline.draw(renderer)
|
|
384
|
+
elif 800 < Range or 9_999 < self.xmax: self.blitcandle.draw(renderer)
|
|
385
|
+
else: self.candlecollection.draw(renderer)
|
|
390
386
|
self.macollection.draw(renderer)
|
|
391
387
|
|
|
392
388
|
self.ax_volume.xaxis.draw(renderer)
|
|
@@ -436,12 +432,16 @@ class ClickMixin(SimpleMixin):
|
|
|
436
432
|
if not self.is_click: return
|
|
437
433
|
elif e.inaxes is self.ax_slider: return super()._on_release(e)
|
|
438
434
|
elif not self.in_price and not self.in_volume and not self.is_click_chart: return
|
|
435
|
+
# 차트 click release action
|
|
439
436
|
self.canvas.set_cursor(cursors.POINTER)
|
|
440
437
|
self.is_click, self.is_move = (False, False)
|
|
441
438
|
self.is_click_chart = False
|
|
442
439
|
|
|
443
|
-
self.
|
|
444
|
-
|
|
440
|
+
self._restore_region(empty=True)
|
|
441
|
+
self._copy_bbox()
|
|
442
|
+
self._restore_region()
|
|
443
|
+
self._blit()
|
|
444
|
+
return
|
|
445
445
|
|
|
446
446
|
def _on_chart_click(self, e: MouseEvent):
|
|
447
447
|
self.is_click = True
|
|
@@ -469,7 +469,7 @@ class ClickMixin(SimpleMixin):
|
|
|
469
469
|
return
|
|
470
470
|
|
|
471
471
|
def _on_move(self, e):
|
|
472
|
-
self._restore_region(with_nav=(not self.is_click),
|
|
472
|
+
self._restore_region(with_nav=(not self.is_click), empty_with_nav=self.is_click)
|
|
473
473
|
|
|
474
474
|
self._on_move_action(e)
|
|
475
475
|
|
|
@@ -506,58 +506,6 @@ class SliderMixin(ClickMixin):
|
|
|
506
506
|
|
|
507
507
|
|
|
508
508
|
class Chart(SliderMixin, CM, Mixin):
|
|
509
|
-
r"""
|
|
510
|
-
You can see the guidance document:
|
|
511
|
-
Korean: https://white.seolpyo.com/entry/147/
|
|
512
|
-
English: https://white.seolpyo.com/entry/148/
|
|
513
|
-
|
|
514
|
-
Variables:
|
|
515
|
-
unit_price, unit_volume: unit for price and volume. default ('원', '주').
|
|
516
|
-
|
|
517
|
-
figsize: figure size if you use plt.show(). default (12, 6).
|
|
518
|
-
ratio_ax_slider, ratio_ax_legend, ratio_ax_price, ratio_ax_volume: Axes ratio. default (3, 2, 18, 5).
|
|
519
|
-
adjust: figure adjust. default dict(top=0.95, bottom=0.05, left=0.01, right=0.93, wspace=0, hspace=0).
|
|
520
|
-
slider_top: ax_slider is located at the top or bottom. default True.
|
|
521
|
-
color_background: color of background. default '#fafafa'.
|
|
522
|
-
color_grid: color of grid. default '#d0d0d0'.
|
|
523
|
-
|
|
524
|
-
df: stock data.
|
|
525
|
-
date: date column key. default 'date'
|
|
526
|
-
Open, high, low, close: price column key. default ('open', 'high', 'low', 'close')
|
|
527
|
-
volume: volume column key. default 'volume'
|
|
528
|
-
|
|
529
|
-
label_ma: moving average legend label format. default '{}일선'
|
|
530
|
-
list_ma: Decide how many days to draw the moving average line. default (5, 20, 60, 120, 240)
|
|
531
|
-
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',)
|
|
532
|
-
|
|
533
|
-
candle_on_ma: Decide whether to draw candles on the moving average line. default True
|
|
534
|
-
color_sliderline: Color of closing price line in ax_slider. default 'k'
|
|
535
|
-
color_navigatorline: Color of left and right dividing lines in selected area. default '#1e78ff'
|
|
536
|
-
color_navigator: Color of unselected area. default 'k'
|
|
537
|
-
|
|
538
|
-
color_up: The color of the candle. When the closing price is greater than the opening price. default '#fe3032'
|
|
539
|
-
color_down: The color of the candle. When the opening price is greater than the opening price. default '#0095ff'
|
|
540
|
-
color_flat: The color of the candle. WWhen the closing price is the same as the opening price. default 'k'
|
|
541
|
-
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'
|
|
542
|
-
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'
|
|
543
|
-
colors_volume: The color of the volume bar. default '#1f77b4'
|
|
544
|
-
|
|
545
|
-
lineKwargs: Options applied to horizontal and vertical lines drawn along the mouse position. default dict(edgecolor='k', linewidth=1, linestyle='-')
|
|
546
|
-
textboxKwargs: Options that apply to the information text box. dufault dict(boxstyle='round', facecolor='w')
|
|
547
|
-
|
|
548
|
-
fraction: Decide whether to express information as a fraction. default False
|
|
549
|
-
candleformat: Candle information text format. default '{}\n\n종가: {}\n등락률: {}\n대비: {}\n시가: {}({})\n고가: {}({})\n저가: {}({})\n거래량: {}({})'
|
|
550
|
-
volumeformat: Volume information text format. default '{}\n\n거래량 : {}\n거래량증가율: {}'
|
|
551
|
-
digit_price, digit_volume: Number of decimal places expressed in informational text. default (0, 0)
|
|
552
|
-
|
|
553
|
-
min_distance: Minimum number of candles that can be selected with the slider. default 30
|
|
554
|
-
simpler: Decide whether to display candles simply when moving the chart. default False
|
|
555
|
-
limit_volume: Maximum number of volume bars drawn when moving the chart. default 2_000
|
|
556
|
-
"""
|
|
557
|
-
def _generate_data(self, df):
|
|
558
|
-
super()._generate_data(df)
|
|
559
|
-
return self.generate_data(self.df)
|
|
560
|
-
|
|
561
509
|
def _on_draw(self, e):
|
|
562
510
|
super()._on_draw(e)
|
|
563
511
|
return self.on_draw(e)
|
|
@@ -568,7 +516,10 @@ class Chart(SliderMixin, CM, Mixin):
|
|
|
568
516
|
|
|
569
517
|
def _draw_artist(self):
|
|
570
518
|
super()._draw_artist()
|
|
571
|
-
return self.
|
|
519
|
+
return self.draw_artist()
|
|
520
|
+
def _draw_blit_artist(self):
|
|
521
|
+
super()._draw_blit_artist()
|
|
522
|
+
return self.draw_artist()
|
|
572
523
|
|
|
573
524
|
def _blit(self):
|
|
574
525
|
super()._blit()
|
|
@@ -594,8 +545,10 @@ if __name__ == '__main__':
|
|
|
594
545
|
df = pd.DataFrame(data)
|
|
595
546
|
|
|
596
547
|
t = time()
|
|
548
|
+
# c = SimpleMixin()
|
|
597
549
|
c = SliderMixin()
|
|
598
550
|
c.set_data(df)
|
|
599
551
|
t2 = time() - t
|
|
600
552
|
print(f'{t2=}')
|
|
601
553
|
plt.show()
|
|
554
|
+
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: seolpyo-mplchart
|
|
3
|
-
Version: 0.0
|
|
4
|
-
Summary: Fast candlestick chart using Python.
|
|
3
|
+
Version: 0.1.0
|
|
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
|
|
7
7
|
Project-URL: Homepage, https://white.seolpyo.com/
|
|
@@ -22,8 +22,8 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
22
22
|
Classifier: Programming Language :: Python :: 3.12
|
|
23
23
|
Requires-Python: >=3.11
|
|
24
24
|
Description-Content-Type: text/markdown
|
|
25
|
-
Requires-Dist: matplotlib>=3.
|
|
26
|
-
Requires-Dist: pandas>=2.
|
|
25
|
+
Requires-Dist: matplotlib>=3.6.0
|
|
26
|
+
Requires-Dist: pandas>=2.0.0
|
|
27
27
|
|
|
28
28
|
# Donation
|
|
29
29
|
Bitcoin: 1MKCHW8smDZv5DFMiVkA5G3DeXcMn871ZX
|
|
@@ -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.dist-info/METADATA,sha256=W3N4J-vLlzw7Cx_t1WiGBDx2g0eiXdf5VlPoe0RVLDw,2176
|
|
11
|
+
seolpyo_mplchart-0.1.0.dist-info/WHEEL,sha256=A3WOREP4zgxI0fKrHUG8DC8013e3dK3n7a6HDbcEIwE,91
|
|
12
|
+
seolpyo_mplchart-0.1.0.dist-info/top_level.txt,sha256=KgqFn7rKWize7OjMaTCHxKm9ie6vqnyb5c8fN7y_tSo,17
|
|
13
|
+
seolpyo_mplchart-0.1.0.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=-IUk2JcjvIM-TjHyeJx3lJFVI_Sx0xccDcO_RL-Co4c,17185
|
|
4
|
-
seolpyo_mplchart/draw.py,sha256=GSvZBUIUyrlztdcxYD6_Ef1d5ZBdxi9uyLvAYb0WWVQ,12786
|
|
5
|
-
seolpyo_mplchart/slider.py,sha256=0ZDJIs5rR1spoc3my5chuKXkKOJsXUjbDk6utDBABcU,23060
|
|
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.35.dist-info/METADATA,sha256=JiUChPSvcAPFK7pOmXy7GBJkBrfzJgisP2TevBdTZ1I,2098
|
|
11
|
-
seolpyo_mplchart-0.0.35.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
|
12
|
-
seolpyo_mplchart-0.0.35.dist-info/top_level.txt,sha256=KgqFn7rKWize7OjMaTCHxKm9ie6vqnyb5c8fN7y_tSo,17
|
|
13
|
-
seolpyo_mplchart-0.0.35.dist-info/RECORD,,
|
|
File without changes
|