seolpyo-mplchart 2.0.0.3__py3-none-any.whl → 2.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- seolpyo_mplchart/__init__.py +17 -133
- seolpyo_mplchart/_chart/__init__.py +39 -31
- seolpyo_mplchart/_chart/base/__init__.py +111 -0
- seolpyo_mplchart/_chart/base/a_canvas.py +250 -0
- seolpyo_mplchart/_chart/base/b_artist.py +143 -0
- seolpyo_mplchart/_chart/base/c_draw.py +100 -0
- seolpyo_mplchart/_chart/base/d_segment.py +262 -0
- seolpyo_mplchart/_chart/base/e_axis.py +267 -0
- seolpyo_mplchart/_chart/base/f_background.py +62 -0
- seolpyo_mplchart/_chart/base/g_event.py +66 -0
- seolpyo_mplchart/_chart/base/h_data.py +138 -0
- seolpyo_mplchart/_chart/base/test.py +58 -0
- seolpyo_mplchart/_chart/cursor/__init__.py +125 -0
- seolpyo_mplchart/_chart/cursor/b_artist.py +130 -0
- seolpyo_mplchart/_chart/cursor/c_draw.py +96 -0
- seolpyo_mplchart/_chart/cursor/d_segment.py +359 -0
- seolpyo_mplchart/_chart/cursor/e_axis.py +65 -0
- seolpyo_mplchart/_chart/cursor/g_event.py +233 -0
- seolpyo_mplchart/_chart/cursor/h_data.py +61 -0
- seolpyo_mplchart/_chart/cursor/test.py +69 -0
- seolpyo_mplchart/_chart/slider/__init__.py +169 -0
- seolpyo_mplchart/_chart/slider/a_canvas.py +260 -0
- seolpyo_mplchart/_chart/slider/b_artist.py +91 -0
- seolpyo_mplchart/_chart/slider/c_draw.py +54 -0
- seolpyo_mplchart/_chart/slider/d_segment.py +166 -0
- seolpyo_mplchart/_chart/slider/e_axis.py +70 -0
- seolpyo_mplchart/_chart/slider/f_background.py +37 -0
- seolpyo_mplchart/_chart/slider/g_event.py +353 -0
- seolpyo_mplchart/_chart/slider/h_data.py +102 -0
- seolpyo_mplchart/_chart/slider/test.py +71 -0
- seolpyo_mplchart/_config/candle.py +1 -0
- seolpyo_mplchart/_config/figure.py +3 -4
- seolpyo_mplchart/_config/ma.py +2 -0
- seolpyo_mplchart/_config/slider/config.py +2 -2
- seolpyo_mplchart/_config/slider/figure.py +3 -4
- seolpyo_mplchart/_config/slider/nav.py +3 -2
- seolpyo_mplchart/_config/volume.py +1 -0
- seolpyo_mplchart/_utils/__init__.py +10 -0
- seolpyo_mplchart/_utils/nums.py +67 -0
- seolpyo_mplchart/_utils/theme/__init__.py +15 -0
- seolpyo_mplchart/_utils/theme/dark.py +57 -0
- seolpyo_mplchart/_utils/theme/light.py +56 -0
- seolpyo_mplchart/_utils/utils.py +28 -0
- seolpyo_mplchart/_utils/xl/__init__.py +15 -0
- seolpyo_mplchart/_utils/xl/csv.py +46 -0
- seolpyo_mplchart/_utils/xl/xlsx.py +49 -0
- seolpyo_mplchart/sample/apple.txt +6058 -0
- seolpyo_mplchart/sample/samsung.txt +5938 -0
- seolpyo_mplchart/test.py +5 -5
- {seolpyo_mplchart-2.0.0.3.dist-info → seolpyo_mplchart-2.1.0.dist-info}/METADATA +21 -13
- seolpyo_mplchart-2.1.0.dist-info/RECORD +89 -0
- seolpyo_mplchart-2.0.0.3.dist-info/RECORD +0 -50
- {seolpyo_mplchart-2.0.0.3.dist-info → seolpyo_mplchart-2.1.0.dist-info}/WHEEL +0 -0
- {seolpyo_mplchart-2.0.0.3.dist-info → seolpyo_mplchart-2.1.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from matplotlib.axes import Axes
|
|
2
|
+
from matplotlib.text import Text
|
|
3
|
+
|
|
4
|
+
from ..base.a_canvas import Figure
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Base:
|
|
9
|
+
figure: Figure
|
|
10
|
+
ax_price: Axes
|
|
11
|
+
ax_volume: Axes
|
|
12
|
+
|
|
13
|
+
key_volume: str
|
|
14
|
+
|
|
15
|
+
vxmin: int
|
|
16
|
+
vxmax: int
|
|
17
|
+
price_ymin: int
|
|
18
|
+
price_ymax: int
|
|
19
|
+
volume_ymax: int
|
|
20
|
+
|
|
21
|
+
artist_info_candle: Text
|
|
22
|
+
artist_info_volume: Text
|
|
23
|
+
artist_label_x: Text
|
|
24
|
+
artist_label_y: Text
|
|
25
|
+
|
|
26
|
+
axis: callable
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class Mixin(Base):
|
|
30
|
+
def axis(self, xmin, *, xmax):
|
|
31
|
+
super().axis(xmin, xmax=xmax)
|
|
32
|
+
# print('cursor axis')
|
|
33
|
+
|
|
34
|
+
psub = (self.price_ymax - self.price_ymin)
|
|
35
|
+
self.min_height_box_candle = psub / 8
|
|
36
|
+
|
|
37
|
+
pydistance = psub / 20
|
|
38
|
+
|
|
39
|
+
self.min_height_box_volume = 10
|
|
40
|
+
if self.key_volume:
|
|
41
|
+
self.min_height_box_volume = self.volume_ymax / 4
|
|
42
|
+
|
|
43
|
+
vxsub = self.vxmax - self.vxmin
|
|
44
|
+
self.vmiddle = self.vxmax - int((vxsub) / 2)
|
|
45
|
+
|
|
46
|
+
vxdistance = vxsub / 50
|
|
47
|
+
self.v0, self.v1 = (self.vxmin + vxdistance, self.vxmax - vxdistance)
|
|
48
|
+
|
|
49
|
+
yvolume = self.volume_ymax * 0.85
|
|
50
|
+
|
|
51
|
+
# 정보 텍스트박스 y축 설정
|
|
52
|
+
self.artist_info_candle.set_y(self.price_ymax - pydistance)
|
|
53
|
+
self.artist_info_volume.set_y(yvolume)
|
|
54
|
+
|
|
55
|
+
return
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class AxisMixin(Mixin):
|
|
59
|
+
v0: int
|
|
60
|
+
v1: int
|
|
61
|
+
vmiddle: int
|
|
62
|
+
|
|
63
|
+
min_height_box_candle: float
|
|
64
|
+
min_height_box_volume: float
|
|
65
|
+
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
from matplotlib.axes import Axes
|
|
2
|
+
from matplotlib.backend_bases import MouseEvent
|
|
3
|
+
from matplotlib.collections import LineCollection
|
|
4
|
+
from matplotlib.text import Text
|
|
5
|
+
import pandas as pd
|
|
6
|
+
|
|
7
|
+
from ..base.a_canvas import Figure
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Base:
|
|
11
|
+
figure: Figure
|
|
12
|
+
ax_price: Axes
|
|
13
|
+
ax_volume: Axes
|
|
14
|
+
|
|
15
|
+
key_volume: str
|
|
16
|
+
df: pd.DataFrame
|
|
17
|
+
|
|
18
|
+
vxmin: int
|
|
19
|
+
vxmax: int
|
|
20
|
+
price_ymin: float
|
|
21
|
+
price_ymax: float
|
|
22
|
+
volume_ymax: float
|
|
23
|
+
|
|
24
|
+
collection_box_price: LineCollection
|
|
25
|
+
collection_box_volume: LineCollection
|
|
26
|
+
collection_price_crossline: LineCollection
|
|
27
|
+
|
|
28
|
+
index_list: list
|
|
29
|
+
|
|
30
|
+
min_height_box_candle: float
|
|
31
|
+
min_height_box_volume: float
|
|
32
|
+
|
|
33
|
+
connect_events: callable
|
|
34
|
+
|
|
35
|
+
artist_label_x: Text
|
|
36
|
+
artist_label_y: Text
|
|
37
|
+
|
|
38
|
+
in_chart = False
|
|
39
|
+
in_chart_price = False
|
|
40
|
+
in_chart_volume = False
|
|
41
|
+
|
|
42
|
+
in_candle = False
|
|
43
|
+
in_volume = False
|
|
44
|
+
|
|
45
|
+
_restore_region: callable
|
|
46
|
+
|
|
47
|
+
_set_crossline: callable
|
|
48
|
+
_draw_crossline: callable
|
|
49
|
+
_set_label_x: callable
|
|
50
|
+
_draw_label_x: callable
|
|
51
|
+
|
|
52
|
+
_set_label_y: callable
|
|
53
|
+
_draw_label_y: callable
|
|
54
|
+
|
|
55
|
+
_set_box_candle: callable
|
|
56
|
+
_draw_box_candle: callable
|
|
57
|
+
_set_box_volume: callable
|
|
58
|
+
_draw_box_volume: callable
|
|
59
|
+
|
|
60
|
+
_set_info_candle: callable
|
|
61
|
+
_draw_info_candle: callable
|
|
62
|
+
_set_info_volume: callable
|
|
63
|
+
_draw_info_volume: callable
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class AxMixin(Base):
|
|
67
|
+
def _check_ax(self, e: MouseEvent):
|
|
68
|
+
ax = e.inaxes
|
|
69
|
+
# print(f'{ax=}')
|
|
70
|
+
self.in_chart = False
|
|
71
|
+
self.in_chart_price, self.in_chart_volume = (False, False)
|
|
72
|
+
|
|
73
|
+
if e.xdata is None or e.ydata is None:
|
|
74
|
+
return
|
|
75
|
+
|
|
76
|
+
if self.vxmin <= e.xdata and e.xdata <= self.vxmax:
|
|
77
|
+
if ax is self.ax_price and (
|
|
78
|
+
self.price_ymin <= e.ydata and e.ydata <= self.price_ymax
|
|
79
|
+
):
|
|
80
|
+
self.in_chart = True
|
|
81
|
+
self.in_chart_price = True
|
|
82
|
+
elif ax is self.ax_volume and (
|
|
83
|
+
0 <= e.ydata and e.ydata <= self.volume_ymax
|
|
84
|
+
):
|
|
85
|
+
self.in_chart = True
|
|
86
|
+
self.in_chart_volume = True
|
|
87
|
+
return
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class BoxMixin(Base):
|
|
91
|
+
def _draw_box_artist(self, e: MouseEvent):
|
|
92
|
+
xdata, ydata = (e.xdata, e.ydata)
|
|
93
|
+
ind = int(xdata)
|
|
94
|
+
|
|
95
|
+
self.in_candle, self.in_volume = (False, False)
|
|
96
|
+
|
|
97
|
+
if self.in_chart_price:
|
|
98
|
+
series = self.df.iloc[ind]
|
|
99
|
+
# print(f'{series=}')
|
|
100
|
+
# 박스 크기
|
|
101
|
+
high = series['box_candle_top']
|
|
102
|
+
low = series['box_candle_bottom']
|
|
103
|
+
height = series['box_candle_height']
|
|
104
|
+
# print(f'{(low, high)=}')
|
|
105
|
+
# print(f'{height=}')
|
|
106
|
+
|
|
107
|
+
# 박스 높이 보정
|
|
108
|
+
if height < self.min_height_box_candle:
|
|
109
|
+
sub = (self.min_height_box_candle - height) / 2
|
|
110
|
+
high, low = (high+sub, low-sub)
|
|
111
|
+
|
|
112
|
+
# 커서가 캔들 사이에 있는지 확인
|
|
113
|
+
if low <= ydata and ydata <= high:
|
|
114
|
+
self.in_candle = True
|
|
115
|
+
|
|
116
|
+
# 캔들 강조
|
|
117
|
+
x0, x1 = (ind-0.3, ind+1.3)
|
|
118
|
+
segment = [(
|
|
119
|
+
(x0, high),
|
|
120
|
+
(x1, high),
|
|
121
|
+
(x1, low),
|
|
122
|
+
(x0, low),
|
|
123
|
+
(x0, high)
|
|
124
|
+
)]
|
|
125
|
+
self._set_box_candle(segment)
|
|
126
|
+
self._draw_box_candle()
|
|
127
|
+
|
|
128
|
+
return 1
|
|
129
|
+
elif self.in_chart_volume and self.key_volume:
|
|
130
|
+
# 박스 크기
|
|
131
|
+
high = self.df.iloc[ind]['box_volume_top']
|
|
132
|
+
low = 0
|
|
133
|
+
if high < self.min_height_box_volume:
|
|
134
|
+
high = self.min_height_box_volume
|
|
135
|
+
|
|
136
|
+
if low <= ydata and ydata <= high:
|
|
137
|
+
# 거래량 강조
|
|
138
|
+
self.in_volume = True
|
|
139
|
+
|
|
140
|
+
x0, x1 = (ind-0.3, ind+1.3)
|
|
141
|
+
segment = [(
|
|
142
|
+
(x0, high),
|
|
143
|
+
(x1, high),
|
|
144
|
+
(x1, low),
|
|
145
|
+
(x0, low),
|
|
146
|
+
(x0, high)
|
|
147
|
+
)]
|
|
148
|
+
self._set_box_volume(segment)
|
|
149
|
+
self._draw_box_volume()
|
|
150
|
+
|
|
151
|
+
return 1
|
|
152
|
+
return
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class InfoMixin(Base):
|
|
156
|
+
def _draw_info_artist(self, e: MouseEvent):
|
|
157
|
+
return
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class Mixin(AxMixin, BoxMixin, InfoMixin):
|
|
161
|
+
_in_mouse_move = False
|
|
162
|
+
|
|
163
|
+
def need_restore(self):
|
|
164
|
+
if self.collection_price_crossline.get_segments():
|
|
165
|
+
self.collection_price_crossline.set_segments([])
|
|
166
|
+
return True
|
|
167
|
+
return
|
|
168
|
+
|
|
169
|
+
def _on_move(self, e: MouseEvent):
|
|
170
|
+
# print(f'{not self._in_mouse_move=}')
|
|
171
|
+
if not self._in_mouse_move:
|
|
172
|
+
self._in_mouse_move = True
|
|
173
|
+
# print(f'{(e.xdata, e.ydata)=}')
|
|
174
|
+
self.on_move(e)
|
|
175
|
+
self._in_mouse_move = False
|
|
176
|
+
return
|
|
177
|
+
|
|
178
|
+
def on_move(self, e: MouseEvent):
|
|
179
|
+
self._check_ax(e)
|
|
180
|
+
self._on_move_action(e)
|
|
181
|
+
return
|
|
182
|
+
|
|
183
|
+
def _set_and_draw_crossline(self, e: MouseEvent):
|
|
184
|
+
self._set_crossline(e)
|
|
185
|
+
self._draw_crossline()
|
|
186
|
+
|
|
187
|
+
if self._set_label_x(e):
|
|
188
|
+
# print('draw label x')
|
|
189
|
+
self._draw_label_x()
|
|
190
|
+
if self._draw_box_artist(e):
|
|
191
|
+
ind = int(e.xdata)
|
|
192
|
+
if self.in_chart_price:
|
|
193
|
+
self._set_info_candle(ind)
|
|
194
|
+
self._draw_info_candle()
|
|
195
|
+
else:
|
|
196
|
+
self._set_info_volume(ind)
|
|
197
|
+
self._draw_info_volume()
|
|
198
|
+
self._set_label_y(e, is_price_chart=self.in_chart_price)
|
|
199
|
+
self._draw_label_y()
|
|
200
|
+
|
|
201
|
+
return
|
|
202
|
+
|
|
203
|
+
def _on_move_action(self, e: MouseEvent):
|
|
204
|
+
if self.in_chart:
|
|
205
|
+
self._restore_region()
|
|
206
|
+
|
|
207
|
+
self._set_and_draw_crossline(e)
|
|
208
|
+
|
|
209
|
+
self.figure.canvas.blit()
|
|
210
|
+
self.figure.canvas.flush_events()
|
|
211
|
+
elif self.need_restore():
|
|
212
|
+
self._restore_region()
|
|
213
|
+
self.figure.canvas.blit()
|
|
214
|
+
self.figure.canvas.flush_events()
|
|
215
|
+
return
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
class EventMixin(Mixin):
|
|
219
|
+
in_chart = False
|
|
220
|
+
in_chart_price = False
|
|
221
|
+
in_chart_volume = False
|
|
222
|
+
|
|
223
|
+
in_candle = False
|
|
224
|
+
in_volume = False
|
|
225
|
+
|
|
226
|
+
_in_mouse_move = False
|
|
227
|
+
|
|
228
|
+
def connect_events(self):
|
|
229
|
+
super().connect_events()
|
|
230
|
+
|
|
231
|
+
self.figure.canvas.mpl_connect('motion_notify_event', lambda x: self._on_move(x))
|
|
232
|
+
return
|
|
233
|
+
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
|
|
3
|
+
from ..._config import ConfigData
|
|
4
|
+
|
|
5
|
+
class Base:
|
|
6
|
+
CONFIG: ConfigData
|
|
7
|
+
|
|
8
|
+
key_volume: str
|
|
9
|
+
df: pd.DataFrame
|
|
10
|
+
|
|
11
|
+
_add_columns: callable
|
|
12
|
+
set_variables: callable
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Mixin(Base):
|
|
16
|
+
def _add_columns(self):
|
|
17
|
+
super()._add_columns()
|
|
18
|
+
|
|
19
|
+
self.df['compare'] = (self.df['close'] - self.df['pre_close']).fillna(0)
|
|
20
|
+
self.df['rate'] = (self.df['compare'] * 100 / self.df['pre_close']).__round__(2).fillna(0)
|
|
21
|
+
self.df['rate_open'] = ((self.df['open'] - self.df['pre_close']) * 100 / self.df['pre_close']).__round__(2).fillna(0)
|
|
22
|
+
self.df['rate_high'] = ((self.df['high'] - self.df['pre_close']) * 100 / self.df['pre_close']).__round__(2).fillna(0)
|
|
23
|
+
self.df['rate_low'] = ((self.df['low'] - self.df['pre_close']) * 100 / self.df['pre_close']).__round__(2).fillna(0)
|
|
24
|
+
if self.key_volume:
|
|
25
|
+
self.df['pre_volume'] = self.df['volume'].shift(1)
|
|
26
|
+
self.df['compare_volume'] = (self.df['volume'] - self.df['pre_volume']).fillna(0)
|
|
27
|
+
self.df['rate_volume'] = (self.df['compare_volume'] * 100 / self.df['pre_volume']).__round__(2).fillna(0)
|
|
28
|
+
|
|
29
|
+
self.df['space_box_candle'] = (self.df['high'] - self.df['low']) / 5
|
|
30
|
+
self.df['box_candle_top'] = self.df['high'] + self.df['space_box_candle']
|
|
31
|
+
self.df['box_candle_bottom'] = self.df['low'] - self.df['space_box_candle']
|
|
32
|
+
self.df['box_candle_height'] = self.df['box_candle_top'] - self.df['box_candle_bottom']
|
|
33
|
+
|
|
34
|
+
if self.key_volume:
|
|
35
|
+
self.df['box_volume_top'] = self.df['volume'] * 1.15
|
|
36
|
+
|
|
37
|
+
return
|
|
38
|
+
|
|
39
|
+
def set_variables(self):
|
|
40
|
+
super().set_variables()
|
|
41
|
+
|
|
42
|
+
self._set_length_text()
|
|
43
|
+
return
|
|
44
|
+
|
|
45
|
+
def _set_length_text(self):
|
|
46
|
+
func = lambda x: len(self.CONFIG.UNIT.func(x, digit=self.CONFIG.UNIT.digit, word=self.CONFIG.UNIT.price))
|
|
47
|
+
self._length_text = self.df['high'].apply(func).max()
|
|
48
|
+
|
|
49
|
+
if self.key_volume:
|
|
50
|
+
func = lambda x: len(self.CONFIG.UNIT.func(x, digit=self.CONFIG.UNIT.digit_volume, word=self.CONFIG.UNIT.volume))
|
|
51
|
+
lenth_volume = self.df['volume'].apply(func).max()
|
|
52
|
+
# print(f'{self._length_text=}')
|
|
53
|
+
# print(f'{lenth_volume=}')
|
|
54
|
+
if self._length_text < lenth_volume:
|
|
55
|
+
self._length_text = lenth_volume
|
|
56
|
+
return
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class DataMixin(Mixin):
|
|
60
|
+
_length_text: int
|
|
61
|
+
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
name_pkg = 'seolpyo_mplchart'
|
|
4
|
+
path_pkg = Path(__file__)
|
|
5
|
+
while path_pkg.name != name_pkg:
|
|
6
|
+
path_pkg = path_pkg.parent
|
|
7
|
+
sys.path = [path_pkg.parent.__str__()] + sys.path
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
|
|
11
|
+
import pandas as pd
|
|
12
|
+
import matplotlib.pyplot as plt
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
from seolpyo_mplchart._utils.theme import set_theme
|
|
16
|
+
from seolpyo_mplchart._chart.cursor import Chart
|
|
17
|
+
from seolpyo_mplchart._config import DEFAULTCONFIG, DEFAULTCONFIG_EN
|
|
18
|
+
|
|
19
|
+
path_file = path_pkg / 'sample' / 'samsung.txt'
|
|
20
|
+
with open(path_file, 'r', encoding='utf-8') as txt:
|
|
21
|
+
data = json.load(txt)
|
|
22
|
+
df = pd.DataFrame(data[:300])
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class C(Chart):
|
|
26
|
+
# limit_candle = 200
|
|
27
|
+
# limit_wick = 200
|
|
28
|
+
t = 'light'
|
|
29
|
+
# watermark = ''
|
|
30
|
+
def __init__(self):
|
|
31
|
+
super().__init__()
|
|
32
|
+
self.figure.canvas.mpl_connect('button_press_event', lambda x: self.theme(x))
|
|
33
|
+
self.CONFIG.FORMAT.candle += '\nCustom: {custom}'
|
|
34
|
+
|
|
35
|
+
def get_info_kwargs(self, is_price, **kwargs):
|
|
36
|
+
kwargs = super().get_info_kwargs(is_price, **kwargs)
|
|
37
|
+
kwargs['close'] = 'Cusotom close value'
|
|
38
|
+
kwargs['custom'] = 'this is custom add info kwargs'
|
|
39
|
+
return kwargs
|
|
40
|
+
|
|
41
|
+
def theme(self, e):
|
|
42
|
+
btn = getattr(e, 'button')
|
|
43
|
+
# print(f'{str(btn)=}')
|
|
44
|
+
if str(btn) == '3':
|
|
45
|
+
# print('refresh')
|
|
46
|
+
if self.t == 'light':
|
|
47
|
+
self.t = 'dark'
|
|
48
|
+
self.CONFIG = set_theme(DEFAULTCONFIG_EN, theme=self.t)
|
|
49
|
+
else:
|
|
50
|
+
self.t = 'light'
|
|
51
|
+
self.CONFIG = set_theme(DEFAULTCONFIG, theme=self.t)
|
|
52
|
+
# print(f'{self.t=}')
|
|
53
|
+
# self.CONFIG.MA.ma_list = []
|
|
54
|
+
self.refresh()
|
|
55
|
+
return
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def run():
|
|
59
|
+
chart = C()
|
|
60
|
+
chart.set_data(df)
|
|
61
|
+
plt.show()
|
|
62
|
+
plt.close()
|
|
63
|
+
return
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
if __name__ == '__main__':
|
|
67
|
+
run()
|
|
68
|
+
|
|
69
|
+
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
from matplotlib.axes import Axes
|
|
2
|
+
from matplotlib.collections import LineCollection
|
|
3
|
+
from matplotlib.text import Text
|
|
4
|
+
from matplotlib.axes import Axes
|
|
5
|
+
import numpy as np
|
|
6
|
+
import pandas as pd
|
|
7
|
+
|
|
8
|
+
from ..._config import SLIDERCONFIG, SliderConfigData
|
|
9
|
+
from ..base import Chart as BaseChart
|
|
10
|
+
from ..cursor import CursorMixin
|
|
11
|
+
|
|
12
|
+
from .a_canvas import CanvasMixin, Figure
|
|
13
|
+
from .b_artist import ArtistMixin
|
|
14
|
+
from .c_draw import DrawMixin
|
|
15
|
+
from .d_segment import SegmentMixin
|
|
16
|
+
from .e_axis import AxisMixin
|
|
17
|
+
from .f_background import BackgroundMixin
|
|
18
|
+
from .g_event import EventMixin
|
|
19
|
+
from .h_data import DataMixin
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class SliderMixin(
|
|
23
|
+
CanvasMixin,
|
|
24
|
+
ArtistMixin,
|
|
25
|
+
DrawMixin,
|
|
26
|
+
SegmentMixin,
|
|
27
|
+
AxisMixin,
|
|
28
|
+
BackgroundMixin,
|
|
29
|
+
EventMixin,
|
|
30
|
+
DataMixin,
|
|
31
|
+
):
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class Chart(SliderMixin, CursorMixin, BaseChart):
|
|
36
|
+
candle_on_ma = True
|
|
37
|
+
slider_top = True
|
|
38
|
+
fraction = False
|
|
39
|
+
|
|
40
|
+
limit_candle = 400
|
|
41
|
+
limit_wick = 2_000
|
|
42
|
+
limit_volume = 200
|
|
43
|
+
limit_ma = 8_000
|
|
44
|
+
|
|
45
|
+
min_distance = 5
|
|
46
|
+
|
|
47
|
+
key_date = 'date'
|
|
48
|
+
key_open, key_high, key_low, key_close = ('open', 'high', 'low', 'close')
|
|
49
|
+
key_volume = 'volume'
|
|
50
|
+
|
|
51
|
+
index_list: list[int] = []
|
|
52
|
+
|
|
53
|
+
df: pd.DataFrame
|
|
54
|
+
|
|
55
|
+
CONFIG: SliderConfigData
|
|
56
|
+
|
|
57
|
+
figure: Figure
|
|
58
|
+
ax_legend: Axes
|
|
59
|
+
ax_price: Axes
|
|
60
|
+
ax_volume: Axes
|
|
61
|
+
|
|
62
|
+
ax_slider: Axes
|
|
63
|
+
ax_none: Axes
|
|
64
|
+
_ax_slider_top: Axes
|
|
65
|
+
_ax_slider_bottom: Axes
|
|
66
|
+
|
|
67
|
+
artist_watermark: Text
|
|
68
|
+
collection_candle: LineCollection
|
|
69
|
+
collection_volume: LineCollection
|
|
70
|
+
collection_ma: LineCollection
|
|
71
|
+
|
|
72
|
+
collection_slider: LineCollection
|
|
73
|
+
collection_nav: LineCollection
|
|
74
|
+
collection_slider_vline: LineCollection
|
|
75
|
+
artist_text_slider: Text
|
|
76
|
+
|
|
77
|
+
in_candle = False
|
|
78
|
+
in_volume = False
|
|
79
|
+
|
|
80
|
+
collection_price_crossline: LineCollection
|
|
81
|
+
collection_volume_crossline: LineCollection
|
|
82
|
+
|
|
83
|
+
artist_label_x: Text = None
|
|
84
|
+
artist_label_y: Text = None
|
|
85
|
+
|
|
86
|
+
collection_box_price: LineCollection
|
|
87
|
+
collection_box_volume: LineCollection
|
|
88
|
+
|
|
89
|
+
artist_info_candle: Text
|
|
90
|
+
artist_info_volume: Text
|
|
91
|
+
|
|
92
|
+
v0: int
|
|
93
|
+
v1: int
|
|
94
|
+
vmiddle: int
|
|
95
|
+
|
|
96
|
+
min_height_box_candle: float
|
|
97
|
+
min_height_box_volume: float
|
|
98
|
+
|
|
99
|
+
_length_text: int
|
|
100
|
+
|
|
101
|
+
_in_mouse_move = False
|
|
102
|
+
|
|
103
|
+
###
|
|
104
|
+
|
|
105
|
+
segment_nav: np.ndarray
|
|
106
|
+
segment_volume: np.ndarray
|
|
107
|
+
segment_volume_wick: np.ndarray
|
|
108
|
+
facecolor_volume: np.ndarray
|
|
109
|
+
edgecolor_volume: np.ndarray
|
|
110
|
+
|
|
111
|
+
segment_candle: np.ndarray
|
|
112
|
+
segment_candle_wick: np.ndarray
|
|
113
|
+
segment_priceline: np.ndarray
|
|
114
|
+
facecolor_candle: np.ndarray
|
|
115
|
+
edgecolor_candle: np.ndarray
|
|
116
|
+
|
|
117
|
+
segment_ma: np.ndarray
|
|
118
|
+
edgecolor_ma: np.ndarray
|
|
119
|
+
|
|
120
|
+
price_ymin: int
|
|
121
|
+
price_ymax: int
|
|
122
|
+
volume_ymax: int
|
|
123
|
+
|
|
124
|
+
chart_price_ymax: float
|
|
125
|
+
chart_volume_ymax: float
|
|
126
|
+
|
|
127
|
+
vxmin: int
|
|
128
|
+
vxmax: int
|
|
129
|
+
|
|
130
|
+
_nav_width: float
|
|
131
|
+
|
|
132
|
+
slider_xmin: int
|
|
133
|
+
slider_xmax: int
|
|
134
|
+
slider_ymin: float
|
|
135
|
+
slider_ymax: float
|
|
136
|
+
|
|
137
|
+
in_chart = False
|
|
138
|
+
in_slider = False
|
|
139
|
+
in_chart_price = False
|
|
140
|
+
in_chart_volume = False
|
|
141
|
+
|
|
142
|
+
is_move_chart = False
|
|
143
|
+
is_click_slider = False
|
|
144
|
+
is_click_chart = False
|
|
145
|
+
x_click: float
|
|
146
|
+
|
|
147
|
+
click_nav_left = False
|
|
148
|
+
click_nav_right = False
|
|
149
|
+
|
|
150
|
+
navcoordinate: tuple[int, int]
|
|
151
|
+
|
|
152
|
+
###
|
|
153
|
+
|
|
154
|
+
_visible_ma: set[int] = set()
|
|
155
|
+
_edgecolor_ma = []
|
|
156
|
+
|
|
157
|
+
_background = None
|
|
158
|
+
_creating_background = False
|
|
159
|
+
|
|
160
|
+
def __init__(self, config=SLIDERCONFIG):
|
|
161
|
+
self.CONFIG = config
|
|
162
|
+
super().__init__()
|
|
163
|
+
return
|
|
164
|
+
|
|
165
|
+
def refresh(self):
|
|
166
|
+
self._set_slider_artists()
|
|
167
|
+
super().refresh()
|
|
168
|
+
return
|
|
169
|
+
|