seolpyo-mplchart 0.1.3.1__py3-none-any.whl → 2.0.0.3__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 +164 -99
- seolpyo_mplchart/_base.py +117 -0
- seolpyo_mplchart/_chart/__init__.py +137 -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/test.py +121 -0
- seolpyo_mplchart/_config/__init__.py +3 -0
- seolpyo_mplchart/_config/ax.py +28 -0
- seolpyo_mplchart/_config/candle.py +30 -0
- seolpyo_mplchart/_config/config.py +21 -0
- seolpyo_mplchart/_config/cursor.py +49 -0
- seolpyo_mplchart/_config/figure.py +41 -0
- seolpyo_mplchart/_config/format.py +51 -0
- seolpyo_mplchart/_config/ma.py +15 -0
- seolpyo_mplchart/_config/slider/__init__.py +2 -0
- seolpyo_mplchart/_config/slider/config.py +24 -0
- seolpyo_mplchart/_config/slider/figure.py +20 -0
- seolpyo_mplchart/_config/slider/nav.py +9 -0
- seolpyo_mplchart/_config/unit.py +19 -0
- seolpyo_mplchart/_config/utils.py +67 -0
- seolpyo_mplchart/_config/volume.py +26 -0
- seolpyo_mplchart/_cursor.py +559 -0
- seolpyo_mplchart/_draw.py +634 -0
- seolpyo_mplchart/_slider.py +634 -0
- seolpyo_mplchart/base.py +70 -67
- seolpyo_mplchart/cursor.py +308 -271
- seolpyo_mplchart/draw.py +449 -237
- seolpyo_mplchart/slider.py +451 -396
- seolpyo_mplchart/test.py +173 -24
- seolpyo_mplchart/utils.py +15 -4
- seolpyo_mplchart/xl_to_dict.py +47 -0
- seolpyo_mplchart-2.0.0.3.dist-info/METADATA +710 -0
- seolpyo_mplchart-2.0.0.3.dist-info/RECORD +50 -0
- {seolpyo_mplchart-0.1.3.1.dist-info → seolpyo_mplchart-2.0.0.3.dist-info}/WHEEL +1 -1
- seolpyo_mplchart-0.1.3.1.dist-info/METADATA +0 -49
- seolpyo_mplchart-0.1.3.1.dist-info/RECORD +0 -13
- {seolpyo_mplchart-0.1.3.1.dist-info → seolpyo_mplchart-2.0.0.3.dist-info}/top_level.txt +0 -0
seolpyo_mplchart/test.py
CHANGED
|
@@ -1,38 +1,187 @@
|
|
|
1
|
-
import
|
|
1
|
+
import sys
|
|
2
2
|
from pathlib import Path
|
|
3
|
-
|
|
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
|
+
import tkinter as tk
|
|
11
|
+
from tkinter import filedialog
|
|
4
12
|
|
|
5
|
-
import matplotlib.pyplot as plt
|
|
6
13
|
import pandas as pd
|
|
14
|
+
import matplotlib.pyplot as plt
|
|
15
|
+
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
|
|
7
16
|
|
|
17
|
+
import seolpyo_mplchart as mc
|
|
18
|
+
from seolpyo_mplchart.xl_to_dict import convert
|
|
8
19
|
|
|
9
|
-
from seolpyo_mplchart import Chart
|
|
10
20
|
|
|
21
|
+
path_file = path_pkg / 'sample' / 'apple.txt'
|
|
22
|
+
with open(path_file, 'r', encoding='utf-8') as txt:
|
|
23
|
+
data = json.load(txt)
|
|
11
24
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
25
|
+
df = pd.DataFrame(data[:])
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test():
|
|
29
|
+
import seolpyo_mplchart as mc
|
|
30
|
+
class C(mc.CursorChart):
|
|
31
|
+
# fraction = True
|
|
32
|
+
# watermark = 0
|
|
33
|
+
theme = 'light'
|
|
34
|
+
limit_wick = 2000
|
|
35
|
+
# limit_ma = None
|
|
36
|
+
# candle_on_ma = False
|
|
37
|
+
# slider_top = False
|
|
38
|
+
def on_click(self, e):
|
|
39
|
+
super().on_click(e)
|
|
40
|
+
# print(f'{e.button=}')
|
|
41
|
+
# print(f'{e.button.__str__()=}')
|
|
42
|
+
if e.button.__str__() == '3':
|
|
43
|
+
# print('refresh')
|
|
44
|
+
if self.theme == 'light':
|
|
45
|
+
self.theme = 'dark'
|
|
46
|
+
self.CONFIG = mc.set_theme(mc.SLIDERCONFIG_EN, theme=self.theme)
|
|
47
|
+
# self.CONFIG.FIGURE.RATIO.price = 18
|
|
48
|
+
# self.CONFIG.FIGURE.RATIO.volume = 4
|
|
49
|
+
else:
|
|
50
|
+
self.theme = 'light'
|
|
51
|
+
self.CONFIG = mc.set_theme(mc.SLIDERCONFIG, theme=self.theme)
|
|
52
|
+
# self.CONFIG.FIGURE.RATIO.slider = 9
|
|
53
|
+
# self.CONFIG.FIGURE.RATIO.price = 9
|
|
54
|
+
# self.CONFIG.FIGURE.RATIO.volume = 9
|
|
55
|
+
self.CONFIG.UNIT.digit = 2
|
|
56
|
+
self.refresh()
|
|
57
|
+
return
|
|
58
|
+
# def set_segments(self):
|
|
59
|
+
# super().set_segments()
|
|
60
|
+
# self.collection_candle.set_linewidth(1.3)
|
|
61
|
+
# return
|
|
62
|
+
# def get_candle_segment(self, *, is_up, x, left, right, top, bottom, high, low):
|
|
63
|
+
# if is_up:
|
|
64
|
+
# return [
|
|
65
|
+
# (x, bottom),
|
|
66
|
+
# (x, low),
|
|
67
|
+
# (x, bottom),
|
|
68
|
+
# (left, bottom),
|
|
69
|
+
# (x, bottom),
|
|
70
|
+
# (x, top),
|
|
71
|
+
# (right, top),
|
|
72
|
+
# (x, top),
|
|
73
|
+
# (x, high),
|
|
74
|
+
# ]
|
|
75
|
+
# else:
|
|
76
|
+
# return [
|
|
77
|
+
# (x, bottom),
|
|
78
|
+
# (x, low),
|
|
79
|
+
# (x, bottom),
|
|
80
|
+
# (right, bottom),
|
|
81
|
+
# (x, bottom),
|
|
82
|
+
# (x, top),
|
|
83
|
+
# (left, top),
|
|
84
|
+
# (x, top),
|
|
85
|
+
# (x, high),
|
|
86
|
+
# ]
|
|
87
|
+
c = C()
|
|
88
|
+
c.key_date = '기준일'
|
|
89
|
+
c.key_open = '시가'
|
|
90
|
+
c.key_high = '고가'
|
|
91
|
+
c.key_low = '저가'
|
|
92
|
+
c.key_close = '종가'
|
|
93
|
+
c.key_volume = '거래량'
|
|
94
|
+
# c.volume = None
|
|
31
95
|
c.set_data(df)
|
|
96
|
+
|
|
32
97
|
plt.show()
|
|
98
|
+
return
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class TkChart:
|
|
102
|
+
chart = None
|
|
103
|
+
|
|
104
|
+
def __init__(self, window: tk.Tk):
|
|
105
|
+
window.wm_title('seolpyo tk chart')
|
|
106
|
+
self.window = window
|
|
107
|
+
# self.window.option_add('맑은고딕 14') # 모든 위젯 기본 폰트 크기 설정
|
|
108
|
+
window.protocol('WM_DELETE_WINDOW', lambda *_: (mc.close('all'), window.destroy()))
|
|
109
|
+
|
|
110
|
+
self.add_entry()
|
|
111
|
+
return
|
|
112
|
+
|
|
113
|
+
def open_file(self):
|
|
114
|
+
path_file = filedialog.askopenfilename(
|
|
115
|
+
title="Select a file",
|
|
116
|
+
filetypes=(("Xlsx Files", "*.xlsx"), ("All Files", "*.*"),)
|
|
117
|
+
)
|
|
118
|
+
# print(f'{path_file=}')
|
|
119
|
+
if path_file:
|
|
120
|
+
self.filname.config(state="normal") # 잠깐 풀고
|
|
121
|
+
self.filname.delete(0, tk.END)
|
|
122
|
+
self.filname.insert(0, path_file)
|
|
123
|
+
self.filname.config(state="readonly") # 다시 잠금
|
|
124
|
+
|
|
125
|
+
if not self.chart:
|
|
126
|
+
self.add_chart()
|
|
127
|
+
self.set_chart(path_file)
|
|
128
|
+
return path_file
|
|
129
|
+
return
|
|
33
130
|
|
|
131
|
+
def add_entry(self):
|
|
132
|
+
frame = tk.Frame(self.window)
|
|
133
|
+
frame.grid(column=0, row=0, sticky='w', padx=10, pady=10)
|
|
134
|
+
|
|
135
|
+
btn = tk.Button(frame, text='파일 열기', command=lambda *_: self.open_file())
|
|
136
|
+
btn.grid(column=0, row=0)
|
|
137
|
+
|
|
138
|
+
self.filname = tk.Entry(frame, state='readonly', width=100)
|
|
139
|
+
self.filname.grid(column=1, row=0, padx=10)
|
|
140
|
+
return
|
|
141
|
+
|
|
142
|
+
def add_chart(self):
|
|
143
|
+
self.chart = mc.SliderChart()
|
|
144
|
+
self.chart.key_date = '기준일'
|
|
145
|
+
self.chart.key_open = '시가'
|
|
146
|
+
self.chart.key_high = '고가'
|
|
147
|
+
self.chart.key_low = '저가'
|
|
148
|
+
self.chart.key_close = '종가'
|
|
149
|
+
self.chart.key_volume = '거래량'
|
|
150
|
+
|
|
151
|
+
frame = tk.Frame(self.window)
|
|
152
|
+
frame.grid(column=0, row=1, sticky='ewsn')
|
|
153
|
+
self.window.columnconfigure(0, weight=1)
|
|
154
|
+
self.window.rowconfigure(1, weight=1)
|
|
155
|
+
|
|
156
|
+
self.agg = FigureCanvasTkAgg(self.chart.figure, frame)
|
|
157
|
+
widget = self.agg.get_tk_widget()
|
|
158
|
+
widget.grid(column=0, row=0, sticky='ewsn')
|
|
159
|
+
frame.columnconfigure(0, weight=1)
|
|
160
|
+
frame.rowconfigure(0, weight=1)
|
|
161
|
+
|
|
162
|
+
return
|
|
163
|
+
|
|
164
|
+
def set_chart(self, path_file):
|
|
165
|
+
data = convert(path_file)
|
|
166
|
+
print(f'{len(data)=:,}')
|
|
167
|
+
df = pd.DataFrame(data)
|
|
168
|
+
self.chart.set_data(df)
|
|
169
|
+
return
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def run():
|
|
173
|
+
root = tk.Tk()
|
|
174
|
+
_ = TkChart(root)
|
|
175
|
+
root.mainloop()
|
|
176
|
+
return
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def test_tk():
|
|
180
|
+
run()
|
|
34
181
|
return
|
|
35
182
|
|
|
36
183
|
|
|
37
184
|
if __name__ == '__main__':
|
|
38
|
-
|
|
185
|
+
test()
|
|
186
|
+
# test_tk()
|
|
187
|
+
|
seolpyo_mplchart/utils.py
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
from re import search
|
|
2
2
|
|
|
3
|
+
|
|
3
4
|
def convert_num(num):
|
|
4
5
|
if isinstance(num, float) and num % 1: return num
|
|
5
6
|
return int(num)
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
def float_to_str(num: float, digit=0, plus=False):
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
if 0 < digit:
|
|
11
|
+
num.__round__(digit)
|
|
12
|
+
text = f'{num:+,.{digit}f}' if plus else f'{num:,.{digit}f}'
|
|
13
|
+
else:
|
|
14
|
+
num = round(num, digit).__int__()
|
|
15
|
+
text = f'{num:+,}' if plus else f'{num:,}'
|
|
11
16
|
return text
|
|
12
17
|
|
|
13
18
|
|
|
@@ -16,7 +21,6 @@ dict_unit = {
|
|
|
16
21
|
'조': 1_000_000_000_000,
|
|
17
22
|
'억': 100_000_000,
|
|
18
23
|
'만': 10_000,
|
|
19
|
-
'천': 1_000,
|
|
20
24
|
}
|
|
21
25
|
dict_unit_en = {
|
|
22
26
|
'Qd': 1_000_000_000_000_000,
|
|
@@ -26,15 +30,20 @@ dict_unit_en = {
|
|
|
26
30
|
'K': 1_000,
|
|
27
31
|
}
|
|
28
32
|
|
|
33
|
+
|
|
29
34
|
def convert_unit(value, digit=0, word='원'):
|
|
35
|
+
# print(f'{value=:,}')
|
|
30
36
|
v = abs(value)
|
|
31
37
|
du = dict_unit if search('[가-힣]', word) else dict_unit_en
|
|
32
38
|
for unit, n in du.items():
|
|
33
39
|
if n <= v:
|
|
40
|
+
# print(f'{n=:,}')
|
|
41
|
+
# print(f'{unit=}')
|
|
34
42
|
num = value / n
|
|
35
43
|
return f'{float_to_str(num, digit)}{unit} {word}'
|
|
36
44
|
if not value % 1: value = int(value)
|
|
37
45
|
text = f'{float_to_str(value, digit)}{word}'
|
|
46
|
+
# print(f'{text=}')
|
|
38
47
|
return text
|
|
39
48
|
|
|
40
49
|
|
|
@@ -42,4 +51,6 @@ if __name__ == '__main__':
|
|
|
42
51
|
a = 456.123
|
|
43
52
|
print(float_to_str(a))
|
|
44
53
|
print(float_to_str(a, 2))
|
|
45
|
-
print(float_to_str(a, 6))
|
|
54
|
+
print(float_to_str(a, 6))
|
|
55
|
+
|
|
56
|
+
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from datetime import datetime, timedelta
|
|
2
|
+
from re import findall
|
|
3
|
+
from zipfile import ZipFile
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
base_date = datetime(1899, 12, 30).date()
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def convert(path_file: str, sheet=1, row=4, date='A', Open='B', high='C', low='D', close='E', volume='F'):
|
|
10
|
+
"xlsx 파일에서 주가 정보를 추출합니다."
|
|
11
|
+
zipfile = ZipFile(path_file)
|
|
12
|
+
# print(f'{zipfile.filelist=}')
|
|
13
|
+
a = zipfile.read(f'xl/worksheets/sheet{sheet}.xml')
|
|
14
|
+
# print(f'{a=}')
|
|
15
|
+
# print(f'{type(a)=}')
|
|
16
|
+
b = a.decode('utf-8')
|
|
17
|
+
list_price: list[dict[str, str|int]] = []
|
|
18
|
+
for i in findall('<row.+?</row>', b)[row:]:
|
|
19
|
+
# print()
|
|
20
|
+
# print(f'{i=}')
|
|
21
|
+
dt = findall(f'<c r="{date}[0-9].+?<v>([0-9]+)</v>', i)
|
|
22
|
+
c = findall(f'<c r="{close}[0-9].+?<v>([0-9\.]+)</v>', i)
|
|
23
|
+
o = findall(f'<c r="{Open}[0-9].+?<v>([0-9\.]+)</v>', i)
|
|
24
|
+
h = findall(f'<c r="{high}[0-9].+?<v>([0-9\.]+)</v>', i)
|
|
25
|
+
l = findall(f'<c r="{low}[0-9].+?<v>([0-9\.]+)</v>', i)
|
|
26
|
+
v = findall(f'<c r="{volume}[0-9].+?<v>([0-9]+)</v>', i)
|
|
27
|
+
# print(f'{(dt, c, o, h, l, v)=}')
|
|
28
|
+
if not all([dt, o, h, l, c,]):
|
|
29
|
+
continue
|
|
30
|
+
try:
|
|
31
|
+
dt = base_date + timedelta(int(dt[0]))
|
|
32
|
+
c = float(c[0])
|
|
33
|
+
o = float(o[0])
|
|
34
|
+
h = float(h[0])
|
|
35
|
+
l = float(l[0])
|
|
36
|
+
except:
|
|
37
|
+
continue
|
|
38
|
+
try:
|
|
39
|
+
v = float(v[0])
|
|
40
|
+
except:
|
|
41
|
+
v = 0
|
|
42
|
+
# print(f'{(dt, c, o, h, l, v)=}')
|
|
43
|
+
# if 2020 < dt.year and dt.year < 2024: list_price.append({'기준일': f'{dt}', '종가': c, '시가': o, '고가': h, '저가': l, '거래량': v,})
|
|
44
|
+
list_price.append({'기준일': f'{dt}', '종가': c, '시가': o, '고가': h, '저가': l, '거래량': v,})
|
|
45
|
+
# for i in enumerate(list_price, 1): print(f' {i}')
|
|
46
|
+
return sorted(list_price, key=lambda x: x['기준일'])
|
|
47
|
+
|