neurostats-API 0.0.9__tar.gz → 0.0.11__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. neurostats_API-0.0.9/README.md → neurostats_API-0.0.11/PKG-INFO +20 -3
  2. neurostats_API-0.0.9/PKG-INFO → neurostats_API-0.0.11/README.md +10 -13
  3. neurostats_API-0.0.11/neurostats_API/__init__.py +1 -0
  4. {neurostats_API-0.0.9 → neurostats_API-0.0.11}/neurostats_API/fetchers/__init__.py +1 -0
  5. {neurostats_API-0.0.9 → neurostats_API-0.0.11}/neurostats_API/fetchers/balance_sheet.py +19 -9
  6. {neurostats_API-0.0.9 → neurostats_API-0.0.11}/neurostats_API/fetchers/cash_flow.py +8 -6
  7. {neurostats_API-0.0.9 → neurostats_API-0.0.11}/neurostats_API/fetchers/finance_overview.py +95 -32
  8. neurostats_API-0.0.11/neurostats_API/fetchers/institution.py +214 -0
  9. {neurostats_API-0.0.9 → neurostats_API-0.0.11}/neurostats_API/fetchers/month_revenue.py +23 -7
  10. {neurostats_API-0.0.9 → neurostats_API-0.0.11}/neurostats_API/fetchers/profit_lose.py +21 -9
  11. neurostats_API-0.0.11/neurostats_API/tools/balance_sheet.yaml +34 -0
  12. {neurostats_API-0.0.9 → neurostats_API-0.0.11}/neurostats_API/tools/finance_overview_dict.yaml +76 -34
  13. neurostats_API-0.0.11/neurostats_API/tools/profit_lose.yaml +121 -0
  14. {neurostats_API-0.0.9 → neurostats_API-0.0.11}/neurostats_API/tools/seasonal_data_field_dict.txt +16 -1
  15. {neurostats_API-0.0.9 → neurostats_API-0.0.11}/neurostats_API/utils/data_process.py +13 -2
  16. {neurostats_API-0.0.9 → neurostats_API-0.0.11}/neurostats_API.egg-info/PKG-INFO +11 -4
  17. {neurostats_API-0.0.9 → neurostats_API-0.0.11}/neurostats_API.egg-info/SOURCES.txt +1 -0
  18. {neurostats_API-0.0.9 → neurostats_API-0.0.11}/setup.py +1 -1
  19. {neurostats_API-0.0.9 → neurostats_API-0.0.11}/test/test_fetchers.py +42 -17
  20. neurostats_API-0.0.9/neurostats_API/__init__.py +0 -1
  21. neurostats_API-0.0.9/neurostats_API/tools/balance_sheet.yaml +0 -26
  22. neurostats_API-0.0.9/neurostats_API/tools/profit_lose.yaml +0 -93
  23. {neurostats_API-0.0.9 → neurostats_API-0.0.11}/MANIFEST.in +0 -0
  24. {neurostats_API-0.0.9 → neurostats_API-0.0.11}/neurostats_API/cli.py +0 -0
  25. {neurostats_API-0.0.9 → neurostats_API-0.0.11}/neurostats_API/fetchers/base.py +0 -0
  26. {neurostats_API-0.0.9 → neurostats_API-0.0.11}/neurostats_API/fetchers/tech.py +0 -0
  27. {neurostats_API-0.0.9 → neurostats_API-0.0.11}/neurostats_API/fetchers/value_invest.py +0 -0
  28. {neurostats_API-0.0.9 → neurostats_API-0.0.11}/neurostats_API/main.py +0 -0
  29. {neurostats_API-0.0.9 → neurostats_API-0.0.11}/neurostats_API/tools/cash_flow_percentage.yaml +0 -0
  30. {neurostats_API-0.0.9 → neurostats_API-0.0.11}/neurostats_API/utils/__init__.py +0 -0
  31. {neurostats_API-0.0.9 → neurostats_API-0.0.11}/neurostats_API/utils/datetime.py +0 -0
  32. {neurostats_API-0.0.9 → neurostats_API-0.0.11}/neurostats_API/utils/db_client.py +0 -0
  33. {neurostats_API-0.0.9 → neurostats_API-0.0.11}/neurostats_API/utils/fetcher.py +0 -0
  34. {neurostats_API-0.0.9 → neurostats_API-0.0.11}/neurostats_API.egg-info/dependency_links.txt +0 -0
  35. {neurostats_API-0.0.9 → neurostats_API-0.0.11}/neurostats_API.egg-info/top_level.txt +0 -0
  36. {neurostats_API-0.0.9 → neurostats_API-0.0.11}/setup.cfg +0 -0
@@ -1,3 +1,13 @@
1
+ Metadata-Version: 2.1
2
+ Name: neurostats_API
3
+ Version: 0.0.11
4
+ Summary: The service of NeuroStats website
5
+ Home-page: https://github.com/NeurowattStats/NeuroStats_API.git
6
+ Author: JasonWang@Neurowatt
7
+ Author-email: jason@neurowatt.ai
8
+ Requires-Python: >=3.6
9
+ Description-Content-Type: text/markdown
10
+
1
11
  # neurostats_API
2
12
 
3
13
  - [檔案架構](#檔案架構)
@@ -9,6 +19,8 @@
9
19
  - [損益表](#損益表)
10
20
  - [資產負債表](#資產負債表)
11
21
  - [現金流量表](#現金流量表)
22
+ - [版本紀錄](#版本紀錄)
23
+
12
24
 
13
25
  ## 檔案架構
14
26
 
@@ -68,7 +80,7 @@ pip install neurostats-API
68
80
  ```Python
69
81
  >>> import neurostats_API
70
82
  >>> print(neurostats_API.__version__)
71
- 0.0.9
83
+ 0.0.10
72
84
  ```
73
85
 
74
86
  ### 得到最新一期的評價資料與歷年評價
@@ -424,6 +436,11 @@ stats_fetcher.query()
424
436
  > 大部分資料缺失是因為尚未計算,僅先填上已經有的資料
425
437
 
426
438
 
427
- ## TODO
428
- - 將utils/fetcher.py中的功能切分到fetchers資料夾中
439
+ ## 版本紀錄
440
+ ### 0.0.10
441
+ - 更新指標的資料型態: 單位為千元乘以1000之後回傳整數
442
+
443
+ - 處理銀行公司在finanace_overview會報錯誤的問題(未完全解決,因銀行公司財報有許多名稱不同,目前都會顯示為None)
429
444
 
445
+ ### 0.0.9
446
+ - 更新指標的資料型態: 單位為日, %, 倍轉為字串
@@ -1,13 +1,3 @@
1
- Metadata-Version: 2.1
2
- Name: neurostats_API
3
- Version: 0.0.9
4
- Summary: The service of NeuroStats website
5
- Home-page: https://github.com/NeurowattStats/NeuroStats_API.git
6
- Author: JasonWang@Neurowatt
7
- Author-email: jason@neurowatt.ai
8
- Requires-Python: >=3.6
9
- Description-Content-Type: text/markdown
10
-
11
1
  # neurostats_API
12
2
 
13
3
  - [檔案架構](#檔案架構)
@@ -19,6 +9,8 @@ Description-Content-Type: text/markdown
19
9
  - [損益表](#損益表)
20
10
  - [資產負債表](#資產負債表)
21
11
  - [現金流量表](#現金流量表)
12
+ - [版本紀錄](#版本紀錄)
13
+
22
14
 
23
15
  ## 檔案架構
24
16
 
@@ -78,7 +70,7 @@ pip install neurostats-API
78
70
  ```Python
79
71
  >>> import neurostats_API
80
72
  >>> print(neurostats_API.__version__)
81
- 0.0.9
73
+ 0.0.10
82
74
  ```
83
75
 
84
76
  ### 得到最新一期的評價資料與歷年評價
@@ -434,6 +426,11 @@ stats_fetcher.query()
434
426
  > 大部分資料缺失是因為尚未計算,僅先填上已經有的資料
435
427
 
436
428
 
437
- ## TODO
438
- - 將utils/fetcher.py中的功能切分到fetchers資料夾中
429
+ ## 版本紀錄
430
+ ### 0.0.10
431
+ - 更新指標的資料型態: 單位為千元乘以1000之後回傳整數
432
+
433
+ - 處理銀行公司在finanace_overview會報錯誤的問題(未完全解決,因銀行公司財報有許多名稱不同,目前都會顯示為None)
439
434
 
435
+ ### 0.0.9
436
+ - 更新指標的資料型態: 單位為日, %, 倍轉為字串
@@ -0,0 +1 @@
1
+ __version__='0.0.11'
@@ -2,6 +2,7 @@ from .base import StatsDateTime, StatsFetcher
2
2
  from .balance_sheet import BalanceSheetFetcher
3
3
  from .cash_flow import CashFlowFetcher
4
4
  from .finance_overview import FinanceOverviewFetcher
5
+ from .institution import InstitutionFetcher
5
6
  from .month_revenue import MonthRevenueFetcher
6
7
  from .profit_lose import ProfitLoseFetcher
7
8
  from .value_invest import ValueFetcher
@@ -116,10 +116,13 @@ class BalanceSheetFetcher(StatsFetcher):
116
116
  try: # table_dict[項目][(2020Q1, '%')]
117
117
  if (item_name == 'percentage'):
118
118
  if (isinstance(item, (float, int))):
119
- item = np.round(item, 2)
120
- if ("YoY" in item_name):
119
+ item = StatsProcessor.cal_non_percentage(item, to_str=True, postfix="%")
120
+ elif ("YoY" in item_name):
121
121
  if (isinstance(item, (float, int))):
122
- item = np.round(item * 100, 2)
122
+ item = StatsProcessor.cal_percentage(item)
123
+ else:
124
+ if (isinstance(item, (float, int))):
125
+ item = StatsProcessor.cal_non_percentage(item, postfix="千元")
123
126
  table_dict[index_name][(time_index, item_name)] = item
124
127
 
125
128
  except KeyError:
@@ -132,10 +135,17 @@ class BalanceSheetFetcher(StatsFetcher):
132
135
  total_table.columns = pd.MultiIndex.from_tuples(total_table.columns)
133
136
 
134
137
  for name, setting in self.table_settings.items():
135
- return_dict[name] = StatsProcessor.slice_multi_col_table(
136
- total_table=total_table,
137
- mode=setting['mode'],
138
- target_index=setting['target_index']
139
- if "target_index" in setting.keys() else None)
140
-
138
+ if ('target_index' in setting.keys()):
139
+ target_indexes = [target_index.strip() for target_index in setting['target_index']]
140
+ else:
141
+ target_indexes = [None]
142
+ for target_index in target_indexes:
143
+ try:
144
+ return_dict[name] = StatsProcessor.slice_multi_col_table(
145
+ total_table=total_table,
146
+ mode=setting['mode'],
147
+ target_index=target_index)
148
+ break
149
+ except Exception as e:
150
+ continue
141
151
  return return_dict
@@ -132,14 +132,15 @@ class CashFlowFetcher(StatsFetcher):
132
132
  table_dict[time_index][index_name]['value'] = value[
133
133
  'value']
134
134
  if (value['value']):
135
- table_dict[time_index][index_name][
136
- 'percentage'] = np.round(
135
+ ratio = np.round(
137
136
  (value['value'] / cash_flow[
138
137
  main_cash_flow_name]['value']) * 100, 2)
138
+ table_dict[time_index][index_name][
139
+ 'percentage'] = f"{ratio}%"
139
140
  else:
140
141
  table_dict[time_index][index_name][
141
142
  'percentage'] = None
142
- except:
143
+ except: # 新增index再做一次
143
144
  if (time_index not in table_dict.keys()):
144
145
  table_dict[time_index] = dict()
145
146
  table_dict[time_index][index_name] = dict()
@@ -147,14 +148,15 @@ class CashFlowFetcher(StatsFetcher):
147
148
  table_dict[time_index][index_name]['value'] = value[
148
149
  'value']
149
150
  if (value['value']):
150
- table_dict[time_index][index_name][
151
- 'percentage'] = np.round(
151
+ ratio = np.round(
152
152
  (value['value'] / cash_flow[
153
153
  main_cash_flow_name]['value']) * 100, 2)
154
+ table_dict[time_index][index_name][
155
+ 'percentage'] = f"{ratio}%"
154
156
  else:
155
157
  table_dict[time_index][index_name][
156
158
  'percentage'] = None
157
-
159
+ table_dict[time_index][index_name]['value'] = StatsProcessor.cal_non_percentage(value['value'], postfix="千元")
158
160
  try:
159
161
  partial_cash_flow[time_index][index_name] = table_dict[
160
162
  time_index][index_name]
@@ -31,15 +31,19 @@ class FinanceOverviewFetcher(StatsFetcher):
31
31
 
32
32
  for key, target_sets in self.target_fields.items():
33
33
  try:
34
- small_target = target_sets['field']
35
- big_target = self.inverse_dict[
36
- small_target] # balance_sheet/profit_lose/cash_flow
34
+ small_targets = target_sets['field']
35
+
37
36
  value_index = target_sets['value'] # "金額" or "%"
38
37
 
39
- target_query.update({
40
- f"{key}":
41
- f"$$target_season_data.{big_target}.{small_target}.{value_index}"
42
- })
38
+ for small_target in small_targets:
39
+ big_target = self.inverse_dict[
40
+ small_target] # balance_sheet/profit_lose/cash_flow
41
+ if (small_target == "利息費用_bank"):
42
+ small_target = small_target[:small_target.find("_bank")]
43
+ target_query.update({
44
+ f"{key}":
45
+ f"$$target_season_data.{big_target}.{small_target}.{value_index}"
46
+ })
43
47
  except Exception:
44
48
  continue
45
49
 
@@ -98,8 +102,16 @@ class FinanceOverviewFetcher(StatsFetcher):
98
102
  finance_dict = fetched_data['seasonal_data'][0]
99
103
  FinanceOverviewProcessor.process_rate(finance_dict)
100
104
  FinanceOverviewProcessor.process_all(finance_dict)
105
+ self.fill_nan_index(finance_dict)
106
+ FinanceOverviewProcessor.process_thousand_dollar(finance_dict)
101
107
  fetched_data['seasonal_data'] = finance_dict
108
+
102
109
  return fetched_data
110
+
111
+ def fill_nan_index(self, finance_dict):
112
+ for key in self.target_fields.keys():
113
+ if (key not in finance_dict.keys()):
114
+ finance_dict[key] = None
103
115
 
104
116
 
105
117
  class FinanceOverviewProcessor(StatsProcessor):
@@ -116,6 +128,35 @@ class FinanceOverviewProcessor(StatsProcessor):
116
128
  else:
117
129
  finance_dict[key] = StatsProcessor.cal_non_percentage(
118
130
  finance_dict[key])
131
+
132
+
133
+ @classmethod
134
+ def process_thousand_dollar(cls, finance_dict):
135
+ process_index = [
136
+ "revenue",
137
+ "gross_profit",
138
+ "operating_income",
139
+ "net_income",
140
+ "operating_cash_flow",
141
+ "invest_cash_flow",
142
+ "financing_cash_flow",
143
+ "fcf",
144
+
145
+ 'current_assets',
146
+ 'current_liabilities',
147
+ 'non_current_assets',
148
+ 'non_current_liabilities',
149
+ 'total_assets',
150
+ "total_liabilities",
151
+ "equity"
152
+ ]
153
+
154
+ for index in process_index:
155
+ try:
156
+ finance_dict[index] = StatsProcessor.cal_non_percentage(finance_dict[index], postfix="千元")
157
+ except Exception as e:
158
+ finance_dict[index] = None
159
+
119
160
 
120
161
  @classmethod
121
162
  def process_all(cls, finance_dict):
@@ -135,12 +176,13 @@ class FinanceOverviewProcessor(StatsProcessor):
135
176
  cls.cal_quick_ratio, cls.cal_debt_to_equity_ratio,
136
177
  cls.cal_net_debt_to_equity_ratio, cls.cal_interest_coverage_ratio,
137
178
  cls.cal_debt_to_operating_cash_flow,
138
- cls.cal_debt_to_free_cash_flow, cls.cal_cash_flow_ratio
179
+ cls.cal_debt_to_free_cash_flow, cls.cal_cash_flow_ratio,
139
180
  ]
140
181
 
141
182
  for method in methods:
142
183
  method(finance_dict)
143
184
 
185
+
144
186
  @classmethod
145
187
  def cal_EBIT(cls, finance_dict):
146
188
  """
@@ -224,7 +266,14 @@ class FinanceOverviewProcessor(StatsProcessor):
224
266
  計算每股毛利
225
267
  = (當期營業毛利)÷(當期在外流通股數)
226
268
  """
227
-
269
+ if ('gross_profit' not in finance_dict.keys()):
270
+ try:
271
+ finance_dict['gross_profit'] = (
272
+ finance_dict['revenue'] -
273
+ finance_dict['operating_cost']
274
+ )
275
+ except:
276
+ finance_dict['gross_profit'] = None
228
277
  try:
229
278
  gross_per_share = (finance_dict['gross_profit'] /
230
279
  finance_dict['share_outstanding'])
@@ -267,7 +316,7 @@ class FinanceOverviewProcessor(StatsProcessor):
267
316
  operating_cash_flow_per_share)
268
317
  except (KeyError, ZeroDivisionError, TypeError) as e:
269
318
  finance_dict['operating_cash_flow_per_share'] = None
270
- print(f'operating_cash_flow_per_share because of {str(e)}')
319
+ # print(f'operating_cash_flow_per_share because of {str(e)}')
271
320
 
272
321
  @classmethod
273
322
  def fcf_per_share(cls, finance_dict):
@@ -292,12 +341,15 @@ class FinanceOverviewProcessor(StatsProcessor):
292
341
  計算資產報酬率(ROA)
293
342
  ROA = [ 本期淨利 + 利息費用 × (1-有效稅率) ] ÷(資產總額)
294
343
  """
295
- roa = (
296
- finance_dict['net_income'] + finance_dict['interest'] +
297
- (1 * 0.1) # 有效稅率需要改,這裡先設0.1
298
- ) / finance_dict['inventories']
344
+ try:
345
+ roa = (
346
+ finance_dict['net_income'] + finance_dict['interest'] +
347
+ (1 * 0.1) # 有效稅率需要改,這裡先設0.1
348
+ ) / finance_dict['inventories']
299
349
 
300
- finance_dict["roa"] = StatsProcessor.cal_percentage(roa)
350
+ finance_dict["roa"] = StatsProcessor.cal_percentage(roa)
351
+ except Exception as e:
352
+ finance_dict["roa"] = None
301
353
 
302
354
  @classmethod
303
355
  def cal_roe(cls, finance_dict):
@@ -305,8 +357,11 @@ class FinanceOverviewProcessor(StatsProcessor):
305
357
  計算股東權益報酬率(ROE)
306
358
  ROE = (本期淨利) ÷(權益總額)
307
359
  """
308
- roe = (finance_dict['net_income'] / finance_dict['equity'])
309
- finance_dict['roe'] = StatsProcessor.cal_percentage(roe)
360
+ try:
361
+ roe = (finance_dict['net_income'] / finance_dict['equity'])
362
+ finance_dict['roe'] = StatsProcessor.cal_percentage(roe)
363
+ except Exception as e:
364
+ finance_dict['roe'] = None
310
365
 
311
366
  @classmethod
312
367
  def cal_gross_over_asset(cls, finance_dict):
@@ -315,7 +370,7 @@ class FinanceOverviewProcessor(StatsProcessor):
315
370
  """
316
371
  try:
317
372
  gross_over_asset = (finance_dict['gross_profit'] /
318
- finance_dict['total_asset'])
373
+ finance_dict['total_assets'])
319
374
  finance_dict['gross_over_asset'] = StatsProcessor.cal_percentage(
320
375
  gross_over_asset)
321
376
  except (KeyError, ZeroDivisionError, TypeError) as e:
@@ -331,7 +386,7 @@ class FinanceOverviewProcessor(StatsProcessor):
331
386
  try:
332
387
  roce = ((finance_dict['net_income_before_tax'] +
333
388
  finance_dict['interest']) /
334
- (finance_dict['total_asset'] -
389
+ (finance_dict['total_assets'] -
335
390
  finance_dict['current_liabilities']))
336
391
  finance_dict['roce'] = StatsProcessor.cal_percentage(roce)
337
392
 
@@ -351,7 +406,7 @@ class FinanceOverviewProcessor(StatsProcessor):
351
406
  finance_dict[
352
407
  'gross_profit_margin'] = StatsProcessor.cal_percentage(
353
408
  gross_profit_margin)
354
- except:
409
+ except Exception as e:
355
410
  finance_dict['gross_profit_margin'] = None
356
411
  print(f"gross_profit_margin failed because of {str(e)}")
357
412
 
@@ -409,7 +464,7 @@ class FinanceOverviewProcessor(StatsProcessor):
409
464
  (finance_dict['account_pay'] / finance_dict['revenue']))
410
465
  finance_dict['dso'] = StatsProcessor.cal_non_percentage(
411
466
  dso, to_str=True, postfix="日")
412
- except:
467
+ except Exception as e:
413
468
  finance_dict['dso'] = None
414
469
  print(f"Error calculating 應收帳款收現天數 because of {str(e)}")
415
470
 
@@ -419,11 +474,15 @@ class FinanceOverviewProcessor(StatsProcessor):
419
474
  計算應收帳款佔營收比率
420
475
  = 應收帳款平均餘額 ÷ 營業收入
421
476
  """
422
- account_receive_over_revenue = (finance_dict['account_receive'] /
423
- finance_dict['revenue'])
424
- finance_dict[
425
- "account_receive_over_revenue"] = StatsProcessor.cal_percentage(
426
- account_receive_over_revenue)
477
+ try:
478
+ account_receive_over_revenue = (finance_dict['account_receive'] /
479
+ finance_dict['revenue'])
480
+ finance_dict[
481
+ "account_receive_over_revenue"] = StatsProcessor.cal_percentage(
482
+ account_receive_over_revenue)
483
+ except Exception as e:
484
+ finance_dict[
485
+ "account_receive_over_revenue"] = None
427
486
 
428
487
  @classmethod
429
488
  def cal_dpo(cls, finance_dict):
@@ -513,10 +572,13 @@ class FinanceOverviewProcessor(StatsProcessor):
513
572
  計算資產周轉率
514
573
  營業收入 ÷ 資產總額
515
574
  """
516
- asset_turnover = (finance_dict["revenue"] /
517
- finance_dict["inventories"])
518
- finance_dict["asset_turnover"] = StatsProcessor.cal_percentage(
519
- asset_turnover)
575
+ try:
576
+ asset_turnover = (finance_dict["revenue"] /
577
+ finance_dict["inventories"])
578
+ finance_dict["asset_turnover"] = StatsProcessor.cal_percentage(
579
+ asset_turnover)
580
+ except Exception as e:
581
+ finance_dict["asset_turnover"] = None
520
582
 
521
583
  @classmethod
522
584
  def cal_application_turnover(cls, finance_dict):
@@ -528,11 +590,12 @@ class FinanceOverviewProcessor(StatsProcessor):
528
590
  applcation_turnover = (finance_dict['revenue'] /
529
591
  finance_dict["application"])
530
592
  finance_dict[
531
- 'applcation_turnover'] = StatsProcessor.cal_percentage(
593
+ 'application_turnover'] = StatsProcessor.cal_percentage(
532
594
  applcation_turnover)
533
595
 
534
- except (KeyError, ZeroDivisionError, TypeError) as e:
596
+ except Exception as e:
535
597
  finance_dict['application_turnover'] = None
598
+
536
599
 
537
600
  @classmethod
538
601
  def cal_current_ratio(cls, finance_dict):
@@ -0,0 +1,214 @@
1
+ from .base import StatsFetcher
2
+ from datetime import datetime, timedelta
3
+ import json
4
+ import numpy as np
5
+ import pandas as pd
6
+ from ..utils import StatsDateTime, StatsProcessor
7
+ import importlib.resources as pkg_resources
8
+ import yaml
9
+
10
+
11
+ class InstitutionFetcher(StatsFetcher):
12
+ """
13
+ iFa -> 交易資訊 -> 法人買賣
14
+
15
+ 包括:
16
+ 1. 當日交易
17
+ 2. 一年內交易
18
+ """
19
+
20
+ def __init__(self, ticker, db_client):
21
+ super().__init__(ticker, db_client)
22
+
23
+ def prepare_query(self, start_date, end_date):
24
+ pipeline = super().prepare_query()
25
+
26
+ # target_query = {
27
+ # "date": date,
28
+ # "institution_trading": "$$target_season_data.institution_trading"
29
+ # }
30
+
31
+ pipeline.append({
32
+ "$project": {
33
+ "_id": 0,
34
+ "ticker": 1,
35
+ "company_name": 1,
36
+ "daily_data": {
37
+ "$map": {
38
+ "input": {
39
+ "$filter": {
40
+ "input": "$daily_data",
41
+ "as": "daily",
42
+ "cond": {
43
+ "$and": [{
44
+ "$gte": ["$$daily.date", start_date]
45
+ }, {
46
+ "$lte": ["$$daily.date", end_date]
47
+ }]
48
+ }
49
+ }
50
+ },
51
+ "as": "target_daily_data",
52
+ "in": "$$target_daily_data"
53
+ }
54
+ }
55
+ }
56
+ })
57
+
58
+ return pipeline
59
+
60
+ def collect_data(self, start_date, end_date):
61
+ pipeline = self.prepare_query(start_date, end_date)
62
+
63
+ fetched_data = self.collection.aggregate(pipeline).to_list()
64
+
65
+ return fetched_data[-1]
66
+
67
+ def query_data(self):
68
+ try:
69
+ latest_time = StatsDateTime.get_latest_time(
70
+ self.ticker, self.collection)['last_update_time']
71
+ latest_date = latest_time['institution_trading'][
72
+ 'latest_date']
73
+ date = latest_date.replace(hour=0,
74
+ minute=0,
75
+ second=0,
76
+ microsecond=0)
77
+ except Exception as e:
78
+ print(
79
+ f"No updated time for institution_trading in {self.ticker}, use current time instead"
80
+ )
81
+ date = datetime.now(self.timezone)
82
+ date = date.replace(hour=0, minute=0, second=0, microsecond=0)
83
+
84
+ if (date.hour < 17): # 拿不到今天的資料
85
+ date = date - timedelta(days=1)
86
+
87
+ start_date = date - timedelta(days=365)
88
+
89
+ daily_data = self.collect_data(start_date, end_date=date)
90
+
91
+ daily_data = sorted(daily_data['daily_data'],
92
+ key=lambda x: x['date'],
93
+ reverse=True)
94
+
95
+ table_dict = self.process_data(daily_data)
96
+
97
+ return table_dict
98
+
99
+ def process_data(self, daily_data):
100
+ table_dict = dict()
101
+
102
+ latest_data = daily_data[0]
103
+ yesterday_data = daily_data[1]
104
+
105
+ # 交易價格與昨天交易
106
+ price_dict = {
107
+ "open": latest_data['open'],
108
+ 'close': latest_data['close'],
109
+ 'range': f"{latest_data['low']}-{latest_data['high']}",
110
+ 'volumn': latest_data['volume'] / 1000,
111
+ 'last_open': yesterday_data['open'],
112
+ 'last_close': yesterday_data['close'],
113
+ 'last_range': f"{yesterday_data['low']}-{yesterday_data['high']}",
114
+ 'last_volumn': yesterday_data['volume'] / 1000
115
+ }
116
+ # 一年範圍
117
+ annual_lows = [data['low'] for data in daily_data]
118
+ annual_highs = [data['high'] for data in daily_data]
119
+ lowest = np.min(annual_lows).item()
120
+ highest = np.max(annual_highs).item()
121
+
122
+ price_dict['52weeks_range'] = f"{lowest}-{highest}"
123
+ table_dict['price'] = price_dict
124
+
125
+ # 發行股數 & 市值
126
+
127
+ # 今日法人買賣
128
+ table_dict['latest_trading'] = {
129
+ "date":
130
+ daily_data[0]['date'],
131
+ "table":
132
+ self.process_latest_trading(daily_data[0]['institution_trading'], daily_data[0]['volume'])
133
+ }
134
+ # 一年內法人
135
+ annual_trading = [
136
+ {
137
+ **data['institution_trading'],
138
+ "收盤價": int(data['close'])
139
+ }
140
+ for data in daily_data
141
+ ] # 將close也併入這個表格
142
+ annual_dates = [data['date'] for data in daily_data]
143
+ table_dict['annual_trading'] = self.process_annual_trading(
144
+ annual_dates, annual_trading)
145
+
146
+ return table_dict
147
+
148
+ def process_latest_trading(self, latest_trading, volume):
149
+ latest_table = {
150
+ "foreign": self.default_institution_chart(),
151
+ "mutual": self.default_institution_chart(),
152
+ "prop": self.default_institution_chart(),
153
+ "institutional_investor":self.default_institution_chart(),
154
+ }
155
+
156
+ for key in latest_trading.keys():
157
+ if (key.find("外陸資") >= 0 or key.find("外資") >= 0):
158
+ self.target_institution(latest_trading, latest_table['foreign'], key, volume)
159
+ elif (key.find("自營商") >= 0):
160
+ self.target_institution(latest_trading,latest_table['prop'], key, volume)
161
+ elif (key.find("投信") >= 0):
162
+ self.target_institution(latest_trading,latest_table['mutual'], key, volume)
163
+ elif (key.find("三大法人") >= 0):
164
+ self.target_institution(latest_trading,latest_table['institutional_investor'], key, volume)
165
+
166
+ frames = []
167
+ for category, trades in latest_table.items():
168
+ temp_df = pd.DataFrame(trades).T
169
+ temp_df['category'] = category
170
+ frames.append(temp_df)
171
+
172
+ latest_df = pd.concat(frames)
173
+ latest_df = latest_df.reset_index().rename(columns={'index': 'type'})
174
+ latest_df = latest_df[['type', 'category', 'stock', 'price', 'average_price', 'percentage']]
175
+
176
+ return latest_df
177
+
178
+ def process_annual_trading(self, dates, annual_tradings):
179
+ dates = [date.strftime("%m/%d") for date in dates]
180
+ return pd.DataFrame(annual_tradings, index=dates)
181
+
182
+ def target_institution(self, old_table, new_table, key, volume):
183
+ if (key.find("買進") >= 0):
184
+ self.cal_institution(old_table, new_table['buy'], key, volume)
185
+ elif (key.find("賣出") >= 0):
186
+ self.cal_institution(old_table, new_table['sell'], key, volume)
187
+ elif (key.find("買賣超") >= 0):
188
+ self.cal_institution(old_table, new_table['over_buy_sell'], key, volume)
189
+
190
+ def cal_institution(self, old_table, new_table, key, volume):
191
+ new_table['stock'] = np.round(old_table[key] / 1000, 2).item()
192
+ new_table['percentage'] = np.round((old_table[key] / volume) * 100, 2).item()
193
+
194
+ def default_institution_chart(self):
195
+ return {
196
+ "buy": {
197
+ "stock": 0,
198
+ "price": 0,
199
+ "average_price": 0,
200
+ "percentage": 0
201
+ },
202
+ "sell": {
203
+ "stock": 0,
204
+ "price": 0,
205
+ "average_price": 0,
206
+ "percentage": 0
207
+ },
208
+ "over_buy_sell": {
209
+ "stock": 0,
210
+ "price": 0,
211
+ "average_price": 0,
212
+ "percentage": 0
213
+ },
214
+ }