neurostats-API 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.
@@ -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()