neurostats-API 0.0.3__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,1137 @@
1
+ from .utils.db_client import shared_client
2
+ import pandas as pd
3
+ import json
4
+ import pytz
5
+ from datetime import datetime, timedelta, date
6
+ import yaml
7
+
8
+
9
+ class StatsFetcher:
10
+
11
+ def __init__(self):
12
+ self.db = shared_client["company"] # Replace with your database name
13
+ self.collection = self.db["twse_stats"]
14
+
15
+ self.timezone = pytz.timezone("Asia/Taipei")
16
+
17
+ self.inverse_dict = dict()
18
+
19
+ with open("./tools/seasonal_data_field_dict.txt") as f:
20
+ self.inverse_dict = json.load(f)
21
+
22
+ self.seasons = ["01", "02", "03", "04"]
23
+
24
+ self.pipeline = list()
25
+
26
+ self.target_metric_dict = {
27
+ 'value': ['value'],
28
+ 'value_and_percentage': ['value', 'percentage'],
29
+ 'percentage': ['percentage'],
30
+ 'grand_total': ['grand_total'],
31
+ 'grand_total_values':['grand_total', 'grand_total_percentage'],
32
+ 'grand_total_percentage':['grand_total_percentage'],
33
+ 'growth': [f'YoY_{i}' for i in [1,3,5,10]],
34
+ 'grand_total_growth': [f"YoY_{i}" for i in [1,3,5,10]]
35
+ }
36
+
37
+ self.__return_dict = dict()
38
+
39
+ def _flush_dict(self):
40
+ self.__return_dict = dict()
41
+
42
+ def _default_query(self, ticker, start_date, end_date):
43
+
44
+ start_year, start_month, start_day = [
45
+ int(num) for num in start_date.split("-")
46
+ ]
47
+ end_year, end_month, end_day = [
48
+ int(num) for num in end_date.split("-")
49
+ ]
50
+
51
+ start_date = datetime.strptime(start_date, "%Y-%m-%d")
52
+ end_date = datetime.strptime(end_date, "%Y-%m-%d")
53
+ start_date = self.timezone.localize(start_date)
54
+ end_date = self.timezone.localize(end_date)
55
+
56
+ start_season = start_month // 3 + 1
57
+ end_season = end_month // 3 + 1
58
+
59
+ ticker = ticker.strip().split()[0]
60
+
61
+ self.pipeline = [
62
+ # 1. Find Ticker
63
+ {
64
+ "$match": {
65
+ "ticker": ticker,
66
+ }
67
+ },
68
+ # 2. Find by date
69
+ {
70
+ "$project": {
71
+ "ticker": 1,
72
+ "company_name": 1,
73
+ # 2.1 Filter monthly_data
74
+ "daily_data": {
75
+ "$filter": {
76
+ "input": "$daily_data",
77
+ "as": "daily",
78
+ "cond": {
79
+ "$and": [
80
+ {
81
+ "$gte": ["$$daily.date", start_date]
82
+ },
83
+ {
84
+ "$lte": ["$$daily.date", end_date]
85
+ },
86
+ ]
87
+ },
88
+ }
89
+ },
90
+ # 2.2 Filter monthly_data
91
+ "monthly_data": {
92
+ "$filter": {
93
+ "input": "$monthly_data",
94
+ "as": "monthly",
95
+ "cond": {
96
+ "$or": [
97
+ {
98
+ "$and": [
99
+ {
100
+ "$eq":
101
+ ["$$monthly.year", start_year]
102
+ },
103
+ {
104
+ "$gte": [
105
+ "$$monthly.month",
106
+ start_month
107
+ ]
108
+ },
109
+ ]
110
+ },
111
+ {
112
+ "$and": [
113
+ {
114
+ "$eq":
115
+ ["$$monthly.year", end_year]
116
+ },
117
+ {
118
+ "$lte":
119
+ ["$$monthly.month", end_month]
120
+ },
121
+ ]
122
+ },
123
+ {
124
+ "$and": [
125
+ {
126
+ "$gt":
127
+ ["$$monthly.year", start_year]
128
+ },
129
+ {
130
+ "$lt":
131
+ ["$$monthly.year", end_year]
132
+ },
133
+ ]
134
+ },
135
+ ]
136
+ },
137
+ }
138
+ },
139
+ # 2.3 Filter seasonal_data
140
+ "seasonal_data": {
141
+ "$filter": {
142
+ "input": "$seasonal_data",
143
+ "as": "seasonal",
144
+ "cond": {
145
+ "$or": [
146
+ {
147
+ "$and": [
148
+ {
149
+ "$eq": [
150
+ "$$seasonal.year",
151
+ start_year
152
+ ]
153
+ },
154
+ {
155
+ "$gte": [
156
+ "$$seasonal.season",
157
+ start_season
158
+ ]
159
+ },
160
+ ]
161
+ },
162
+ {
163
+ "$and": [
164
+ {
165
+ "$eq":
166
+ ["$$seasonal.year", end_year]
167
+ },
168
+ {
169
+ "$lte": [
170
+ "$$seasonal.season",
171
+ end_season
172
+ ]
173
+ },
174
+ ]
175
+ },
176
+ {
177
+ "$and": [
178
+ {
179
+ "$gt": [
180
+ "$$seasonal.year",
181
+ start_year
182
+ ]
183
+ },
184
+ {
185
+ "$lt":
186
+ ["$$seasonal.year", end_year]
187
+ },
188
+ ]
189
+ },
190
+ ]
191
+ },
192
+ }
193
+ },
194
+ "yearly_data": {
195
+ "$filter": {
196
+ "input": "$yearly_data",
197
+ "as": "yearly",
198
+ "cond": {
199
+ "$and": [
200
+ {
201
+ "$gte": ["$$yearly.year", 107]
202
+ },
203
+ {
204
+ "$lte": ["$$yearly.year", end_year]
205
+ },
206
+ ]
207
+ },
208
+ }
209
+ }
210
+ }
211
+ },
212
+ ]
213
+
214
+ def query_data(self, ticker, start_date, end_date):
215
+ """
216
+ Return : Dict {
217
+ 'ticker' : <ticker>,
218
+ 'company_name': <company_name>,
219
+ 'daily_data': List[Dict]
220
+ 'monthly_data': List[Dict]
221
+ 'seasonal_data': List[Dict]
222
+ }
223
+ """
224
+
225
+ self._default_query(ticker, start_date, end_date)
226
+
227
+ fetched_datas = list(self.collection.aggregate(self.pipeline))
228
+
229
+ return fetched_datas[0]
230
+
231
+ def query_values(self, ticker, start_date, end_date):
232
+ self._default_query(ticker, start_date, end_date)
233
+
234
+ self.pipeline.append({
235
+ "$project": {
236
+ "ticker": 1,
237
+ "company_name": 1,
238
+
239
+ # Transform daily_data to include only index and date
240
+ "daily_data": {
241
+ "$map": {
242
+ "input": "$daily_data",
243
+ "as": "daily_item",
244
+ "in": {
245
+ "date": "$$daily_item.date",
246
+ "close": "$$daily_item.close",
247
+ "P_B": "$$daily_item.P_B",
248
+ "P_E": "$$daily_item.P_E",
249
+ "P_FCF": "$$daily_item.P_FCF",
250
+ "P_S": "$$daily_item.P_S",
251
+ "EV_OPI": "$$daily_item.EV_OPI",
252
+ "EV_EBIT": "$$daily_item.EV_EBIT",
253
+ "EV_EBITDA": "$$daily_item.EV_EBITDA",
254
+ "EV_S": "$$daily_item.EV_S"
255
+ }
256
+ }
257
+ },
258
+ "yearly_data": 1
259
+ }
260
+ })
261
+
262
+ fetched_datas = list(self.collection.aggregate(self.pipeline))
263
+
264
+ return fetched_datas[0]
265
+
266
+ def query_stock_price(self, ticker, start_date, end_date):
267
+
268
+ self.pipeline.append({
269
+ "$project": {
270
+ "ticker": 1,
271
+ "company_name": 1,
272
+
273
+ # Transform daily_data to include only index and date
274
+ "daily_data": {
275
+ "$map": {
276
+ "input": "$daily_data",
277
+ "as": "daily_item",
278
+ "in": {
279
+ "date": "$$daily_item.date",
280
+ "open": "$$daily_item.open",
281
+ "high": "$$daily_item.high",
282
+ "los": "$$daily_item.low",
283
+ "close": "$$daily_item.close",
284
+ }
285
+ }
286
+ },
287
+ }
288
+ })
289
+
290
+ fetched_datas = list(self.collection.aggregate(self.pipeline))
291
+
292
+ return fetched_datas[0]
293
+
294
+ def query_seasonal_data(self, ticker, start_date, end_date, sheet, target_season):
295
+
296
+ self._default_query(ticker, start_date, end_date)
297
+ self.pipeline.append({
298
+ "$project": {
299
+ "ticker": 1,
300
+ "company_name": 1,
301
+ "seasonal_data": {
302
+ "$filter":{
303
+ "input":{
304
+ "$map": {
305
+ "input": "$seasonal_data",
306
+ "as": "seasonal_item",
307
+ "in": {
308
+ "year": "$$seasonal_item.year",
309
+ "season": "$$seasonal_item.season",
310
+ f"{sheet}": f"$$seasonal_item.{sheet}"
311
+ }
312
+ }
313
+ },
314
+ "as": "filtered_item",
315
+ "cond": { "$eq": ["$$filtered_item.season", target_season] }
316
+ }
317
+ },
318
+ }
319
+ })
320
+
321
+ self.pipeline.append({"$unwind": "$seasonal_data"})
322
+
323
+ self.pipeline.append(
324
+ {"$sort": {
325
+ "seasonal_data.year": 1,
326
+ "seasonal_data.season": 1
327
+ }})
328
+
329
+ # self.pipeline.append({
330
+ # "$group": {
331
+ # "_id": {
332
+ # "ticker": "$ticker",
333
+ # "company_name": "$company_name"
334
+ # },
335
+ # "seasonal_data": {
336
+ # "$push": "$seasonal_data.balance_sheet"
337
+ # }
338
+ # }
339
+ # })
340
+
341
+ # self.pipeline.append({
342
+ # "$project": {
343
+ # "ticker": "$_id.ticker",
344
+ # "company_name": "$_id.company_name",
345
+ # "seasonal_data": {
346
+ # "balance_sheet": "$seasonal_data"
347
+ # }
348
+ # }
349
+ # })
350
+
351
+ fetched_datas = list(self.collection.aggregate(self.pipeline))
352
+
353
+ return fetched_datas
354
+
355
+ def query_month_revenue(self, ticker, start_date, end_date):
356
+ self._default_query(ticker, start_date, end_date)
357
+ self.pipeline.append(
358
+ {
359
+ "$project": {
360
+ "ticker": 1,
361
+ "company_name": 1,
362
+ "monthly_data": {
363
+ "$sortArray": {
364
+ "input": "$monthly_data",
365
+ "sortBy": {
366
+ "year": 1,
367
+ "month": 1
368
+ }
369
+ }
370
+ },
371
+ }
372
+ }, )
373
+
374
+ fetched_datas = list(self.collection.aggregate(self.pipeline))
375
+
376
+ return fetched_datas[0]
377
+
378
+ def query_latest_values(self, ticker):
379
+ """
380
+ 傳回最近一天的價值面
381
+
382
+ return : Dict {
383
+ "ticker": 股票代碼,
384
+ "company_name": 公司中文名稱,
385
+ ## 以下八個是iFa項目
386
+ "P_E": 本益比,
387
+ "P_B": 股價,
388
+ "P_FCF": 股價自由現金流比,
389
+ "P_S": 股價營收比,
390
+ "EV_EBIT: ,
391
+ "EV_EBITDA": ,
392
+ "EV_OPI": ,
393
+ "EV_S"; ,
394
+ ## 以上八個是iFa項目
395
+ "close": 收盤價,
396
+ "EV": 市場價值
397
+ }
398
+ """
399
+ today = date.today()
400
+ yesterday = (today - timedelta(days=14)).strftime("%Y-%m-%d")
401
+ today = today.strftime("%Y-%m-%d")
402
+
403
+ fetched_datas = self.query_values(ticker, yesterday, today)
404
+
405
+ daily_data = fetched_datas.pop('daily_data')
406
+ fetched_datas.update(daily_data[-1])
407
+ return fetched_datas
408
+
409
+ def query_latest_month_revenue(self, ticker):
410
+ """
411
+ 傳回最新一期的月營收
412
+ """
413
+
414
+ today = date.today()
415
+
416
+ last_month = (today - timedelta(days=30)).strftime("%Y-%m-%d")
417
+ today = today.strftime("%Y-%m-%d")
418
+
419
+ fetched_datas = self.query_month_revenue(ticker, last_month, today)
420
+
421
+ print(fetched_datas)
422
+
423
+ latest_month_revenue = fetched_datas['monthly_data']
424
+ fetched_datas.pop('monthly_data')
425
+
426
+ fetched_datas.update(latest_month_revenue[-1])
427
+
428
+ return fetched_datas
429
+
430
+ def query_latest_seasonal_data(self, ticker):
431
+ """
432
+ 傳回最新一期的季報
433
+ """
434
+ today = date.today()
435
+
436
+ last_season = (today - timedelta(days=90)).strftime("%Y-%m-%d")
437
+ today = today.strftime("%Y-%m-%d")
438
+
439
+ fetched_datas = self.query_seasonal_data(ticker, last_season, today)
440
+
441
+ print(fetched_datas)
442
+
443
+ latest_seasonal_data = fetched_datas['seasonal_data']
444
+ fetched_datas.pop('seasonal_data')
445
+
446
+ fetched_datas.update(latest_seasonal_data[-1])
447
+
448
+ return fetched_datas
449
+
450
+ def get_value_sheet(self, ticker):
451
+ """
452
+ iFa.ai: 價值投資-> 市場指標
453
+ """
454
+ """
455
+ 傳回最近一天的價值面
456
+
457
+ return : Dict {
458
+ "ticker": 股票代碼,
459
+ "company_name": 公司中文名稱,
460
+ "daily_data":{
461
+ ## 以下八個是iFa項目
462
+ "P_E": 本益比,
463
+ "P_B": 股價,
464
+ "P_FCF": 股價自由現金流比,
465
+ "P_S": 股價營收比,
466
+ "EV_EBIT: ,
467
+ "EV_EBITDA": ,
468
+ "EV_OPI": ,
469
+ "EV_S";
470
+ ## 以上八個是iFa項目
471
+ "close": 收盤價,
472
+ }
473
+
474
+ "yearly_data": pd.DataFrame (下表格為範例)
475
+ year P_E P_FCF P_B P_S EV_OPI EV_EBIT EV_EBITDA EV_S
476
+ 0 107 16.68 29.155555 3.71 11.369868 29.837201 28.798274 187.647704 11.107886
477
+ 1 108 26.06 67.269095 5.41 17.025721 50.145736 47.853790 302.526388 17.088863
478
+ 2 109 27.98 95.650723 7.69 22.055379 53.346615 51.653834 205.847232 22.481951
479
+ 3 110 27.83 149.512474 7.68 22.047422 55.398018 54.221387 257.091893 22.615355
480
+ 4 111 13.11 48.562021 4.25 11.524975 24.683850 24.226554 66.953260 12.129333
481
+ 5 112 17.17 216.371410 4.59 16.419533 40.017707 37.699267 105.980652 17.127656
482
+ }
483
+ """
484
+ today = date.today()
485
+ this_year = today.year - 1911
486
+ yesterday = (today - timedelta(days=14)).strftime("%Y-%m-%d")
487
+ today = today.strftime("%Y-%m-%d")
488
+
489
+ fetched_datas = self.query_values(ticker, yesterday, today)
490
+
491
+ fetched_datas['daily_data'] = fetched_datas['daily_data'][-1]
492
+
493
+ latest_data = {"year": f"過去4季"}
494
+
495
+ latest_data.update(fetched_datas['daily_data'])
496
+ latest_data.pop("date")
497
+ latest_data.pop("close")
498
+
499
+ fetched_datas['yearly_data'].append(latest_data)
500
+
501
+ fetched_datas['yearly_data'] = pd.DataFrame.from_dict(
502
+ fetched_datas['yearly_data'])
503
+
504
+ return fetched_datas
505
+
506
+ def get_month_revenue_sheet(self, ticker):
507
+ """
508
+ iFa.ai: 財務分析 -> 每月營收
509
+
510
+ return: Dict {
511
+ 'ticker': str,
512
+ 'company_name': str,
513
+ 'month_revenue': pd.DataFrame (歷年的月營收以及到今年最新月份累計的月營收表格)
514
+ 'this_month_revenue_over_years': pd.DataFrame (今年這個月的月營收與歷年同月份的營收比較)
515
+ 'grand_total_over_years': pd.DataFrame (累計至今年這個月的月營收與歷年的比較)
516
+ }
517
+ """
518
+
519
+ today = datetime.today()
520
+ today = today.strftime("%Y-%m-%d")
521
+
522
+ start_date = "2014-01-01"
523
+
524
+ query_data = self.query_month_revenue(ticker, start_date, today)
525
+
526
+ monthly_data = query_data['monthly_data']
527
+
528
+ this_month = monthly_data[-1]["month"]
529
+
530
+ month_dict = {i: None for i in range(1, 13)}
531
+ month_dict[f"grand_total"] = None
532
+
533
+ monthly_dict = dict()
534
+
535
+ revenue_by_year = dict()
536
+ single_month_revenue_dict = {
537
+ "revenue": None,
538
+ "MoM": None,
539
+ "YoY": None,
540
+ "YoY_3": None,
541
+ "YoY_5": None,
542
+ "YoY_10": None
543
+ }
544
+
545
+ grand_total_by_year = dict()
546
+ grand_total_dict = {
547
+ "revenue": None,
548
+ "MoM": None,
549
+ "YoY": None,
550
+ "YoY_3": None,
551
+ "YoY_5": None,
552
+ "YoY_10": None
553
+ }
554
+
555
+ for data in monthly_data:
556
+ try:
557
+ monthly_dict[data['year']][data['month']] = data['revenue']
558
+ except:
559
+ monthly_dict[data['year']] = month_dict.copy()
560
+ monthly_dict[data['year']][data['month']] = data['revenue']
561
+
562
+ try:
563
+ if (data['last_year_revenue']
564
+ != monthly_dict[data['year'] - 1][data['month']]):
565
+ monthly_dict[data['year'] -
566
+ 1][data['month']] = data['last_year_revenue']
567
+ except:
568
+ pass
569
+
570
+ if (data['month'] == this_month):
571
+ monthly_dict[
572
+ data['year']][f"grand_total"] = data['grand_total']
573
+
574
+ single_month_revenue_dict['revenue'] = data["revenue"]
575
+ single_month_revenue_dict['YoY'] = data[
576
+ "revenue_increment_ratio"]
577
+
578
+ grand_total_dict['revenue'] = data["grand_total"]
579
+ grand_total_dict['YoY'] = data['grand_total_increment_ratio']
580
+
581
+ revenue_by_year[
582
+ data['year']] = single_month_revenue_dict.copy()
583
+ grand_total_by_year[data['year']] = grand_total_dict.copy()
584
+
585
+ query_data['month_revenue'] = pd.DataFrame(monthly_dict)
586
+ query_data['this_month_revenue_over_years'] = pd.DataFrame(
587
+ revenue_by_year)
588
+ query_data['grand_total_over_years'] = pd.DataFrame(
589
+ grand_total_by_year)
590
+
591
+ query_data.pop("monthly_data")
592
+
593
+ return query_data
594
+
595
+ def _expand_value_percentage(self, dataframe):
596
+
597
+ expanded_columns = {}
598
+ for col in dataframe.columns:
599
+ # Use json_normalize to split 'value' and 'percentage'
600
+ expanded_df = pd.json_normalize(
601
+ dataframe[col]).add_prefix(f"{col}_")
602
+ expanded_df.index = dataframe.index
603
+ # Append the expanded columns to the new DataFrame
604
+ expanded_columns[col] = expanded_df
605
+
606
+ expanded_df = pd.concat(expanded_columns.values(), axis=1)
607
+
608
+ return expanded_df
609
+
610
+ def _get_today(self):
611
+ today = datetime.today()
612
+ this_year = today.year
613
+ this_month = today.month
614
+ this_day = today.day
615
+
616
+ return {
617
+ "today": today,
618
+ "year": this_year,
619
+ "month": this_month,
620
+ "day": this_day,
621
+ }
622
+
623
+ def get_balance_sheet(self, ticker):
624
+ """
625
+ iFa.ai: 財務分析 -> 資產負債表
626
+
627
+ Return: Dict
628
+ {
629
+ 'ticker': 股票代碼,
630
+ 'company_name': 公司名稱,
631
+
632
+ 'balance_sheet': 歷年當季資場負債表"全表" (pd.DataFrame)
633
+ 'total_asset': 歷年當季資產總額 (pd.DataFrame)
634
+ 'current_asset': 歷年當季流動資產總額 (pd.DataFrame)
635
+ 'non_current_asset': 歷年當季非流動資產 (pd.DataFrame)
636
+ 'current_debt': 歷年當季流動負債 (pd.DataFrame)
637
+ 'non_current_debt': 歷年當季非流動負債 (pd.DataFrame)
638
+ 'equity': : 歷年當季權益 (pd.DataFrame)
639
+ }
640
+ """
641
+ today_dict = self._get_today()
642
+
643
+ today = today_dict['today'].strftime("%Y-%m-%d")
644
+
645
+ start_date = "2014-01-01"
646
+
647
+ query_data = self.query_seasonal_data(ticker, start_date, today,
648
+ "balance_sheet")
649
+
650
+ return_dict = {
651
+ "ticker": query_data[0]['ticker'],
652
+ "company_name": query_data[0]['company_name'],
653
+ }
654
+
655
+ index_names = []
656
+
657
+ table_dict = dict()
658
+ total_asset_dict = dict()
659
+ current_asset_dict = dict()
660
+ non_current_asset_dict = dict()
661
+ current_debt_dict = dict()
662
+ non_current_debt_dict = dict()
663
+ equity_dict = dict()
664
+
665
+ this_season = query_data[-1]['seasonal_data']['season']
666
+
667
+ value_type_list = ['value', 'percentage']
668
+
669
+ for data in query_data:
670
+ year = data['seasonal_data']['year']
671
+ season = data['seasonal_data']['season']
672
+
673
+ time_index = f"{year}Q{season}"
674
+
675
+ if (season == this_season):
676
+ try:
677
+ table_dict[time_index] = data['seasonal_data'][
678
+ 'balance_sheet']
679
+ except:
680
+ table_dict[time_index] = dict()
681
+ table_dict[time_index] = data['seasonal_data'][
682
+ 'balance_sheet']
683
+
684
+ try:
685
+ total_asset_dict[time_index] = {
686
+ "total_asset":
687
+ data['seasonal_data']['balance_sheet']['資產總額'],
688
+ "total_debt":
689
+ data['seasonal_data']['balance_sheet']['負債總額'],
690
+ "total_equity":
691
+ data['seasonal_data']['balance_sheet']['權益總額'],
692
+ }
693
+ except:
694
+ total_asset_dict[time_index] = {
695
+ "total_asset": None,
696
+ "total_debt": None,
697
+ "total_equity": None,
698
+ }
699
+
700
+ for value_type in value_type_list:
701
+ try:
702
+ current_asset_dict[
703
+ f"{time_index}_{value_type}"] = data[
704
+ 'seasonal_data']['balance_sheet']['流動資產合計'][
705
+ value_type]
706
+ except:
707
+ if (time_index not in current_asset_dict.keys()):
708
+ current_asset_dict[
709
+ f"{time_index}_{value_type}"] = None
710
+
711
+ try:
712
+ non_current_asset_dict[
713
+ f"{time_index}_{value_type}"] = data[
714
+ 'seasonal_data']['balance_sheet']['非流動資產合計'][
715
+ value_type]
716
+ except:
717
+ non_current_asset_dict[
718
+ f"{time_index}_{value_type}"] = None
719
+
720
+ try:
721
+ current_debt_dict[f"{time_index}_{value_type}"] = data[
722
+ 'seasonal_data']['balance_sheet']['流動負債合計'][
723
+ value_type]
724
+ except:
725
+ current_debt_dict[f"{time_index}_{value_type}"] = None
726
+
727
+ try:
728
+ non_current_debt_dict[
729
+ f"{time_index}_{value_type}"] = data[
730
+ 'seasonal_data']['balance_sheet']['非流動負債合計'][
731
+ value_type]
732
+ except:
733
+ non_current_debt_dict[
734
+ f"{time_index}_{value_type}"] = None
735
+
736
+ try:
737
+ equity_dict[f"{time_index}_{value_type}"] = data[
738
+ 'seasonal_data']['balance_sheet']['權益合計'][
739
+ value_type]
740
+ except:
741
+ equity_dict[f"{time_index}_{value_type}"] = None
742
+
743
+ index_names += list(
744
+ data['seasonal_data']['balance_sheet'].keys())
745
+
746
+ index_names = list(dict.fromkeys(index_names))
747
+
748
+ balance_sheet_table = pd.DataFrame(table_dict)
749
+ balance_sheet_table = self._expand_value_percentage(
750
+ balance_sheet_table)
751
+
752
+ total_asset_table = pd.DataFrame(total_asset_dict)
753
+ total_asset_table = self._expand_value_percentage(total_asset_table)
754
+
755
+ current_asset_table = pd.DataFrame(current_asset_dict,
756
+ index=['current_asset'])
757
+ non_current_asset_table = pd.DataFrame(non_current_asset_dict,
758
+ index=['non_current_asset'])
759
+ current_debt_table = pd.DataFrame(non_current_asset_dict,
760
+ index=['current_debt'])
761
+ non_current_debt_table = pd.DataFrame(non_current_asset_dict,
762
+ index=['non_current_debt'])
763
+ equity_table = pd.DataFrame(non_current_asset_dict, index=['equity'])
764
+
765
+ return_dict['balance_sheet'] = balance_sheet_table
766
+ return_dict['total_asset'] = total_asset_table
767
+ return_dict['current_asset'] = current_asset_table
768
+ return_dict['non_current_asset'] = non_current_asset_table
769
+ return_dict['current_debt'] = current_debt_table
770
+ return_dict['non_current_debt'] = non_current_debt_table
771
+ return_dict['equity'] = equity_table
772
+ return return_dict
773
+
774
+ def _gen_dict(self,
775
+ query_data,
776
+ target_season,
777
+ keys,
778
+ calculate_type='value',
779
+ calculate_grand_total=False):
780
+ """
781
+ Will be deprecated
782
+ """
783
+ assert(calculate_type in ['growth_rate', 'value', 'percentage']), "args: calculate_type Error"
784
+ table_dict = dict()
785
+ grand_total_dict = dict() if (calculate_grand_total) else None
786
+
787
+ for data in query_data:
788
+ if (calculate_grand_total
789
+ and data['seasonal_data']['season'] <= target_season):
790
+ time_index = f"{data['seasonal_data']['year']}Q{target_season}"
791
+ profit_lose = data['seasonal_data']['profit_lose']
792
+
793
+ for key in keys:
794
+ try:
795
+ if (calculate_type in ['growth_rate']):
796
+ for growth_rate in ['YoY_1', 'YoY_3', 'YoY_5', 'YoY_10']:
797
+ try:
798
+ grand_total_dict[time_index][
799
+ growth_rate] += profit_lose[key][growth_rate]
800
+ except Exception:
801
+ if (time_index not in
802
+ grand_total_dict.keys()):
803
+ grand_total_dict[time_index] = {
804
+ "YoY": None,
805
+ "YoY_3": None,
806
+ "YoY_5": None,
807
+ "YoY_10": None,
808
+ }
809
+ grand_total_dict[time_index][
810
+ growth_rate] = profit_lose[key][growth_rate]
811
+
812
+ elif (calculate_type in ['percentage']):
813
+ grand_total_dict[time_index] += profit_lose[key][
814
+ calculate_type] / target_season
815
+ else:
816
+ grand_total_dict[time_index] += profit_lose[key][
817
+ calculate_type]
818
+ break
819
+ except KeyError:
820
+ try:
821
+ if (calculate_type
822
+ in ['percentage']):
823
+ grand_total_dict[time_index] = profit_lose[
824
+ key][calculate_type] / target_season
825
+ else:
826
+ grand_total_dict[time_index] = profit_lose[
827
+ key][calculate_type]
828
+ break
829
+ except: # key in profit_lose not found or not growth_rate not implemented
830
+ continue
831
+ except Exception: # Other exceotion
832
+ continue
833
+ else: # All keys not found
834
+ grand_total_dict[time_index] = None
835
+
836
+ if (data['seasonal_data']['season'] == target_season):
837
+ time_index = f"{data['seasonal_data']['year']}Q{target_season}"
838
+ profit_lose = data['seasonal_data']['profit_lose']
839
+
840
+ for key in keys:
841
+ try:
842
+ if (calculate_type in ['growth_rate']):
843
+ for item in items:
844
+ table_dict[time_index][item] = profit_dict[key][item]
845
+ else:
846
+ table_dict[time_index] = profit_lose[key][
847
+ calculate_type]
848
+ break
849
+ except Exception:
850
+ continue
851
+ else:
852
+ if (calculate_type == 'growth_rate'):
853
+ table_dict[time_index] = {
854
+ "YoY_1": None,
855
+ "YoY_3": None,
856
+ "YoY_5": None,
857
+ "YoY_10": None
858
+ }
859
+ else:
860
+ table_dict[time_index] = None
861
+ return table_dict, grand_total_dict
862
+
863
+ def _slice_multi_col_table(
864
+ self,
865
+ total_table,
866
+ mode='value',
867
+ target_index=None, # None or Str, 要特別抓哪個index
868
+ ):
869
+ times = total_table.columns.get_level_values(0).unique()
870
+ try:
871
+ target_metrics = self.target_metric_dict[mode]
872
+ except KeyError as e:
873
+ return f"mode Error: Get mode should be {list(self.target_metric_dict.keys())} but get {mode}"
874
+
875
+ desired_order = [
876
+ (time, value_name) for time in times for value_name in target_metrics
877
+ ]
878
+
879
+ if (target_index):
880
+ sliced_table = total_table.loc[[target_index], pd.IndexSlice[:, target_metrics]][desired_order].T
881
+ sliced_table = sliced_table.reset_index()
882
+ sliced_table = sliced_table.pivot(index='level_1', columns='level_0', values=target_index)
883
+ sliced_table.columns.name = None
884
+ sliced_table.index.name = None
885
+ return sliced_table.reindex(target_metrics)
886
+
887
+ else:
888
+ return total_table.loc[:, pd.IndexSlice[:, target_metrics]][desired_order]
889
+
890
+ def get_profit_lose(self, ticker):
891
+ """
892
+ ticker: str
893
+ iFa.ai: 財務分析 -> 損益表
894
+ """
895
+ today_dict = self._get_today()
896
+
897
+ with open(f"./tools/profit_lose.yaml", 'r') as f:
898
+ table_settings = yaml.safe_load(f)
899
+ today = today_dict['today'].strftime("%Y-%m-%d")
900
+ start_date = "2014-01-01"
901
+
902
+ this_season = ((today_dict['month'] - 1) // 3)
903
+ this_season = 4 if (this_season == 0) else this_season - 1
904
+ # TODO: 將這裡改成根據每公司的最後更新季度
905
+
906
+ query_data = self.query_seasonal_data(ticker,
907
+ start_date=start_date,
908
+ end_date=today,
909
+ sheet='profit_lose',
910
+ target_season=this_season)
911
+
912
+ index_names = []
913
+
914
+ return_dict = {
915
+ "ticker": query_data[0]['ticker'],
916
+ "company_name": query_data[0]['company_name'],
917
+ }
918
+
919
+ table_dict = dict()
920
+ grand_total_dict = dict()
921
+
922
+ column_names = []
923
+
924
+ for data in query_data:
925
+ year = data['seasonal_data']['year']
926
+ season = data['seasonal_data']['season']
927
+
928
+ time_index = f"{year}Q{season}"
929
+
930
+ index_names += list(
931
+ data['seasonal_data']['profit_lose'].keys())
932
+
933
+ profit_lose = data['seasonal_data']['profit_lose']
934
+
935
+ for index_name, value_dict in profit_lose.items():
936
+ column_names += [
937
+ (time_index, index_name, item_name)
938
+ for item_name in value_dict.keys()
939
+ ]
940
+ for item_name, item in value_dict.items():
941
+ try:
942
+ table_dict[index_name][(time_index, item_name)] = item
943
+ #[time_index][index_name][item_name] = item
944
+
945
+ except KeyError:
946
+ if (index_name not in table_dict.keys()):
947
+ table_dict[index_name] = dict()
948
+ grand_total_dict[index_name] = dict()
949
+
950
+ table_dict[index_name][(time_index, item_name)] = item
951
+
952
+ columns = pd.MultiIndex.from_tuples(table_dict.keys())
953
+ total_table = pd.DataFrame.from_dict(
954
+ table_dict,
955
+ orient='index'
956
+ )
957
+
958
+ total_table.columns = pd.MultiIndex.from_tuples(total_table.columns)
959
+
960
+ for name, setting in table_settings.items():
961
+ return_dict[name] = self._slice_multi_col_table(
962
+ total_table=total_table,
963
+ mode=setting['mode'],
964
+ target_index=setting['target_index'] if "target_index" in setting.keys() else None
965
+ )
966
+
967
+ return return_dict
968
+
969
+ def get_cash_flow(self, ticker):
970
+ """
971
+ iFa.ai: 財務分析 -> 現金金流表
972
+ """
973
+ today_dict = self._get_today()
974
+
975
+ today = today_dict['today'].strftime("%Y-%m-%d")
976
+ start_date = "2014-01-01"
977
+
978
+ query_data = self.query_seasonal_data(ticker,
979
+ start_date=start_date,
980
+ end_date=today,
981
+ sheet='cash_flow')
982
+
983
+ index_names = []
984
+
985
+ return_dict = {
986
+ "ticker": query_data[0]['ticker'],
987
+ "company_name": query_data[0]['company_name'],
988
+ }
989
+
990
+ table_dict = dict()
991
+ CASHO_dict = dict() # 營業活動
992
+ CASHI_dict = dict() # 投資活動
993
+ CASHF_dict = dict() # 籌資活動
994
+
995
+ this_season = query_data[-1]['seasonal_data']['season']
996
+
997
+ checkpoints = ["營業活動之現金流量-間接法", "投資活動之現金流量", "籌資活動之現金流量"]
998
+ main_cash_flows = [
999
+ "營業活動之淨現金流入(流出)", "投資活動之淨現金流入(流出)", "籌資活動之淨現金流入(流出)"
1000
+ ]
1001
+
1002
+ partial_cash_flows = [CASHO_dict, CASHI_dict, CASHF_dict]
1003
+
1004
+ for data in query_data:
1005
+ year = data['seasonal_data']['year']
1006
+ season = data['seasonal_data']['season']
1007
+
1008
+ time_index = f"{year}Q{season}"
1009
+
1010
+ if (season == this_season):
1011
+ cash_flow = data['seasonal_data']['cash_flow']
1012
+ main_cash_flow_name = None
1013
+ partial_cash_flow = None
1014
+ next_checkpoint = 0
1015
+
1016
+ for index_name, value in cash_flow.items():
1017
+ if (next_checkpoint < 3
1018
+ and index_name == checkpoints[next_checkpoint]):
1019
+ main_cash_flow_name = main_cash_flows[next_checkpoint]
1020
+ partial_cash_flow = partial_cash_flows[next_checkpoint]
1021
+ next_checkpoint += 1
1022
+ try:
1023
+ table_dict[time_index][index_name]['value'] = value[
1024
+ 'value']
1025
+ if (value['value']):
1026
+ table_dict[time_index][index_name][
1027
+ 'percentage'] = value['value'] / cash_flow[
1028
+ main_cash_flow_name]['value']
1029
+ else:
1030
+ table_dict[time_index][index_name][
1031
+ 'percentage'] = None
1032
+ except:
1033
+ if (time_index not in table_dict.keys()):
1034
+ table_dict[time_index] = dict()
1035
+ table_dict[time_index][index_name] = dict()
1036
+
1037
+ table_dict[time_index][index_name]['value'] = value[
1038
+ 'value']
1039
+ if (value['value']):
1040
+ table_dict[time_index][index_name][
1041
+ 'percentage'] = value['value'] / cash_flow[
1042
+ main_cash_flow_name]['value']
1043
+ else:
1044
+ table_dict[time_index][index_name][
1045
+ 'percentage'] = None
1046
+
1047
+ try:
1048
+ partial_cash_flow[time_index][index_name] = table_dict[
1049
+ time_index][index_name]
1050
+ except:
1051
+ if (time_index not in partial_cash_flow.keys()):
1052
+ partial_cash_flow[time_index] = dict()
1053
+ partial_cash_flow[time_index][index_name] = table_dict[
1054
+ time_index][index_name]
1055
+
1056
+ index_names += list(cash_flow.keys())
1057
+
1058
+ index_names = list(dict.fromkeys(index_names))
1059
+
1060
+ cash_flow_table = pd.DataFrame(table_dict)
1061
+ cash_flow_table = self._expand_value_percentage(cash_flow_table)
1062
+
1063
+ CASHO_table = pd.DataFrame(CASHO_dict)
1064
+ CASHO_table = self._expand_value_percentage(CASHO_table)
1065
+
1066
+ CASHI_table = pd.DataFrame(CASHI_dict)
1067
+ CASHI_table = self._expand_value_percentage(CASHI_table)
1068
+
1069
+ CASHF_table = pd.DataFrame(CASHF_dict)
1070
+ CASHF_table = self._expand_value_percentage(CASHF_table)
1071
+
1072
+ return_dict['cash_flow'] = cash_flow_table
1073
+ return_dict['CASHO'] = CASHO_table
1074
+ return_dict['CASHI'] = CASHI_table
1075
+ return_dict['CASHF'] = CASHF_table
1076
+
1077
+ return return_dict
1078
+
1079
+ def get_financial_sheet(self, ticker):
1080
+ """
1081
+ (尚未完成)
1082
+ iFa.ai: 財務分析 -> 重要指標
1083
+ """
1084
+ # Not implemented !!!!
1085
+ return
1086
+ today = datetime.today()
1087
+ this_year = today.year
1088
+ start_date = (f"{this_year}-01-01")
1089
+ today = today.strftime("%Y-%m-%d")
1090
+
1091
+ this_year_data = self.query_seasonal_data(ticker, start_date, today)
1092
+
1093
+ latest_seasonal_data = this_year_data["seasonal_data"][-1]
1094
+ all_time_data = this_year_data['seasonal_data']
1095
+
1096
+ # retrieved_list = [
1097
+ # "營業收入合計",
1098
+ # "營業毛利(毛損)淨額",
1099
+ # "營業利益(損失)",
1100
+ # "本期淨利(淨損)",
1101
+ # "營業活動之淨現金流入(流出)",
1102
+ # "投資活動之淨現金流入(流出)",
1103
+ # "籌資活動之淨現金流入(流出)",
1104
+ # ]
1105
+
1106
+ try:
1107
+ single_month_revenue_dict = {
1108
+ "revenue": {
1109
+ "value":
1110
+ latest_seasonal_data['profit_lose']["營業收入合計"],
1111
+ "accumulation":
1112
+ self._accumulate(all_time_data, 'profit_lose', "營業收入合計"),
1113
+ "name":
1114
+ '營業收入'
1115
+ }
1116
+ }
1117
+ except:
1118
+ try:
1119
+ single_month_revenue_dict = {
1120
+ "revenue": {
1121
+ "value":
1122
+ latest_seasonal_data['profit_lose']["利息收入"],
1123
+ "accumulation":
1124
+ self._accumulate(all_time_data, 'profit_lose', "利息收入"),
1125
+ "name":
1126
+ '利息收入'
1127
+ }
1128
+ }
1129
+ except:
1130
+ single_month_revenue_dict = {
1131
+ "revenue": {
1132
+ "value": None,
1133
+ "accumulation": None,
1134
+ "name": "營業收入"
1135
+ }
1136
+ }
1137
+ return_dict.update()