seolpyo-mplchart 1.4.1__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 +53 -333
- seolpyo_mplchart/_chart/__init__.py +145 -0
- seolpyo_mplchart/_chart/_base.py +217 -0
- seolpyo_mplchart/_chart/_cursor/__init__.py +2 -0
- seolpyo_mplchart/_chart/_cursor/_artist.py +217 -0
- seolpyo_mplchart/_chart/_cursor/_cursor.py +165 -0
- seolpyo_mplchart/_chart/_cursor/_info.py +187 -0
- seolpyo_mplchart/_chart/_draw/__init__.py +2 -0
- seolpyo_mplchart/_chart/_draw/_artist.py +50 -0
- seolpyo_mplchart/_chart/_draw/_data.py +314 -0
- seolpyo_mplchart/_chart/_draw/_draw.py +103 -0
- seolpyo_mplchart/_chart/_draw/_lim.py +265 -0
- seolpyo_mplchart/_chart/_slider/__init__.py +1 -0
- seolpyo_mplchart/_chart/_slider/_base.py +268 -0
- seolpyo_mplchart/_chart/_slider/_data.py +105 -0
- seolpyo_mplchart/_chart/_slider/_mouse.py +176 -0
- seolpyo_mplchart/_chart/_slider/_nav.py +204 -0
- 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/_chart/test.py +121 -0
- seolpyo_mplchart/_config/__init__.py +3 -0
- seolpyo_mplchart/_config/ax.py +28 -0
- seolpyo_mplchart/_config/candle.py +31 -0
- seolpyo_mplchart/_config/config.py +21 -0
- seolpyo_mplchart/_config/cursor.py +49 -0
- seolpyo_mplchart/_config/figure.py +40 -0
- seolpyo_mplchart/_config/format.py +51 -0
- seolpyo_mplchart/_config/ma.py +17 -0
- seolpyo_mplchart/_config/slider/__init__.py +2 -0
- seolpyo_mplchart/_config/slider/config.py +24 -0
- seolpyo_mplchart/_config/slider/figure.py +19 -0
- seolpyo_mplchart/_config/slider/nav.py +10 -0
- seolpyo_mplchart/_config/unit.py +19 -0
- seolpyo_mplchart/_config/utils.py +67 -0
- seolpyo_mplchart/_config/volume.py +27 -0
- seolpyo_mplchart/_cursor.py +27 -25
- seolpyo_mplchart/_draw.py +7 -18
- seolpyo_mplchart/_slider.py +26 -20
- 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 +172 -56
- seolpyo_mplchart/xl_to_dict.py +47 -0
- seolpyo_mplchart-2.1.0.dist-info/METADATA +718 -0
- seolpyo_mplchart-2.1.0.dist-info/RECORD +89 -0
- {seolpyo_mplchart-1.4.1.dist-info → seolpyo_mplchart-2.1.0.dist-info}/WHEEL +1 -1
- seolpyo_mplchart-1.4.1.dist-info/METADATA +0 -57
- seolpyo_mplchart-1.4.1.dist-info/RECORD +0 -17
- {seolpyo_mplchart-1.4.1.dist-info → seolpyo_mplchart-2.1.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
from matplotlib.axes import Axes
|
|
2
|
+
import numpy as np
|
|
3
|
+
import pandas as pd
|
|
4
|
+
|
|
5
|
+
from ..._config import ConfigData
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Base:
|
|
9
|
+
CONFIG: ConfigData
|
|
10
|
+
df: pd.DataFrame
|
|
11
|
+
|
|
12
|
+
key_date = 'date'
|
|
13
|
+
key_open, key_high, key_low, key_close = ('open', 'high', 'low', 'close')
|
|
14
|
+
key_volume = 'volume'
|
|
15
|
+
|
|
16
|
+
ax_price: Axes
|
|
17
|
+
index_list: list[int] = []
|
|
18
|
+
|
|
19
|
+
set_segments: callable
|
|
20
|
+
axis: callable
|
|
21
|
+
|
|
22
|
+
def get_default_xlim(self):
|
|
23
|
+
"""
|
|
24
|
+
get_default_xlim.
|
|
25
|
+
|
|
26
|
+
space = int(self.index_list[-1] / 20)
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
(int, int): (-space, self.index_list[-1]+space)
|
|
30
|
+
"""
|
|
31
|
+
# print(f'{self.index_list[-1]=}')
|
|
32
|
+
space = int(self.index_list[-1] / 20)
|
|
33
|
+
return (-space, self.index_list[-1]+space)
|
|
34
|
+
|
|
35
|
+
def set_variables(self):
|
|
36
|
+
self.index_list.clear()
|
|
37
|
+
self.index_list = self.df.index.tolist()
|
|
38
|
+
self.xmin, self.xmax = (0, self.index_list[-1])
|
|
39
|
+
|
|
40
|
+
self.chart_price_ymax = round(self.df['high'].max() * 1.3, self.CONFIG.UNIT.digit+2)
|
|
41
|
+
if self.key_volume:
|
|
42
|
+
self.chart_volume_ymax = round(self.df['volume'].max() * 1.3, self.CONFIG.UNIT.digit_volume+2)
|
|
43
|
+
else:
|
|
44
|
+
self.chart_volume_ymax = 10
|
|
45
|
+
|
|
46
|
+
if not self.CONFIG.MA.ma_list:
|
|
47
|
+
self.CONFIG.MA.ma_list = []
|
|
48
|
+
else:
|
|
49
|
+
self.CONFIG.MA.ma_list = sorted(self.CONFIG.MA.ma_list)
|
|
50
|
+
|
|
51
|
+
return
|
|
52
|
+
|
|
53
|
+
def set_data(self, df: pd.DataFrame, *, change_xlim=True):
|
|
54
|
+
"""
|
|
55
|
+
`if change_xlim`: change xlim with `self.get_default_xlim()` value
|
|
56
|
+
|
|
57
|
+
`if not change_xlim`: Keep the current xlim
|
|
58
|
+
"""
|
|
59
|
+
self.df = self._convert_df(df)
|
|
60
|
+
|
|
61
|
+
self._add_columns()
|
|
62
|
+
# print(f'{self.df.columns=}')
|
|
63
|
+
|
|
64
|
+
self.set_variables()
|
|
65
|
+
|
|
66
|
+
self.set_segments()
|
|
67
|
+
|
|
68
|
+
if change_xlim:
|
|
69
|
+
xmin, xmax = self.get_default_xlim()
|
|
70
|
+
self.axis(xmin, xmax=xmax)
|
|
71
|
+
return
|
|
72
|
+
|
|
73
|
+
def _convert_df(self, df: pd.DataFrame):
|
|
74
|
+
keys = {
|
|
75
|
+
self.key_date: 'date',
|
|
76
|
+
self.key_open: 'open',
|
|
77
|
+
self.key_high: 'high',
|
|
78
|
+
self.key_low: 'low',
|
|
79
|
+
self.key_close: 'close',
|
|
80
|
+
self.key_volume: 'volume',
|
|
81
|
+
}
|
|
82
|
+
df.rename(columns=keys, inplace=True)
|
|
83
|
+
|
|
84
|
+
# df column 추출
|
|
85
|
+
if self.key_volume:
|
|
86
|
+
df = df[['date', 'open', 'high', 'low', 'close', 'volume']].copy()
|
|
87
|
+
else:
|
|
88
|
+
df = df[['date', 'open', 'high', 'low', 'close',]].copy()
|
|
89
|
+
df['volume'] = 0
|
|
90
|
+
df.loc[:, 'ymax_volume'] = df['volume'] * 1.2
|
|
91
|
+
|
|
92
|
+
# 오름차순 정렬
|
|
93
|
+
df = df.sort_values(['date'])
|
|
94
|
+
df = df.reset_index(drop=True)
|
|
95
|
+
|
|
96
|
+
return df
|
|
97
|
+
|
|
98
|
+
def _add_columns(self):
|
|
99
|
+
# 전일 종가 추가
|
|
100
|
+
self.df['pre_close'] = self.df['close'].shift(1).fillna(0)
|
|
101
|
+
# 거래정지인 경우 전일종가 적용
|
|
102
|
+
self.df.loc[self.df['close'] == 0, 'close'] = self.df['pre_close']
|
|
103
|
+
# 종가만 유효한 경우 종가로 통일
|
|
104
|
+
self.df.loc[(self.df['close'] != 0) & (self.df['open'] == 0), ['open', 'high', 'low']] = self.df['close']
|
|
105
|
+
|
|
106
|
+
# 가격이동평균선 계산
|
|
107
|
+
for ma in self.CONFIG.MA.ma_list:
|
|
108
|
+
self.df[f'ma{ma}'] = self.df['close'].rolling(ma).mean()
|
|
109
|
+
|
|
110
|
+
# 세그먼트 생성을 위한 column 추가
|
|
111
|
+
self.df['x'] = self.df.index + 0.5
|
|
112
|
+
self.df['left_candle'] = self.df['x'] - self.CONFIG.CANDLE.half_width
|
|
113
|
+
self.df['right_candle'] = self.df['x'] + self.CONFIG.CANDLE.half_width
|
|
114
|
+
self.df['left_volume'] = self.df['x'] - self.CONFIG.VOLUME.half_width
|
|
115
|
+
self.df['right_volume'] = self.df['x'] + self.CONFIG.VOLUME.half_width
|
|
116
|
+
self.df['zero'] = 0
|
|
117
|
+
|
|
118
|
+
self.df['is_up'] = np.where(self.df['open'] < self.df['close'], True, False)
|
|
119
|
+
self.df['top_candle'] = np.where(self.df['is_up'], self.df['close'], self.df['open'])
|
|
120
|
+
self.df['bottom_candle'] = np.where(self.df['is_up'], self.df['open'], self.df['close'])
|
|
121
|
+
|
|
122
|
+
return
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class DataMixin(Base):
|
|
126
|
+
key_date = 'date'
|
|
127
|
+
key_open, key_high, key_low, key_close = ('open', 'high', 'low', 'close')
|
|
128
|
+
key_volume = 'volume'
|
|
129
|
+
|
|
130
|
+
index_list: list[int] = []
|
|
131
|
+
|
|
132
|
+
df: pd.DataFrame
|
|
133
|
+
|
|
134
|
+
chart_price_ymax: float
|
|
135
|
+
chart_volume_ymax: float
|
|
136
|
+
xmin: int
|
|
137
|
+
xmax: int
|
|
138
|
+
|
|
@@ -0,0 +1,58 @@
|
|
|
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
|
+
from seolpyo_mplchart._utils.theme import set_theme
|
|
15
|
+
from seolpyo_mplchart._chart.base import Chart
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
path_file = path_pkg / 'sample' / 'samsung.txt'
|
|
19
|
+
with open(path_file, 'r', encoding='utf-8') as txt:
|
|
20
|
+
data = json.load(txt)
|
|
21
|
+
df = pd.DataFrame(data[:])
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class C(Chart):
|
|
25
|
+
# limit_wick = 200
|
|
26
|
+
t = 'light'
|
|
27
|
+
# watermark = ''
|
|
28
|
+
def __init__(self):
|
|
29
|
+
super().__init__()
|
|
30
|
+
self.figure.canvas.mpl_connect('button_press_event', lambda x: self.theme(x))
|
|
31
|
+
|
|
32
|
+
def theme(self, e):
|
|
33
|
+
btn = getattr(e, 'button')
|
|
34
|
+
# print(f'{str(btn)=}')
|
|
35
|
+
if str(btn) == '3':
|
|
36
|
+
# print('refresh')
|
|
37
|
+
if self.t == 'light':
|
|
38
|
+
self.t = 'dark'
|
|
39
|
+
else:
|
|
40
|
+
self.t = 'light'
|
|
41
|
+
# print(f'{self.t=}')
|
|
42
|
+
self.CONFIG = set_theme(self.CONFIG, theme=self.t)
|
|
43
|
+
self.refresh()
|
|
44
|
+
return
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def run():
|
|
48
|
+
chart = C()
|
|
49
|
+
chart.set_data(df)
|
|
50
|
+
plt.show()
|
|
51
|
+
plt.close()
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
if __name__ == '__main__':
|
|
56
|
+
run()
|
|
57
|
+
|
|
58
|
+
|
|
@@ -0,0 +1,125 @@
|
|
|
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 ..base import Chart as BaseChart, Figure, ConfigData
|
|
9
|
+
|
|
10
|
+
from .b_artist import ArtistMixin
|
|
11
|
+
from .c_draw import DrawMixin
|
|
12
|
+
from .d_segment import SegmentMixin
|
|
13
|
+
from .e_axis import AxisMixin
|
|
14
|
+
from .g_event import EventMixin
|
|
15
|
+
from .h_data import DataMixin
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class CursorMixin(
|
|
19
|
+
ArtistMixin,
|
|
20
|
+
DrawMixin,
|
|
21
|
+
SegmentMixin,
|
|
22
|
+
AxisMixin,
|
|
23
|
+
EventMixin,
|
|
24
|
+
DataMixin
|
|
25
|
+
):
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class Chart(CursorMixin, BaseChart):
|
|
30
|
+
limit_candle = 400
|
|
31
|
+
limit_wick = 2_000
|
|
32
|
+
candle_on_ma = True
|
|
33
|
+
fraction = False
|
|
34
|
+
|
|
35
|
+
key_date = 'date'
|
|
36
|
+
key_open, key_high, key_low, key_close = ('open', 'high', 'low', 'close')
|
|
37
|
+
key_volume = 'volume'
|
|
38
|
+
|
|
39
|
+
index_list: list[int] = []
|
|
40
|
+
|
|
41
|
+
df: pd.DataFrame
|
|
42
|
+
|
|
43
|
+
CONFIG: ConfigData
|
|
44
|
+
|
|
45
|
+
figure: Figure
|
|
46
|
+
ax_legend: Axes
|
|
47
|
+
ax_price: Axes
|
|
48
|
+
ax_volume: Axes
|
|
49
|
+
|
|
50
|
+
artist_watermark: Text
|
|
51
|
+
collection_candle: LineCollection
|
|
52
|
+
collection_volume: LineCollection
|
|
53
|
+
collection_ma: LineCollection
|
|
54
|
+
|
|
55
|
+
in_chart = False
|
|
56
|
+
in_chart_price = False
|
|
57
|
+
in_chart_volume = False
|
|
58
|
+
|
|
59
|
+
in_candle = False
|
|
60
|
+
in_volume = False
|
|
61
|
+
|
|
62
|
+
collection_price_crossline: LineCollection
|
|
63
|
+
collection_volume_crossline: LineCollection
|
|
64
|
+
|
|
65
|
+
artist_label_x: Text = None
|
|
66
|
+
artist_label_y: Text = None
|
|
67
|
+
|
|
68
|
+
collection_box_price: LineCollection
|
|
69
|
+
collection_box_volume: LineCollection
|
|
70
|
+
|
|
71
|
+
artist_info_candle: Text
|
|
72
|
+
artist_info_volume: Text
|
|
73
|
+
|
|
74
|
+
###
|
|
75
|
+
|
|
76
|
+
segment_volume: np.ndarray
|
|
77
|
+
segment_volume_wick: np.ndarray
|
|
78
|
+
facecolor_volume: np.ndarray
|
|
79
|
+
edgecolor_volume: np.ndarray
|
|
80
|
+
|
|
81
|
+
segment_candle: np.ndarray
|
|
82
|
+
segment_candle_wick: np.ndarray
|
|
83
|
+
segment_priceline: np.ndarray
|
|
84
|
+
facecolor_candle: np.ndarray
|
|
85
|
+
edgecolor_candle: np.ndarray
|
|
86
|
+
|
|
87
|
+
segment_ma: np.ndarray
|
|
88
|
+
edgecolor_ma: np.ndarray
|
|
89
|
+
|
|
90
|
+
price_ymin: int
|
|
91
|
+
price_ymax: int
|
|
92
|
+
volume_ymax: int
|
|
93
|
+
|
|
94
|
+
chart_price_ymax: float
|
|
95
|
+
chart_volume_ymax: float
|
|
96
|
+
|
|
97
|
+
vxmin: int
|
|
98
|
+
vxmax: int
|
|
99
|
+
|
|
100
|
+
v0: int
|
|
101
|
+
v1: int
|
|
102
|
+
vmiddle: int
|
|
103
|
+
|
|
104
|
+
min_height_box_candle: float
|
|
105
|
+
min_height_box_volume: float
|
|
106
|
+
|
|
107
|
+
###
|
|
108
|
+
|
|
109
|
+
_visible_ma: set[int] = set()
|
|
110
|
+
_edgecolor_ma = []
|
|
111
|
+
|
|
112
|
+
_background = None
|
|
113
|
+
_background_background = None
|
|
114
|
+
_creating_background = False
|
|
115
|
+
|
|
116
|
+
_length_text: int
|
|
117
|
+
|
|
118
|
+
_in_mouse_move = False
|
|
119
|
+
|
|
120
|
+
def refresh(self):
|
|
121
|
+
super().refresh()
|
|
122
|
+
|
|
123
|
+
self._set_length_text()
|
|
124
|
+
return
|
|
125
|
+
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
from matplotlib.axes import Axes
|
|
2
|
+
from matplotlib.collections import LineCollection
|
|
3
|
+
from matplotlib.text import Text
|
|
4
|
+
|
|
5
|
+
from ..._config import ConfigData
|
|
6
|
+
from ..base.a_canvas import Figure
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Base:
|
|
10
|
+
CONFIG: ConfigData
|
|
11
|
+
|
|
12
|
+
figure: Figure
|
|
13
|
+
ax_price: Axes
|
|
14
|
+
ax_volume: Axes
|
|
15
|
+
|
|
16
|
+
add_artists: callable
|
|
17
|
+
set_artists: callable
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class CrossLineMixin(Base):
|
|
21
|
+
def _add_crosslines(self):
|
|
22
|
+
kwargs = {'segments': [], 'animated': True}
|
|
23
|
+
|
|
24
|
+
self.collection_price_crossline = LineCollection(**kwargs)
|
|
25
|
+
self.ax_price.add_artist(self.collection_price_crossline)
|
|
26
|
+
|
|
27
|
+
self.collection_volume_crossline = LineCollection(**kwargs)
|
|
28
|
+
self.ax_volume.add_artist(self.collection_volume_crossline)
|
|
29
|
+
return
|
|
30
|
+
|
|
31
|
+
def _set_crosslines(self):
|
|
32
|
+
kwargs = self.CONFIG.CURSOR.CROSSLINE.__dict__
|
|
33
|
+
kwargs.update({'segments': [], 'animated': True})
|
|
34
|
+
|
|
35
|
+
self.collection_price_crossline.set(**kwargs)
|
|
36
|
+
self.collection_volume_crossline.set(**kwargs)
|
|
37
|
+
return
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class LabelMixin(Base):
|
|
41
|
+
def _add_artist_labels(self):
|
|
42
|
+
kwargs = {'text': '', 'animated': True,}
|
|
43
|
+
|
|
44
|
+
self.artist_label_x = Text(**kwargs)
|
|
45
|
+
self.figure.add_artist(self.artist_label_x)
|
|
46
|
+
|
|
47
|
+
self.artist_label_y = Text(**kwargs)
|
|
48
|
+
self.figure.add_artist(self.artist_label_y)
|
|
49
|
+
return
|
|
50
|
+
|
|
51
|
+
def _set_artist_labels(self):
|
|
52
|
+
kwargs = self.CONFIG.CURSOR.TEXT.to_dict()
|
|
53
|
+
kwargs.update({'text': ' ', 'animated': True, 'horizontalalignment': 'center', 'verticalalignment': 'center', 'clip_on':True})
|
|
54
|
+
|
|
55
|
+
self.artist_label_x.set(**kwargs)
|
|
56
|
+
self.artist_label_y.set(**kwargs)
|
|
57
|
+
return
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class BoxMixin(Base):
|
|
61
|
+
def _add_box_collections(self):
|
|
62
|
+
kwargs = {'segments': [], 'animated': True,}
|
|
63
|
+
|
|
64
|
+
self.collection_box_price = LineCollection(**kwargs)
|
|
65
|
+
self.ax_price.add_artist(self.collection_box_price)
|
|
66
|
+
self.collection_box_volume = LineCollection(**kwargs)
|
|
67
|
+
self.ax_volume.add_artist(self.collection_box_volume)
|
|
68
|
+
return
|
|
69
|
+
|
|
70
|
+
def _set_box_collections(self):
|
|
71
|
+
kwargs = self.CONFIG.CURSOR.BOX.__dict__
|
|
72
|
+
kwargs.update({'segments': [], 'animated': True,})
|
|
73
|
+
|
|
74
|
+
self.collection_box_price.set(**kwargs)
|
|
75
|
+
self.collection_box_volume.set(**kwargs)
|
|
76
|
+
return
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class InfoMixin(Base):
|
|
80
|
+
def _add_info_texts(self):
|
|
81
|
+
kwargs = {'text': '', 'animated': True, 'horizontalalignment': 'left', 'verticalalignment': 'top',}
|
|
82
|
+
|
|
83
|
+
self.artist_info_candle = Text(**kwargs)
|
|
84
|
+
self.ax_price.add_artist(self.artist_info_candle)
|
|
85
|
+
self.artist_info_volume = Text(**kwargs)
|
|
86
|
+
self.ax_volume.add_artist(self.artist_info_volume)
|
|
87
|
+
return
|
|
88
|
+
|
|
89
|
+
def _set_info_texts(self):
|
|
90
|
+
kwargs = self.CONFIG.CURSOR.TEXT.to_dict()
|
|
91
|
+
kwargs.update({'text': '', 'animated': True, 'horizontalalignment': 'left', 'verticalalignment': 'top',})
|
|
92
|
+
|
|
93
|
+
self.artist_info_candle.set(**kwargs)
|
|
94
|
+
self.artist_info_volume.set(**kwargs)
|
|
95
|
+
return
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class ArtistMixin(CrossLineMixin, LabelMixin, BoxMixin, InfoMixin):
|
|
99
|
+
collection_price_crossline: LineCollection
|
|
100
|
+
collection_volume_crossline: LineCollection
|
|
101
|
+
|
|
102
|
+
artist_label_x: Text = None
|
|
103
|
+
artist_label_y: Text = None
|
|
104
|
+
|
|
105
|
+
collection_box_price: LineCollection
|
|
106
|
+
collection_box_volume: LineCollection
|
|
107
|
+
|
|
108
|
+
artist_info_candle: Text
|
|
109
|
+
artist_info_volume: Text
|
|
110
|
+
|
|
111
|
+
def add_artists(self):
|
|
112
|
+
super().add_artists()
|
|
113
|
+
|
|
114
|
+
self._add_crosslines()
|
|
115
|
+
self._add_artist_labels()
|
|
116
|
+
self._add_box_collections()
|
|
117
|
+
self._add_info_texts()
|
|
118
|
+
|
|
119
|
+
self.set_artists()
|
|
120
|
+
return
|
|
121
|
+
|
|
122
|
+
def set_artists(self):
|
|
123
|
+
super().set_artists()
|
|
124
|
+
|
|
125
|
+
self._set_crosslines()
|
|
126
|
+
self._set_artist_labels()
|
|
127
|
+
self._set_box_collections()
|
|
128
|
+
self._set_info_texts()
|
|
129
|
+
return
|
|
130
|
+
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
from matplotlib.axes import Axes
|
|
2
|
+
from matplotlib.collections import LineCollection
|
|
3
|
+
from matplotlib.text import Text
|
|
4
|
+
import pandas as pd
|
|
5
|
+
|
|
6
|
+
from ..._config import ConfigData
|
|
7
|
+
from ..._chart.base.a_canvas import Figure
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Base:
|
|
11
|
+
CONFIG: ConfigData
|
|
12
|
+
|
|
13
|
+
df: pd.DataFrame
|
|
14
|
+
|
|
15
|
+
watermark: str
|
|
16
|
+
|
|
17
|
+
figure: Figure
|
|
18
|
+
ax_legend: Axes
|
|
19
|
+
ax_price: Axes
|
|
20
|
+
ax_volume: Axes
|
|
21
|
+
artist_watermark: Text
|
|
22
|
+
collection_candle: LineCollection
|
|
23
|
+
collection_volume: LineCollection
|
|
24
|
+
collection_ma: LineCollection
|
|
25
|
+
|
|
26
|
+
collection_price_crossline: LineCollection
|
|
27
|
+
collection_volume_crossline: LineCollection
|
|
28
|
+
|
|
29
|
+
artist_label_x: Text
|
|
30
|
+
artist_label_y: Text
|
|
31
|
+
|
|
32
|
+
collection_box_price: LineCollection
|
|
33
|
+
collection_box_volume: LineCollection
|
|
34
|
+
|
|
35
|
+
artist_info_candle: Text
|
|
36
|
+
artist_info_volume: Text
|
|
37
|
+
|
|
38
|
+
in_chart_price: bool
|
|
39
|
+
in_chart_volume: bool
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class CrosslineMixin(Base):
|
|
43
|
+
def _draw_crossline(self):
|
|
44
|
+
renderer = self.figure.canvas.renderer
|
|
45
|
+
for artist in [
|
|
46
|
+
self.collection_price_crossline,
|
|
47
|
+
self.collection_volume_crossline,
|
|
48
|
+
]:
|
|
49
|
+
artist.draw(renderer)
|
|
50
|
+
return
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class LabelMixin(Base):
|
|
54
|
+
def _draw_label_x(self):
|
|
55
|
+
artist = self.artist_label_x
|
|
56
|
+
renderer = self.figure.canvas.renderer
|
|
57
|
+
|
|
58
|
+
artist.draw(renderer)
|
|
59
|
+
# print(f'{artist.get_position()=}')
|
|
60
|
+
return
|
|
61
|
+
|
|
62
|
+
def _draw_label_y(self):
|
|
63
|
+
artist = self.artist_label_y
|
|
64
|
+
renderer = self.figure.canvas.renderer
|
|
65
|
+
|
|
66
|
+
artist.draw(renderer)
|
|
67
|
+
return
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class BoxMixin(Base):
|
|
71
|
+
def _draw_box_candle(self):
|
|
72
|
+
renderer = self.figure.canvas.renderer
|
|
73
|
+
self.collection_box_price.draw(renderer)
|
|
74
|
+
return
|
|
75
|
+
|
|
76
|
+
def _draw_box_volume(self):
|
|
77
|
+
renderer = self.figure.canvas.renderer
|
|
78
|
+
self.collection_box_volume.draw(renderer)
|
|
79
|
+
return
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class InfoMixin(Base):
|
|
83
|
+
def _draw_info_candle(self):
|
|
84
|
+
renderer = self.figure.canvas.renderer
|
|
85
|
+
self.artist_info_candle.draw(renderer)
|
|
86
|
+
return
|
|
87
|
+
|
|
88
|
+
def _draw_info_volume(self):
|
|
89
|
+
renderer = self.figure.canvas.renderer
|
|
90
|
+
self.artist_info_volume.draw(renderer)
|
|
91
|
+
return
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class DrawMixin(CrosslineMixin, LabelMixin, BoxMixin, InfoMixin):
|
|
95
|
+
pass
|
|
96
|
+
|