analyser_hj3415 2.9.8__tar.gz → 2.9.10__tar.gz
Sign up to get free protection for your applications and to get access to all the features.
- analyser_hj3415-2.9.10/.gitignore +6 -0
- {analyser_hj3415-2.9.8 → analyser_hj3415-2.9.10}/PKG-INFO +5 -3
- {analyser_hj3415-2.9.8 → analyser_hj3415-2.9.10}/analyser_hj3415/cli.py +21 -32
- {analyser_hj3415-2.9.8 → analyser_hj3415-2.9.10}/analyser_hj3415/tsa.py +12 -15
- {analyser_hj3415-2.9.8 → analyser_hj3415-2.9.10}/pyproject.toml +8 -3
- analyser_hj3415-2.9.8/.DS_Store +0 -0
- analyser_hj3415-2.9.8/analyser_hj3415/.DS_Store +0 -0
- {analyser_hj3415-2.9.8 → analyser_hj3415-2.9.10}/README.md +0 -0
- {analyser_hj3415-2.9.8 → analyser_hj3415-2.9.10}/analyser_hj3415/__init__.py +0 -0
- {analyser_hj3415-2.9.8 → analyser_hj3415-2.9.10}/analyser_hj3415/eval.py +0 -0
- {analyser_hj3415-2.9.8 → analyser_hj3415-2.9.10}/analyser_hj3415/workroom/__init__.py +0 -0
- {analyser_hj3415-2.9.8 → analyser_hj3415-2.9.10}/analyser_hj3415/workroom/mysklearn.py +0 -0
- {analyser_hj3415-2.9.8 → analyser_hj3415-2.9.10}/analyser_hj3415/workroom/mysklearn2.py +0 -0
- {analyser_hj3415-2.9.8 → analyser_hj3415-2.9.10}/analyser_hj3415/workroom/score.py +0 -0
- {analyser_hj3415-2.9.8 → analyser_hj3415-2.9.10}/analyser_hj3415/workroom/trash.py +0 -0
@@ -1,18 +1,20 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: analyser_hj3415
|
3
|
-
Version: 2.9.
|
3
|
+
Version: 2.9.10
|
4
4
|
Summary: Stock analyser and database processing programs
|
5
5
|
Requires-Python: >=3.6
|
6
6
|
Description-Content-Type: text/markdown
|
7
7
|
Requires-Dist: utils-hj3415>=2.9.2
|
8
|
-
Requires-Dist: db-hj3415>=4.
|
8
|
+
Requires-Dist: db-hj3415>=4.1.4
|
9
9
|
Requires-Dist: scikit-learn>=1.5.2
|
10
10
|
Requires-Dist: plotly>=5.24.1
|
11
11
|
Requires-Dist: yfinance>=0.2.44
|
12
12
|
Requires-Dist: prophet>=1.1.6
|
13
13
|
Requires-Dist: kaleido>=0.2.1
|
14
14
|
Requires-Dist: matplotlib>=3.9.2
|
15
|
-
Requires-Dist: tensorflow>=2.
|
15
|
+
Requires-Dist: tensorflow-macos>=2.16.2; platform_machine == 'arm64' and sys_platform == 'darwin'
|
16
|
+
Requires-Dist: tensorflow-metal>=0.5.0; platform_machine == 'arm64' and sys_platform == 'darwin'
|
17
|
+
Requires-Dist: tensorflow>=2.18.0; platform_machine != 'arm64' or sys_platform != 'darwin'
|
16
18
|
|
17
19
|
### analyser-hj3415
|
18
20
|
|
@@ -4,8 +4,7 @@ import pprint
|
|
4
4
|
|
5
5
|
from utils_hj3415 import noti, utils
|
6
6
|
from utils_hj3415.helpers import SettingsManager
|
7
|
-
from db_hj3415 import myredis
|
8
|
-
|
7
|
+
from db_hj3415 import myredis, mypeewee
|
9
8
|
|
10
9
|
class AnalyserSettingsManager(SettingsManager):
|
11
10
|
DEFAULT_SETTINGS = {
|
@@ -34,6 +33,8 @@ class AnalyserSettingsManager(SettingsManager):
|
|
34
33
|
print(f"{title}이 기본값 ({self.DEFAULT_SETTINGS[title]}) 으로 초기화 되었습니다.")
|
35
34
|
|
36
35
|
|
36
|
+
log_cli = mypeewee.LogManager('cli')
|
37
|
+
|
37
38
|
def analyser_manager():
|
38
39
|
settings_manager = AnalyserSettingsManager()
|
39
40
|
expect_earn_from_setting = settings_manager.get_value('EXPECT_EARN')
|
@@ -47,7 +48,6 @@ def analyser_manager():
|
|
47
48
|
# ranking 파서
|
48
49
|
ranking_parser = prophet_subparser.add_parser('ranking', help='prophet 랭킹 책정 및 레디스 저장')
|
49
50
|
ranking_parser.add_argument('-r', '--refresh', action='store_true', help='래디스 캐시를 사용하지 않고 강제로 재계산 할지')
|
50
|
-
ranking_parser.add_argument('-n', '--noti', action='store_true', help='작업 완료 후 메시지 전송 여부')
|
51
51
|
|
52
52
|
# lstm 명령어 서브파서
|
53
53
|
lstm_parser = type_subparsers.add_parser('lstm', help='MyLSTM 타입')
|
@@ -56,12 +56,10 @@ def analyser_manager():
|
|
56
56
|
caching_parser = lstm_subparser.add_parser('caching', help='lstm 랭킹 책정 및 레디스 저장')
|
57
57
|
caching_parser.add_argument('-r', '--refresh', action='store_true', help='래디스 캐시를 사용하지 않고 강제로 재계산 할지')
|
58
58
|
caching_parser.add_argument('-t', '--top', type=int, help='prophet ranking 몇위까지 작업을 할지')
|
59
|
-
caching_parser.add_argument('-n', '--noti', action='store_true', help='작업 완료 후 메시지 전송 여부')
|
60
59
|
# red - get 파서
|
61
60
|
lstm_get_parser = lstm_subparser.add_parser('get', help='lstm get 책정 및 레디스 저장')
|
62
61
|
lstm_get_parser.add_argument('code', type=str, help='종목코드')
|
63
62
|
lstm_get_parser.add_argument('-r', '--refresh', action='store_true', help='래디스 캐시를 사용하지 않고 강제로 재계산 할지')
|
64
|
-
lstm_get_parser.add_argument('-n', '--noti', action='store_true', help='작업 완료 후 메시지 전송 여부')
|
65
63
|
|
66
64
|
# red 명령어 서브파서
|
67
65
|
red_parser = type_subparsers.add_parser('red', help='red 타입')
|
@@ -70,13 +68,11 @@ def analyser_manager():
|
|
70
68
|
ranking_parser = red_subparser.add_parser('ranking', help='red 랭킹 책정 및 레디스 저장')
|
71
69
|
ranking_parser.add_argument('-e', '--expect_earn', type=float, help='기대수익률 (실수 값 입력)')
|
72
70
|
ranking_parser.add_argument('-r', '--refresh', action='store_true', help='래디스 캐시를 사용하지 않고 강제로 재계산 할지')
|
73
|
-
ranking_parser.add_argument('-n', '--noti', action='store_true', help='작업 완료 후 메시지 전송 여부')
|
74
71
|
# red - get 파서
|
75
72
|
red_get_parser = red_subparser.add_parser('get', help='red get 책정 및 레디스 저장')
|
76
73
|
red_get_parser.add_argument('code', type=str, help='종목코드 or all')
|
77
74
|
red_get_parser.add_argument('-e', '--expect_earn', type=float, help='기대수익률 (실수 값 입력)')
|
78
75
|
red_get_parser.add_argument('-r', '--refresh', action='store_true', help='래디스 캐시를 사용하지 않고 강제로 재계산 할지')
|
79
|
-
red_get_parser.add_argument('-n', '--noti', action='store_true', help='작업 완료 후 메시지 전송 여부')
|
80
76
|
|
81
77
|
# mil 명령어 서브파서
|
82
78
|
mil_parser = type_subparsers.add_parser('mil', help='millennial 타입')
|
@@ -86,7 +82,6 @@ def analyser_manager():
|
|
86
82
|
mil_get_parser.add_argument('code', type=str, help='종목코드 or all')
|
87
83
|
mil_get_parser.add_argument('-e', '--expect_earn', type=float, help='기대수익률 (실수 값 입력)')
|
88
84
|
mil_get_parser.add_argument('-r', '--refresh', action='store_true', help='래디스 캐시를 사용하지 않고 강제로 재계산 할지')
|
89
|
-
mil_get_parser.add_argument('-n', '--noti', action='store_true', help='작업 완료 후 메시지 전송 여부')
|
90
85
|
|
91
86
|
# blue 명령어 서브파서
|
92
87
|
blue_parser = type_subparsers.add_parser('blue', help='Blue 타입')
|
@@ -96,7 +91,6 @@ def analyser_manager():
|
|
96
91
|
blue_get_parser.add_argument('code', type=str, help='종목코드 or all')
|
97
92
|
blue_get_parser.add_argument('-e', '--expect_earn', type=float, help='기대수익률 (실수 값 입력)')
|
98
93
|
blue_get_parser.add_argument('-r', '--refresh', action='store_true', help='래디스 캐시를 사용하지 않고 강제로 재계산 할지')
|
99
|
-
blue_get_parser.add_argument('-n', '--noti', action='store_true', help='작업 완료 후 메시지 전송 여부')
|
100
94
|
|
101
95
|
# growth 명령어 서브파서
|
102
96
|
growth_parser = type_subparsers.add_parser('growth', help='Growth 타입')
|
@@ -106,7 +100,6 @@ def analyser_manager():
|
|
106
100
|
growth_get_parser.add_argument('code', type=str, help='종목코드 or all')
|
107
101
|
growth_get_parser.add_argument('-e', '--expect_earn', type=float, help='기대수익률 (실수 값 입력)')
|
108
102
|
growth_get_parser.add_argument('-r', '--refresh', action='store_true', help='래디스 캐시를 사용하지 않고 강제로 재계산 할지')
|
109
|
-
growth_get_parser.add_argument('-n', '--noti', action='store_true', help='작업 완료 후 메시지 전송 여부')
|
110
103
|
|
111
104
|
# setting 명령어 서브파서
|
112
105
|
setting_parser = type_subparsers.add_parser('setting', help='Set and Get settings')
|
@@ -158,14 +151,12 @@ def analyser_manager():
|
|
158
151
|
|
159
152
|
# 원래 저장되었던 기대수익률로 다시 복원
|
160
153
|
eval.Red.expect_earn = ee_orig
|
161
|
-
|
162
|
-
noti.telegram_to('manager', f"오늘의 Red({args.code})를 레디스캐시에 저장했습니다.(유효 12시간)")
|
154
|
+
log_cli.save('INFO', f'run >> analyser red get {args.code}')
|
163
155
|
|
164
156
|
elif args.command == 'ranking':
|
157
|
+
log_cli.save('INFO', 'run >> analyser red ranking')
|
165
158
|
result = eval.Red.ranking(expect_earn=args.expect_earn, refresh=args.refresh)
|
166
159
|
print(result)
|
167
|
-
if args.noti:
|
168
|
-
noti.telegram_to('manager', "오늘의 red ranking을 레디스캐시에 저장했습니다.(유효 12시간)")
|
169
160
|
|
170
161
|
elif args.type == 'mil':
|
171
162
|
if args.command == 'get':
|
@@ -181,8 +172,7 @@ def analyser_manager():
|
|
181
172
|
mil = eval.Mil(args.code)
|
182
173
|
print(f"*** Mil - {mil} ***")
|
183
174
|
pprint.pprint(mil.get(args.refresh))
|
184
|
-
|
185
|
-
noti.telegram_to('manager', f"오늘의 Mil({args.code})를 레디스 캐시에 저장했습니다.(유효 12시간)")
|
175
|
+
log_cli.save('INFO', f'run >> analyser mil get {args.code}')
|
186
176
|
|
187
177
|
elif args.type == 'blue':
|
188
178
|
if args.command == 'get':
|
@@ -198,8 +188,7 @@ def analyser_manager():
|
|
198
188
|
blue = eval.Blue(args.code)
|
199
189
|
print(f"*** Blue - {blue} ***")
|
200
190
|
pprint.pprint(blue.get(args.refresh))
|
201
|
-
|
202
|
-
noti.telegram_to('manager', f"오늘의 Blue({args.code})를 레디스 캐시에 저장했습니다.(유효 12시간)")
|
191
|
+
log_cli.save('INFO', f'run >> analyser blue get {args.code}')
|
203
192
|
|
204
193
|
elif args.type == 'growth':
|
205
194
|
if args.command == 'get':
|
@@ -215,31 +204,28 @@ def analyser_manager():
|
|
215
204
|
growth = eval.Growth(args.code)
|
216
205
|
print(f"*** growth - {growth} ***")
|
217
206
|
pprint.pprint(growth.get(args.refresh))
|
218
|
-
|
219
|
-
|
207
|
+
log_cli.save('INFO', f'run >> analyser growth get {args.code}')
|
208
|
+
|
220
209
|
elif args.type == 'prophet':
|
221
210
|
if args.command == 'ranking':
|
222
211
|
myprophet = tsa.MyProphet
|
223
|
-
myprophet.expire_time_h
|
224
|
-
result = myprophet.ranking(refresh=args.refresh)
|
212
|
+
result = myprophet.ranking(refresh=args.refresh, expire_time_h=24)
|
225
213
|
print(result)
|
226
|
-
|
227
|
-
|
214
|
+
log_cli.save('INFO', 'run >> analyser prophet ranking')
|
215
|
+
|
228
216
|
elif args.type == 'lstm':
|
229
217
|
mylstm = tsa.MyLSTM
|
230
|
-
mylstm.expire_time_h = 72
|
231
218
|
if args.command == 'caching':
|
232
219
|
if args.top:
|
233
|
-
mylstm.caching_based_on_prophet_ranking(refresh=args.refresh, top=args.top)
|
220
|
+
mylstm.caching_based_on_prophet_ranking(refresh=args.refresh, expire_time_h=24, top=args.top)
|
234
221
|
else:
|
235
|
-
mylstm.caching_based_on_prophet_ranking(refresh=args.refresh)
|
236
|
-
if args.
|
237
|
-
noti.telegram_to('manager', f"오늘의 lstm caching(top={args.top if args.top else 20})을 레디스캐시에 저장했습니다.(유효 {mylstm.expire_time_h}시간)")
|
222
|
+
mylstm.caching_based_on_prophet_ranking(refresh=args.refresh, expire_time_h=24)
|
223
|
+
log_cli.save('INFO', f'run >> analyser lstm caching / top={args.top if args.top else 20})')
|
238
224
|
elif args.command == 'get':
|
239
225
|
assert utils.is_6digit(args.code), "code 인자는 6자리 숫자이어야 합니다."
|
240
|
-
result = mylstm(args.code).get_final_predictions(refresh=args.refresh)
|
241
|
-
|
242
|
-
|
226
|
+
result = mylstm(args.code).get_final_predictions(refresh=args.refresh, expire_time_h=24)
|
227
|
+
log_cli.save('INFO', f'run >> analyser lstm get {args.code}')
|
228
|
+
|
243
229
|
elif args.type == 'setting':
|
244
230
|
if args.command == 'set':
|
245
231
|
settings_manager.set_value(args.title, args.value)
|
@@ -248,5 +234,8 @@ def analyser_manager():
|
|
248
234
|
print(f"{args.title} 값: {value}")
|
249
235
|
elif args.command == 'print':
|
250
236
|
print(settings_manager.load_settings())
|
237
|
+
|
251
238
|
else:
|
252
239
|
parser.print_help()
|
240
|
+
|
241
|
+
log_cli.close()
|
@@ -30,10 +30,8 @@ import logging
|
|
30
30
|
|
31
31
|
tsa_logger = helpers.setup_logger('tsa_logger', logging.WARNING)
|
32
32
|
|
33
|
-
expire_time = 3600 * 24
|
34
33
|
|
35
34
|
class MyProphet:
|
36
|
-
expire_time_h = 24
|
37
35
|
def __init__(self, code: str):
|
38
36
|
assert utils.is_6digit(code), f'Invalid value : {code}'
|
39
37
|
self.scaler = StandardScaler()
|
@@ -195,7 +193,7 @@ class MyProphet:
|
|
195
193
|
Exception("to 인자가 맞지 않습니다.")
|
196
194
|
|
197
195
|
@classmethod
|
198
|
-
def ranking(cls, refresh = False) -> OrderedDict:
|
196
|
+
def ranking(cls, refresh = False, expire_time_h = 24) -> OrderedDict:
|
199
197
|
"""
|
200
198
|
가장 최근 날짜의 랭킹 분석
|
201
199
|
:param refresh:
|
@@ -205,7 +203,7 @@ class MyProphet:
|
|
205
203
|
redis_name = 'myprophet_ranking'
|
206
204
|
|
207
205
|
print(
|
208
|
-
f"redisname: '{redis_name}' / refresh : {refresh} / expire_time : {
|
206
|
+
f"redisname: '{redis_name}' / refresh : {refresh} / expire_time : {expire_time_h}h")
|
209
207
|
|
210
208
|
def fetch_ranking() -> dict:
|
211
209
|
data = {}
|
@@ -224,7 +222,7 @@ class MyProphet:
|
|
224
222
|
print(f"{i}.{p.code}/{p.name} date: {recent_date} 가격:{recent_price} 기대하한값:{yhat_lower} 편차:{deviation}")
|
225
223
|
return data
|
226
224
|
|
227
|
-
data_dict = myredis.Base.fetch_and_cache_data(redis_name, refresh, fetch_ranking, timer=
|
225
|
+
data_dict = myredis.Base.fetch_and_cache_data(redis_name, refresh, fetch_ranking, timer=expire_time_h * 3600)
|
228
226
|
|
229
227
|
return OrderedDict(sorted(data_dict.items(), key=lambda item: item[1], reverse=True))
|
230
228
|
|
@@ -265,7 +263,6 @@ class MyLSTM:
|
|
265
263
|
"""
|
266
264
|
# 미래 몇일을 예측할 것인가?
|
267
265
|
future_days = 30
|
268
|
-
expire_time_h = 24
|
269
266
|
|
270
267
|
def __init__(self, code: str):
|
271
268
|
assert utils.is_6digit(code), f'Invalid value : {code}'
|
@@ -489,7 +486,7 @@ class MyLSTM:
|
|
489
486
|
test_r2=test_r2,
|
490
487
|
)
|
491
488
|
|
492
|
-
def get_final_predictions(self, refresh, num=5) -> tuple:
|
489
|
+
def get_final_predictions(self, refresh: bool, expire_time_h: int, num=5) -> tuple:
|
493
490
|
"""
|
494
491
|
미래 예측치를 레디스 캐시를 이용하여 반환함
|
495
492
|
:param refresh:
|
@@ -500,7 +497,7 @@ class MyLSTM:
|
|
500
497
|
redis_name = f'{self.code}_mylstm_predictions'
|
501
498
|
|
502
499
|
print(
|
503
|
-
f"redisname: '{redis_name}' / refresh : {refresh} / expire_time : {
|
500
|
+
f"redisname: '{redis_name}' / refresh : {refresh} / expire_time : {expire_time_h}h")
|
504
501
|
|
505
502
|
def fetch_final_predictions(num_in) -> tuple:
|
506
503
|
"""
|
@@ -531,7 +528,7 @@ class MyLSTM:
|
|
531
528
|
|
532
529
|
return future_dates_str, final_future_predictions.tolist()
|
533
530
|
|
534
|
-
future_dates_str, final_future_predictions = myredis.Base.fetch_and_cache_data(redis_name, refresh, fetch_final_predictions, num, timer=
|
531
|
+
future_dates_str, final_future_predictions = myredis.Base.fetch_and_cache_data(redis_name, refresh, fetch_final_predictions, num, timer=expire_time_h * 3600)
|
535
532
|
|
536
533
|
# 문자열을 날짜 형식으로 변환
|
537
534
|
future_dates = [datetime.strptime(date, '%Y-%m-%d') for date in future_dates_str]
|
@@ -541,14 +538,14 @@ class MyLSTM:
|
|
541
538
|
|
542
539
|
return future_dates, final_future_predictions
|
543
540
|
|
544
|
-
def export(self, refresh=False, to="str") -> Optional[str]:
|
541
|
+
def export(self, refresh=False, expire_time_h=24, to="str") -> Optional[str]:
|
545
542
|
"""
|
546
543
|
prophet과 plotly로 그래프를 그려서 html을 문자열로 반환
|
547
544
|
:param refresh:
|
548
545
|
:param to: str, htmlfile, png
|
549
546
|
:return:
|
550
547
|
"""
|
551
|
-
future_dates, final_future_predictions = self.get_final_predictions(refresh=refresh)
|
548
|
+
future_dates, final_future_predictions = self.get_final_predictions(refresh=refresh, expire_time_h=expire_time_h)
|
552
549
|
final_future_predictions = final_future_predictions.reshape(-1) # 차원을 하나 줄인다.
|
553
550
|
|
554
551
|
# 데이터 준비
|
@@ -636,7 +633,7 @@ class MyLSTM:
|
|
636
633
|
Exception("to 인자가 맞지 않습니다.")
|
637
634
|
|
638
635
|
def visualization(self, refresh=True):
|
639
|
-
future_dates, final_future_predictions = self.get_final_predictions(refresh=refresh)
|
636
|
+
future_dates, final_future_predictions = self.get_final_predictions(refresh=refresh, expire_time_h=1)
|
640
637
|
|
641
638
|
# 시각화1
|
642
639
|
plt.figure(figsize=(10, 6))
|
@@ -664,15 +661,15 @@ class MyLSTM:
|
|
664
661
|
plt.show()"""
|
665
662
|
|
666
663
|
@staticmethod
|
667
|
-
def caching_based_on_prophet_ranking(refresh: bool, top=20):
|
668
|
-
ranking_topn = OrderedDict(itertools.islice(MyProphet.ranking().items(), top))
|
664
|
+
def caching_based_on_prophet_ranking(refresh: bool, expire_time_h: int, top=20):
|
665
|
+
ranking_topn = OrderedDict(itertools.islice(MyProphet.ranking(refresh=False).items(), top))
|
669
666
|
tsa_logger.info(ranking_topn)
|
670
667
|
mylstm = MyLSTM('005930')
|
671
668
|
print(f"*** LSTM prediction redis cashing top{top} items ***")
|
672
669
|
for i, (code, _) in enumerate(ranking_topn.items()):
|
673
670
|
mylstm.code = code
|
674
671
|
print(f"{i+1}. {mylstm.code}/{mylstm.name}")
|
675
|
-
mylstm.get_final_predictions(refresh=refresh, num=5)
|
672
|
+
mylstm.get_final_predictions(refresh=refresh, expire_time_h=expire_time_h, num=5)
|
676
673
|
|
677
674
|
|
678
675
|
|
@@ -5,7 +5,7 @@ build-backend = "flit_core.buildapi"
|
|
5
5
|
|
6
6
|
[project]
|
7
7
|
name = "analyser_hj3415"
|
8
|
-
version = "2.9.
|
8
|
+
version = "2.9.10"
|
9
9
|
description = "Stock analyser and database processing programs"
|
10
10
|
readme = "README.md"
|
11
11
|
requires-python = ">=3.6"
|
@@ -21,14 +21,19 @@ requires-python = ">=3.6"
|
|
21
21
|
#]
|
22
22
|
dependencies = [
|
23
23
|
"utils-hj3415>=2.9.2",
|
24
|
-
"db-hj3415>=4.
|
24
|
+
"db-hj3415>=4.1.4",
|
25
25
|
"scikit-learn>=1.5.2",
|
26
26
|
"plotly>=5.24.1",
|
27
27
|
"yfinance>=0.2.44",
|
28
28
|
"prophet>=1.1.6",
|
29
29
|
"kaleido>=0.2.1", #plotly로 이미지출력위해
|
30
30
|
"matplotlib>=3.9.2",
|
31
|
-
|
31
|
+
# Apple Silicon (ARM, macOS)에서 tensorflow-macos 사용
|
32
|
+
"tensorflow-macos>=2.16.2; platform_machine == 'arm64' and sys_platform == 'darwin'",
|
33
|
+
"tensorflow-metal>=0.5.0; platform_machine == 'arm64' and sys_platform == 'darwin'",
|
34
|
+
# 그 외 환경에서는 일반 tensorflow 사용
|
35
|
+
"tensorflow>=2.18.0; platform_machine != 'arm64' or sys_platform != 'darwin'"
|
36
|
+
|
32
37
|
]
|
33
38
|
|
34
39
|
[project.scripts]
|
analyser_hj3415-2.9.8/.DS_Store
DELETED
Binary file
|
Binary file
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|