seolpyo-mplchart 1.2.1__tar.gz → 1.3.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of seolpyo-mplchart might be problematic. Click here for more details.
- {seolpyo_mplchart-1.2.1/seolpyo_mplchart.egg-info → seolpyo_mplchart-1.3.1}/PKG-INFO +10 -4
- {seolpyo_mplchart-1.2.1 → seolpyo_mplchart-1.3.1}/README.md +9 -3
- {seolpyo_mplchart-1.2.1 → seolpyo_mplchart-1.3.1}/pyproject.toml +1 -1
- {seolpyo_mplchart-1.2.1 → seolpyo_mplchart-1.3.1}/seolpyo_mplchart/__init__.py +5 -8
- {seolpyo_mplchart-1.2.1 → seolpyo_mplchart-1.3.1}/seolpyo_mplchart/_base.py +3 -0
- {seolpyo_mplchart-1.2.1 → seolpyo_mplchart-1.3.1}/seolpyo_mplchart/_cursor.py +25 -17
- {seolpyo_mplchart-1.2.1 → seolpyo_mplchart-1.3.1}/seolpyo_mplchart/_draw.py +68 -60
- {seolpyo_mplchart-1.2.1 → seolpyo_mplchart-1.3.1}/seolpyo_mplchart/_slider.py +17 -15
- {seolpyo_mplchart-1.2.1 → seolpyo_mplchart-1.3.1/seolpyo_mplchart.egg-info}/PKG-INFO +10 -4
- {seolpyo_mplchart-1.2.1 → seolpyo_mplchart-1.3.1}/MANIFEST.in +0 -0
- {seolpyo_mplchart-1.2.1 → seolpyo_mplchart-1.3.1}/seolpyo_mplchart/test.py +0 -0
- {seolpyo_mplchart-1.2.1 → seolpyo_mplchart-1.3.1}/seolpyo_mplchart/utils.py +0 -0
- {seolpyo_mplchart-1.2.1 → seolpyo_mplchart-1.3.1}/seolpyo_mplchart.egg-info/SOURCES.txt +0 -0
- {seolpyo_mplchart-1.2.1 → seolpyo_mplchart-1.3.1}/seolpyo_mplchart.egg-info/dependency_links.txt +0 -0
- {seolpyo_mplchart-1.2.1 → seolpyo_mplchart-1.3.1}/seolpyo_mplchart.egg-info/requires.txt +0 -0
- {seolpyo_mplchart-1.2.1 → seolpyo_mplchart-1.3.1}/seolpyo_mplchart.egg-info/top_level.txt +0 -0
- {seolpyo_mplchart-1.2.1 → seolpyo_mplchart-1.3.1}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: seolpyo-mplchart
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.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
|
|
@@ -32,18 +32,24 @@ Ethereum: 0x1c5fb8a5e0b1153cd4116c91736bd16fabf83520
|
|
|
32
32
|
|
|
33
33
|
|
|
34
34
|
# Document
|
|
35
|
-
[English](https://white.seolpyo.com/entry/148/)
|
|
35
|
+
[English Document](https://white.seolpyo.com/entry/148/)
|
|
36
36
|
|
|
37
|
-
[한글](https://white.seolpyo.com/entry/147/)
|
|
37
|
+
[한글 설명서](https://white.seolpyo.com/entry/147/)
|
|
38
38
|
|
|
39
39
|
|
|
40
|
-
# Sample
|
|
40
|
+
# Sample Image
|
|
41
|
+
## sample
|
|
41
42
|

|
|
42
43
|
|
|
44
|
+
## tkinter sample
|
|
43
45
|

|
|
44
46
|
|
|
47
|
+
## English format sample
|
|
45
48
|

|
|
46
49
|
|
|
50
|
+
## Korean format sample
|
|
47
51
|

|
|
48
52
|
|
|
53
|
+
|
|
54
|
+
# 40,000 data sample
|
|
49
55
|

|
|
@@ -5,18 +5,24 @@ Ethereum: 0x1c5fb8a5e0b1153cd4116c91736bd16fabf83520
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
# Document
|
|
8
|
-
[English](https://white.seolpyo.com/entry/148/)
|
|
8
|
+
[English Document](https://white.seolpyo.com/entry/148/)
|
|
9
9
|
|
|
10
|
-
[한글](https://white.seolpyo.com/entry/147/)
|
|
10
|
+
[한글 설명서](https://white.seolpyo.com/entry/147/)
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
# Sample
|
|
13
|
+
# Sample Image
|
|
14
|
+
## sample
|
|
14
15
|

|
|
15
16
|
|
|
17
|
+
## tkinter sample
|
|
16
18
|

|
|
17
19
|
|
|
20
|
+
## English format sample
|
|
18
21
|

|
|
19
22
|
|
|
23
|
+
## Korean format sample
|
|
20
24
|

|
|
21
25
|
|
|
26
|
+
|
|
27
|
+
# 40,000 data sample
|
|
22
28
|

|
|
@@ -281,7 +281,7 @@ class SliderChart(_BaseSliderChart):
|
|
|
281
281
|
pass
|
|
282
282
|
|
|
283
283
|
|
|
284
|
-
def set_theme(chart:
|
|
284
|
+
def set_theme(chart: SliderChart|CursorChart|OnlyChart, theme: Literal['light', 'dark']='dark'):
|
|
285
285
|
initialized = hasattr(chart, 'ax_price')
|
|
286
286
|
|
|
287
287
|
if theme == 'dark':
|
|
@@ -312,10 +312,8 @@ def set_theme(chart: OnlyChart|CursorChart|SliderChart, theme: Literal['light',
|
|
|
312
312
|
if hasattr(chart, 'navigator'): chart.navigator.set_edgecolor([chart.color_navigator_cover, chart.color_navigator_line])
|
|
313
313
|
|
|
314
314
|
if hasattr(chart, 'df'):
|
|
315
|
-
chart.
|
|
316
|
-
chart.
|
|
317
|
-
chart.figure.canvas.draw()
|
|
318
|
-
|
|
315
|
+
chart.set_data(chart.df, sort_df=False, calc_ma=False, set_candlecolor=True, set_volumecolor=True, calc_info=False, change_lim=False)
|
|
316
|
+
chart.draw_canvas()
|
|
319
317
|
elif theme == 'light':
|
|
320
318
|
chart.color_background = '#fafafa'
|
|
321
319
|
chart.color_tick, chart.color_tick_label = ('k', 'k')
|
|
@@ -344,9 +342,8 @@ def set_theme(chart: OnlyChart|CursorChart|SliderChart, theme: Literal['light',
|
|
|
344
342
|
if hasattr(chart, 'navigator'): chart.navigator.set_edgecolor([chart.color_navigator_cover, chart.color_navigator_line])
|
|
345
343
|
|
|
346
344
|
if hasattr(chart, 'df'):
|
|
347
|
-
chart.
|
|
348
|
-
chart.
|
|
349
|
-
chart.figure.canvas.draw()
|
|
345
|
+
chart.set_data(chart.df, sort_df=False, calc_ma=False, set_candlecolor=True, set_volumecolor=True, calc_info=False, change_lim=False)
|
|
346
|
+
chart.draw_canvas()
|
|
350
347
|
else: raise ValueError(f'You send wrong arg.\n{theme=}')
|
|
351
348
|
|
|
352
349
|
return chart
|
|
@@ -56,6 +56,9 @@ class Base:
|
|
|
56
56
|
|
|
57
57
|
unit_price, unit_volume = ('원', '주')
|
|
58
58
|
|
|
59
|
+
def draw_canvas(self): return self.figure.canvas.draw()
|
|
60
|
+
def blit_canvas(self): return self.figure.canvas.blit()
|
|
61
|
+
|
|
59
62
|
def __init__(self, *args, **kwargs):
|
|
60
63
|
# 기본 툴바 비활성화
|
|
61
64
|
plt.rcParams['toolbar'] = 'None'
|
|
@@ -103,40 +103,48 @@ class CollectionMixin(BM):
|
|
|
103
103
|
return
|
|
104
104
|
|
|
105
105
|
|
|
106
|
-
_set_key = {
|
|
106
|
+
_set_key = {
|
|
107
|
+
'compare', 'rate',
|
|
108
|
+
'rate_open', 'rate_high', 'rate_low',
|
|
109
|
+
'compare_volume', 'rate_volume',
|
|
110
|
+
'space_box_candle',
|
|
111
|
+
'bottom_box_candle', 'top_box_candle',
|
|
112
|
+
'max_box_volume',
|
|
113
|
+
}
|
|
107
114
|
|
|
108
115
|
class DataMixin(CollectionMixin):
|
|
109
|
-
def _validate_column_key(self):
|
|
110
|
-
super()._validate_column_key()
|
|
111
|
-
|
|
116
|
+
def _validate_column_key(self, df):
|
|
117
|
+
super()._validate_column_key(df)
|
|
118
|
+
|
|
119
|
+
for i in ('date', 'Open', 'high', 'low', 'close', 'volume'):
|
|
112
120
|
v = getattr(self, i)
|
|
113
121
|
if v in _set_key: raise Exception(f'you can not set "{i}" to column key.\nself.{i}={v!r}')
|
|
114
122
|
return
|
|
115
123
|
|
|
116
|
-
def _generate_data(self, df, sort_df, calc_ma, set_candlecolor, set_volumecolor, calc_info, *_, **__):
|
|
124
|
+
def _generate_data(self, df: pd.DataFrame, sort_df, calc_ma, set_candlecolor, set_volumecolor, calc_info, *_, **__):
|
|
117
125
|
super()._generate_data(df, sort_df, calc_ma, set_candlecolor, set_volumecolor, *_, **__)
|
|
118
126
|
|
|
119
127
|
if not calc_info:
|
|
120
128
|
keys = set(df.keys())
|
|
121
|
-
list_key = ['
|
|
122
|
-
if self.volume: list_key
|
|
129
|
+
list_key = ['compare', 'rate', 'rate_open', 'rate_high', 'rate_low',]
|
|
130
|
+
if self.volume: list_key += ['compare_volume', 'rate_volume',]
|
|
123
131
|
for i in list_key:
|
|
124
132
|
if i not in keys:
|
|
125
133
|
raise Exception(f'"{i}" column not in DataFrame.\nadd column or set calc_info=True.')
|
|
126
134
|
else:
|
|
127
|
-
self.df['compare'] = (self.df[self.close] - self.df['
|
|
135
|
+
self.df['compare'] = (self.df[self.close] - self.df['close_pre']).fillna(0)
|
|
128
136
|
self.df['rate'] = (self.df['compare'] / self.df[self.close] * 100).__round__(2).fillna(0)
|
|
129
|
-
self.df['rate_open'] = ((self.df[self.Open] - self.df['
|
|
130
|
-
self.df['rate_high'] = ((self.df[self.high] - self.df['
|
|
131
|
-
self.df['rate_low'] = ((self.df[self.low] - self.df['
|
|
137
|
+
self.df['rate_open'] = ((self.df[self.Open] - self.df['close_pre']) / self.df[self.close] * 100).__round__(2).fillna(0)
|
|
138
|
+
self.df['rate_high'] = ((self.df[self.high] - self.df['close_pre']) / self.df[self.close] * 100).__round__(2).fillna(0)
|
|
139
|
+
self.df['rate_low'] = ((self.df[self.low] - self.df['close_pre']) / self.df[self.close] * 100).__round__(2).fillna(0)
|
|
132
140
|
if self.volume:
|
|
133
141
|
self.df['compare_volume'] = (self.df[self.volume] - self.df[self.volume].shift(1)).fillna(0)
|
|
134
142
|
self.df['rate_volume'] = (self.df['compare_volume'] / self.df[self.volume].shift(1) * 100).__round__(2).fillna(0)
|
|
135
143
|
|
|
136
|
-
self.df['
|
|
137
|
-
self.df['
|
|
138
|
-
self.df['
|
|
139
|
-
if self.volume: self.df['
|
|
144
|
+
self.df['space_box_candle'] = (self.df[self.high] - self.df[self.low]) / 5
|
|
145
|
+
self.df['bottom_box_candle'] = self.df[self.low] - self.df['space_box_candle']
|
|
146
|
+
self.df['top_box_candle'] = self.df[self.high] + self.df['space_box_candle']
|
|
147
|
+
if self.volume: self.df['max_box_volume'] = self.df[self.volume] * 1.13
|
|
140
148
|
return
|
|
141
149
|
|
|
142
150
|
def _set_lim(self, xmin, xmax, simpler=False, set_ma=True):
|
|
@@ -245,8 +253,8 @@ class LineMixin(EventMixin):
|
|
|
245
253
|
self.text_date_volume.draw(renderer)
|
|
246
254
|
|
|
247
255
|
# 캔들 강조
|
|
248
|
-
low = self.df['
|
|
249
|
-
high = self.df['
|
|
256
|
+
low = self.df['bottom_box_candle'][index]
|
|
257
|
+
high = self.df['top_box_candle'][index]
|
|
250
258
|
sub = high - low
|
|
251
259
|
if sub < self.min_candleboxheight:
|
|
252
260
|
sub = (self.min_candleboxheight - sub) / 2
|
|
@@ -75,7 +75,7 @@ class CollectionMixin(Base):
|
|
|
75
75
|
|
|
76
76
|
def change_tick_color(self, color):
|
|
77
77
|
for ax in (self.ax_price, self.ax_volume):
|
|
78
|
-
for i in
|
|
78
|
+
for i in ('top', 'bottom', 'left', 'right'): ax.spines[i].set_color(self.color_tick)
|
|
79
79
|
ax.tick_params(colors=color)
|
|
80
80
|
ax.tick_params(colors=color)
|
|
81
81
|
|
|
@@ -95,7 +95,11 @@ class CollectionMixin(Base):
|
|
|
95
95
|
def change_line_color(self, color): return
|
|
96
96
|
|
|
97
97
|
|
|
98
|
-
_set_key = {
|
|
98
|
+
_set_key = {
|
|
99
|
+
'x', 'zero', 'close_pre', 'ymax_volume',
|
|
100
|
+
'top_candle', 'bottom_candle', 'left_candle', 'right_candle',
|
|
101
|
+
'left_volume', 'right_volume',
|
|
102
|
+
}
|
|
99
103
|
|
|
100
104
|
class DataMixin(CollectionMixin):
|
|
101
105
|
df: pd.DataFrame
|
|
@@ -116,11 +120,14 @@ class DataMixin(CollectionMixin):
|
|
|
116
120
|
set_candlecolor, set_volumecolor = (True, True)
|
|
117
121
|
|
|
118
122
|
def _generate_data(self, df: pd.DataFrame, sort_df, calc_ma, set_candlecolor, set_volumecolor, *_, **__):
|
|
119
|
-
self.
|
|
123
|
+
self.set_candlecolor = set_candlecolor
|
|
124
|
+
self.set_volumecolor = set_volumecolor
|
|
125
|
+
|
|
126
|
+
self._validate_column_key(df)
|
|
120
127
|
|
|
121
128
|
# 오름차순 정렬
|
|
122
129
|
if sort_df: df = df.sort_values([self.date])
|
|
123
|
-
df = df.reset_index()
|
|
130
|
+
df = df.reset_index(drop=True)
|
|
124
131
|
|
|
125
132
|
self.list_index = df.index.tolist()
|
|
126
133
|
self.xmin, self.xmax = (0, self.list_index[-1])
|
|
@@ -138,42 +145,42 @@ class DataMixin(CollectionMixin):
|
|
|
138
145
|
if key not in set_key:
|
|
139
146
|
raise KeyError(f'"{key}" column not found.\nset calc_ma=True or add "{key}" column.')
|
|
140
147
|
|
|
141
|
-
df['
|
|
142
|
-
df['
|
|
143
|
-
df['
|
|
144
|
-
df['
|
|
145
|
-
df['
|
|
146
|
-
df.loc[:, '
|
|
148
|
+
df['x'] = df.index + 0.5
|
|
149
|
+
df['left_candle'] = df['x'] - self.candle_width_half
|
|
150
|
+
df['right_candle'] = df['x'] + self.candle_width_half
|
|
151
|
+
df['left_volume'] = df['x'] - self.volume_width_half
|
|
152
|
+
df['right_volume'] = df['x'] + self.volume_width_half
|
|
153
|
+
df.loc[:, 'zero'] = 0
|
|
154
|
+
|
|
155
|
+
df['top_candle'] = np.where(df[self.Open] <= df[self.close], df[self.close], df[self.Open])
|
|
156
|
+
df['top_candle'] = np.where(df[self.close] < df[self.Open], df[self.Open], df[self.close])
|
|
157
|
+
df['bottom_candle'] = np.where(df[self.Open] <= df[self.close], df[self.Open], df[self.close])
|
|
158
|
+
df['bottom_candle'] = np.where(df[self.close] < df[self.Open], df[self.close], df[self.Open])
|
|
159
|
+
|
|
160
|
+
df['close_pre'] = df[self.close].shift(1)
|
|
161
|
+
if self.volume: df['ymax_volume'] = df[self.volume] * 1.2
|
|
147
162
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
df['_bottom'] = np.where(df[self.Open] <= df[self.close], df[self.Open], df[self.close])
|
|
151
|
-
df['_bottom'] = np.where(df[self.close] < df[self.Open], df[self.close], df[self.Open])
|
|
163
|
+
self.df = df
|
|
164
|
+
return
|
|
152
165
|
|
|
153
|
-
|
|
154
|
-
|
|
166
|
+
def _validate_column_key(self, df: pd.DataFrame):
|
|
167
|
+
for i in ('date', 'Open', 'high', 'low', 'close', 'volume'):
|
|
168
|
+
v = getattr(self, i)
|
|
169
|
+
if v in _set_key: raise Exception(f'you can not set "{i}" to column key.\nself.{i}={v!r}')
|
|
155
170
|
|
|
156
|
-
|
|
171
|
+
|
|
172
|
+
if not self.set_candlecolor:
|
|
157
173
|
keys = set(df.keys())
|
|
158
|
-
for i in ('facecolor', 'edgecolor', '
|
|
174
|
+
for i in ('facecolor', 'edgecolor', 'facecolor_volume', 'edgecolor_volume',):
|
|
159
175
|
if i not in keys:
|
|
160
176
|
raise Exception(f'"{i}" column not in DataFrame.\nadd column or set set_candlecolor=True.')
|
|
161
|
-
self.set_candlecolor = set_candlecolor
|
|
162
177
|
|
|
163
|
-
if not set_volumecolor:
|
|
178
|
+
if not self.set_volumecolor:
|
|
164
179
|
keys = set(df.keys())
|
|
165
|
-
for i in ('
|
|
180
|
+
for i in ('facecolor_volume', 'edgecolor_volume',):
|
|
166
181
|
if i not in keys:
|
|
167
182
|
raise Exception(f'"{i}" column not in DataFrame.\nadd column or set set_volumecolor=True.')
|
|
168
|
-
self.set_volumecolor = set_volumecolor
|
|
169
183
|
|
|
170
|
-
self.df = df
|
|
171
|
-
return
|
|
172
|
-
|
|
173
|
-
def _validate_column_key(self):
|
|
174
|
-
for i in ('date', 'Open', 'high', 'low', 'close', 'volume'):
|
|
175
|
-
v = getattr(self, i)
|
|
176
|
-
if v in _set_key: raise Exception(f'you can not set "{i}" to column key.\nself.{i}={v!r}')
|
|
177
184
|
return
|
|
178
185
|
|
|
179
186
|
|
|
@@ -192,46 +199,46 @@ class SegmentMixin(DataMixin):
|
|
|
192
199
|
def _get_segments(self):
|
|
193
200
|
# 캔들 세그먼트
|
|
194
201
|
segment_candle = self.df[[
|
|
195
|
-
'
|
|
196
|
-
'
|
|
197
|
-
'
|
|
198
|
-
'
|
|
199
|
-
'
|
|
200
|
-
'
|
|
201
|
-
'
|
|
202
|
-
'
|
|
203
|
-
'
|
|
204
|
-
'
|
|
205
|
-
'
|
|
206
|
-
'
|
|
202
|
+
'x', self.high,
|
|
203
|
+
'x', 'top_candle',
|
|
204
|
+
'left_candle', 'top_candle',
|
|
205
|
+
'left_candle', 'bottom_candle',
|
|
206
|
+
'x', 'bottom_candle',
|
|
207
|
+
'x', self.low,
|
|
208
|
+
'x', 'bottom_candle',
|
|
209
|
+
'right_candle', 'bottom_candle',
|
|
210
|
+
'right_candle', 'top_candle',
|
|
211
|
+
'x', 'top_candle',
|
|
212
|
+
'x', self.high,
|
|
213
|
+
'x', 'top_candle',
|
|
207
214
|
]].values
|
|
208
215
|
self.segment_candle = segment_candle.reshape(segment_candle.shape[0], 12, 2)
|
|
209
216
|
|
|
210
217
|
# 심지 세그먼트
|
|
211
218
|
segment_wick = self.df[[
|
|
212
|
-
'
|
|
213
|
-
'
|
|
219
|
+
'x', self.high,
|
|
220
|
+
'x', self.low,
|
|
214
221
|
]].values
|
|
215
222
|
self.segment_candle_wick = segment_wick.reshape(segment_wick.shape[0], 2, 2)
|
|
216
223
|
|
|
217
224
|
# 종가 세그먼트
|
|
218
|
-
segment_priceline = segment_wick = self.df[['
|
|
225
|
+
segment_priceline = segment_wick = self.df[['x', self.close]].values
|
|
219
226
|
self.segment_priceline = segment_priceline.reshape(1, *segment_wick.shape)
|
|
220
227
|
|
|
221
228
|
if self.volume:
|
|
222
229
|
# 거래량 바 세그먼트
|
|
223
230
|
segment_volume = self.df[[
|
|
224
|
-
'
|
|
225
|
-
'
|
|
226
|
-
'
|
|
227
|
-
'
|
|
231
|
+
'left_volume', 'zero',
|
|
232
|
+
'left_volume', self.volume,
|
|
233
|
+
'right_volume', self.volume,
|
|
234
|
+
'right_volume', 'zero',
|
|
228
235
|
]].values
|
|
229
236
|
self.segment_volume = segment_volume.reshape(segment_volume.shape[0], 4, 2)
|
|
230
237
|
|
|
231
238
|
# 거래량 심지 세그먼트
|
|
232
239
|
segment_volume_wick = self.df[[
|
|
233
|
-
'
|
|
234
|
-
'
|
|
240
|
+
'x', 'zero',
|
|
241
|
+
'x', self.volume,
|
|
235
242
|
]].values
|
|
236
243
|
self.segment_volume_wick = segment_volume_wick.reshape(segment_volume_wick.shape[0], 2, 2)
|
|
237
244
|
|
|
@@ -251,26 +258,26 @@ class SegmentMixin(DataMixin):
|
|
|
251
258
|
self.df.loc[self.df[self.close] == self.df[self.Open], ['facecolor', 'edgecolor']] = (self.color_flat, self.color_flat)
|
|
252
259
|
if self.color_up != self.color_up_down:
|
|
253
260
|
# 양봉(비우기)
|
|
254
|
-
self.df.loc[(self.df['facecolor'] == self.color_up) & (self.df[self.close] <= self.df['
|
|
261
|
+
self.df.loc[(self.df['facecolor'] == self.color_up) & (self.df[self.close] <= self.df['close_pre']), 'facecolor'] = self.color_up_down
|
|
255
262
|
if self.color_down != self.color_down_up:
|
|
256
263
|
# 음봉(비우기)
|
|
257
|
-
self.df.loc[(self.df['facecolor'] == self.color_down) & (self.df['
|
|
264
|
+
self.df.loc[(self.df['facecolor'] == self.color_down) & (self.df['close_pre'] <= self.df[self.close]), ['facecolor']] = self.color_down_up
|
|
258
265
|
|
|
259
266
|
self.facecolor_candle = self.df['facecolor'].values
|
|
260
267
|
self.edgecolor_candle = self.df['edgecolor'].values
|
|
261
268
|
|
|
262
269
|
if self.set_volumecolor:
|
|
263
270
|
# 거래량
|
|
264
|
-
self.df.loc[:, ['
|
|
271
|
+
self.df.loc[:, ['facecolor_volume', 'edgecolor_volume']] = (self.color_volume_up, self.color_volume_up)
|
|
265
272
|
if self.color_up != self.color_down:
|
|
266
273
|
# 전일대비 하락
|
|
267
|
-
self.df.loc[self.df[self.close] < self.df['
|
|
274
|
+
self.df.loc[self.df[self.close] < self.df['close_pre'], ['facecolor_volume', 'edgecolor_volume']] = (self.color_volume_down, self.color_volume_down)
|
|
268
275
|
if self.color_up != self.color_flat:
|
|
269
276
|
# 전일과 동일
|
|
270
|
-
self.df.loc[self.df[self.close] == self.df['
|
|
277
|
+
self.df.loc[self.df[self.close] == self.df['close_pre'], ['facecolor_volume', 'edgecolor_volume']] = (self.color_volume_flat, self.color_volume_flat)
|
|
271
278
|
|
|
272
|
-
self.facecolor_volume = self.df['
|
|
273
|
-
self.edgecolor_volume = self.df['
|
|
279
|
+
self.facecolor_volume = self.df['facecolor_volume'].values
|
|
280
|
+
self.edgecolor_volume = self.df['edgecolor_volume'].values
|
|
274
281
|
|
|
275
282
|
# 기존 legend 제거
|
|
276
283
|
legends = self.ax_legend.get_legend()
|
|
@@ -307,7 +314,7 @@ class SegmentMixin(DataMixin):
|
|
|
307
314
|
# 주가 차트 가격이동평균선
|
|
308
315
|
key_ma = []
|
|
309
316
|
for i in reversed(self.list_ma):
|
|
310
|
-
key_ma.append('
|
|
317
|
+
key_ma.append('x')
|
|
311
318
|
key_ma.append(f'ma{i}')
|
|
312
319
|
segment_ma = self.df[key_ma].values
|
|
313
320
|
self.segment_ma = segment_ma.reshape(segment_ma.shape[0], len(self.list_ma), 2).swapaxes(0, 1)
|
|
@@ -318,7 +325,7 @@ class SegmentMixin(DataMixin):
|
|
|
318
325
|
if index_start < 0: index_start = 0
|
|
319
326
|
if index_end < 1: index_end = 1
|
|
320
327
|
|
|
321
|
-
index_end +=
|
|
328
|
+
index_end += 2
|
|
322
329
|
if indsub < self.limit_candle:
|
|
323
330
|
self._set_candle_segments(index_start, index_end)
|
|
324
331
|
elif indsub < self.limit_wick:
|
|
@@ -484,6 +491,7 @@ class DrawMixin(EventMixin):
|
|
|
484
491
|
self.vxmin, self.vxmax = (xmin, xmax + 1)
|
|
485
492
|
if xmin < 0: xmin = 0
|
|
486
493
|
if xmax < 0: xmax = 0
|
|
494
|
+
xmax += 1
|
|
487
495
|
if xmin == xmax: xmax += 1
|
|
488
496
|
|
|
489
497
|
ymin, ymax = (self.df[self.low][xmin:xmax].min(), self.df[self.high][xmin:xmax].max())
|
|
@@ -492,7 +500,7 @@ class DrawMixin(EventMixin):
|
|
|
492
500
|
self.price_ymin, self.price_ymax = (ymin-yspace, ymax+yspace)
|
|
493
501
|
|
|
494
502
|
# 거래량 차트 ymax
|
|
495
|
-
self.volume_ymax = self.df['
|
|
503
|
+
self.volume_ymax = self.df['ymax_volume'][xmin:xmax].max() if self.volume else 1
|
|
496
504
|
|
|
497
505
|
self._set_segments(xmin, xmax, simpler, set_ma)
|
|
498
506
|
self._change_lim(self.vxmin, self.vxmax)
|
|
@@ -125,10 +125,10 @@ class CollectionMixin(PlotMixin):
|
|
|
125
125
|
|
|
126
126
|
keys = []
|
|
127
127
|
for i in reversed(self.list_ma):
|
|
128
|
-
keys.append('
|
|
128
|
+
keys.append('x')
|
|
129
129
|
keys.append(f'ma{i}')
|
|
130
130
|
|
|
131
|
-
segment_slider = self.df[keys + ['
|
|
131
|
+
segment_slider = self.df[keys + ['x', self.close] ].values
|
|
132
132
|
segment_slider = segment_slider.reshape(segment_slider.shape[0], len(self.list_ma)+1, 2).swapaxes(0, 1)
|
|
133
133
|
self.collection_slider.set_segments(segment_slider)
|
|
134
134
|
return
|
|
@@ -186,7 +186,7 @@ class NavigatorMixin(CollectionMixin):
|
|
|
186
186
|
# 슬라이더 텍스트 y
|
|
187
187
|
self.text_slider.set_y(ymax)
|
|
188
188
|
|
|
189
|
-
self.navigator.set_linewidth([ysub, 5])
|
|
189
|
+
self.navigator.set_linewidth([ysub+ysub, 5])
|
|
190
190
|
|
|
191
191
|
# 네비게이터 라인 선택 범위
|
|
192
192
|
xsub = self.slider_xmax - self.slider_xmin
|
|
@@ -365,12 +365,10 @@ class ClickMixin(MouseMoveMixin):
|
|
|
365
365
|
return
|
|
366
366
|
|
|
367
367
|
def _on_click(self, e: MouseEvent):
|
|
368
|
-
if self.in_slider: self._on_click_slider(e)
|
|
368
|
+
if self.in_slider and not self.is_click_slider and e.button == MouseButton.LEFT: self._on_click_slider(e)
|
|
369
369
|
return
|
|
370
370
|
|
|
371
371
|
def _on_click_slider(self, e: MouseEvent):
|
|
372
|
-
if self.is_click_slider or e.button != MouseButton.LEFT: return
|
|
373
|
-
|
|
374
372
|
self.background_with_nav_pre = self.background_with_nav
|
|
375
373
|
|
|
376
374
|
self.is_click_slider = True
|
|
@@ -461,7 +459,7 @@ class ReleaseMixin(SliderSelectMixin):
|
|
|
461
459
|
return
|
|
462
460
|
|
|
463
461
|
def _on_release(self, e: MouseEvent):
|
|
464
|
-
if self.in_slider and self.is_click_slider: self._on_release_slider(e)
|
|
462
|
+
if self.in_slider and self.is_click_slider and e.button == MouseButton.LEFT: self._on_release_slider(e)
|
|
465
463
|
return
|
|
466
464
|
|
|
467
465
|
def _on_release_slider(self, e: MouseEvent):
|
|
@@ -481,6 +479,8 @@ class ReleaseMixin(SliderSelectMixin):
|
|
|
481
479
|
self.is_click_slider = False
|
|
482
480
|
self.is_move = False
|
|
483
481
|
self.click_navleft, self.click_navright = (False, False)
|
|
482
|
+
|
|
483
|
+
self.draw_canvas()
|
|
484
484
|
return
|
|
485
485
|
|
|
486
486
|
|
|
@@ -488,21 +488,23 @@ class ChartClickMixin(ReleaseMixin):
|
|
|
488
488
|
is_click_chart = False
|
|
489
489
|
|
|
490
490
|
def _on_click(self, e: MouseEvent):
|
|
491
|
-
if
|
|
492
|
-
|
|
491
|
+
if (
|
|
492
|
+
(self.in_price_chart or self.in_volume_chart)
|
|
493
|
+
and not self.is_click_chart
|
|
494
|
+
and e.button == MouseButton.LEFT
|
|
495
|
+
): self._on_click_chart(e)
|
|
496
|
+
elif self.in_slider and not self.is_click_slider and e.button == MouseButton.LEFT: self._on_click_slider(e)
|
|
493
497
|
return
|
|
494
498
|
|
|
495
499
|
def _on_click_chart(self, e: MouseEvent):
|
|
496
|
-
if self.is_click_chart: return
|
|
497
|
-
|
|
498
500
|
self.is_click_chart = True
|
|
499
|
-
self._x_click = e.x
|
|
501
|
+
self._x_click = e.x
|
|
500
502
|
self.figure.canvas.set_cursor(cursors.RESIZE_HORIZONTAL)
|
|
501
503
|
return
|
|
502
504
|
|
|
503
505
|
def _on_release(self, e):
|
|
504
|
-
if self.is_click_chart and (self.in_price_chart or self.in_volume_chart): self._on_release_chart(e)
|
|
505
|
-
elif self.is_click_slider and self.in_slider: self._on_release_slider(e)
|
|
506
|
+
if self.is_click_chart and (self.in_price_chart or self.in_volume_chart) and e.button == MouseButton.LEFT: self._on_release_chart(e)
|
|
507
|
+
elif self.is_click_slider and self.in_slider and e.button == MouseButton.LEFT: self._on_release_slider(e)
|
|
506
508
|
return
|
|
507
509
|
|
|
508
510
|
def _on_release_chart(self, e):
|
|
@@ -540,7 +542,7 @@ class ChartClickMixin(ReleaseMixin):
|
|
|
540
542
|
return super()._on_move_volume_chart(e)
|
|
541
543
|
|
|
542
544
|
def _move_chart(self, e: MouseEvent):
|
|
543
|
-
x = e.x
|
|
545
|
+
x = e.x
|
|
544
546
|
left, right = self.navcoordinate
|
|
545
547
|
nsub = right - left
|
|
546
548
|
xsub = x - self._x_click
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: seolpyo-mplchart
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.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
|
|
@@ -32,18 +32,24 @@ Ethereum: 0x1c5fb8a5e0b1153cd4116c91736bd16fabf83520
|
|
|
32
32
|
|
|
33
33
|
|
|
34
34
|
# Document
|
|
35
|
-
[English](https://white.seolpyo.com/entry/148/)
|
|
35
|
+
[English Document](https://white.seolpyo.com/entry/148/)
|
|
36
36
|
|
|
37
|
-
[한글](https://white.seolpyo.com/entry/147/)
|
|
37
|
+
[한글 설명서](https://white.seolpyo.com/entry/147/)
|
|
38
38
|
|
|
39
39
|
|
|
40
|
-
# Sample
|
|
40
|
+
# Sample Image
|
|
41
|
+
## sample
|
|
41
42
|

|
|
42
43
|
|
|
44
|
+
## tkinter sample
|
|
43
45
|

|
|
44
46
|
|
|
47
|
+
## English format sample
|
|
45
48
|

|
|
46
49
|
|
|
50
|
+
## Korean format sample
|
|
47
51
|

|
|
48
52
|
|
|
53
|
+
|
|
54
|
+
# 40,000 data sample
|
|
49
55
|

|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{seolpyo_mplchart-1.2.1 → seolpyo_mplchart-1.3.1}/seolpyo_mplchart.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|