neurostats-API 0.0.6__py3-none-any.whl → 0.0.8__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- neurostats_API/__init__.py +1 -1
- neurostats_API/fetchers/__init__.py +4 -0
- neurostats_API/fetchers/balance_sheet.py +135 -0
- neurostats_API/fetchers/cash_flow.py +184 -0
- neurostats_API/fetchers/finance_overview.py +268 -119
- neurostats_API/fetchers/month_revenue.py +92 -0
- neurostats_API/fetchers/profit_lose.py +141 -0
- neurostats_API/tools/balance_sheet.yaml +26 -0
- neurostats_API/tools/cash_flow_percentage.yaml +39 -0
- neurostats_API/tools/finance_overview_dict.yaml +15 -8
- neurostats_API/tools/profit_lose.yaml +1 -1
- neurostats_API/tools/seasonal_data_field_dict.txt +1 -0
- neurostats_API/utils/data_process.py +149 -3
- {neurostats_API-0.0.6.dist-info → neurostats_API-0.0.8.dist-info}/METADATA +139 -190
- neurostats_API-0.0.8.dist-info/RECORD +26 -0
- neurostats_API-0.0.6.dist-info/RECORD +0 -23
- {neurostats_API-0.0.6.dist-info → neurostats_API-0.0.8.dist-info}/WHEEL +0 -0
- {neurostats_API-0.0.6.dist-info → neurostats_API-0.0.8.dist-info}/top_level.txt +0 -0
neurostats_API/__init__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__='0.0.
|
1
|
+
__version__='0.0.8'
|
@@ -1,3 +1,7 @@
|
|
1
1
|
from .base import StatsDateTime, StatsFetcher
|
2
|
+
from .balance_sheet import BalanceSheetFetcher
|
3
|
+
from .cash_flow import CashFlowFetcher
|
2
4
|
from .finance_overview import FinanceOverviewFetcher
|
5
|
+
from .month_revenue import MonthRevenueFetcher
|
6
|
+
from .profit_lose import ProfitLoseFetcher
|
3
7
|
from .value_invest import ValueFetcher
|
@@ -0,0 +1,135 @@
|
|
1
|
+
from .base import StatsFetcher, StatsDateTime
|
2
|
+
import json
|
3
|
+
import numpy as np
|
4
|
+
import pandas as pd
|
5
|
+
from ..utils import StatsDateTime, StatsProcessor
|
6
|
+
import importlib.resources as pkg_resources
|
7
|
+
import yaml
|
8
|
+
|
9
|
+
|
10
|
+
class BalanceSheetFetcher(StatsFetcher):
|
11
|
+
"""
|
12
|
+
對應iFa.ai -> 財務分析 -> 資產負債表
|
13
|
+
"""
|
14
|
+
|
15
|
+
def __init__(self, ticker, db_client):
|
16
|
+
super().__init__(ticker, db_client)
|
17
|
+
self.table_settings = StatsProcessor.load_yaml("balance_sheet.yaml")
|
18
|
+
|
19
|
+
def prepare_query(self, target_year, target_season):
|
20
|
+
pipeline = super().prepare_query()
|
21
|
+
|
22
|
+
target_query = {
|
23
|
+
"year": "$$target_season_data.year",
|
24
|
+
"season": "$$target_season_data.season",
|
25
|
+
"balance_sheet": "$$$$target_season_data.balance_sheet"
|
26
|
+
}
|
27
|
+
|
28
|
+
pipeline.append({
|
29
|
+
"$project": {
|
30
|
+
"_id": 0,
|
31
|
+
"ticker": 1,
|
32
|
+
"company_name": 1,
|
33
|
+
"balance_sheets": {
|
34
|
+
"$sortArray": {
|
35
|
+
"input": {
|
36
|
+
"$map": {
|
37
|
+
"input": {
|
38
|
+
"$filter": {
|
39
|
+
"input": "$seasonal_data",
|
40
|
+
"as": "season",
|
41
|
+
"cond": {
|
42
|
+
"$eq":
|
43
|
+
["$$season.season", target_season]
|
44
|
+
}
|
45
|
+
}
|
46
|
+
},
|
47
|
+
"as": "target_season_data",
|
48
|
+
"in": {
|
49
|
+
"year":
|
50
|
+
"$$target_season_data.year",
|
51
|
+
"season":
|
52
|
+
"$$target_season_data.season",
|
53
|
+
"balance_sheet":
|
54
|
+
"$$target_season_data.balance_sheet"
|
55
|
+
}
|
56
|
+
}
|
57
|
+
},
|
58
|
+
"sortBy": {
|
59
|
+
"year": -1
|
60
|
+
} # 按 year 降序排序
|
61
|
+
}
|
62
|
+
}
|
63
|
+
}
|
64
|
+
})
|
65
|
+
|
66
|
+
return pipeline
|
67
|
+
|
68
|
+
def collect_data(self, target_year, target_season):
|
69
|
+
pipeline = self.prepare_query(target_year, target_season)
|
70
|
+
|
71
|
+
fetched_data = self.collection.aggregate(pipeline)
|
72
|
+
|
73
|
+
fetched_data = list(fetched_data)
|
74
|
+
|
75
|
+
return fetched_data[-1]
|
76
|
+
|
77
|
+
def query_data(self):
|
78
|
+
today = StatsDateTime.get_today()
|
79
|
+
|
80
|
+
year = today.year - 1 if (today.season == 1) else today.year
|
81
|
+
season = 4 if (today.season == 1) else today.season - 2
|
82
|
+
fetched_data = self.collect_data(year, season)
|
83
|
+
|
84
|
+
return self.process_data(season, fetched_data)
|
85
|
+
|
86
|
+
def process_data(self, target_season, fetched_data):
|
87
|
+
return_dict = {
|
88
|
+
"ticker": self.ticker,
|
89
|
+
"company_name": fetched_data['company_name']
|
90
|
+
}
|
91
|
+
|
92
|
+
index_names = []
|
93
|
+
|
94
|
+
table_dict = dict()
|
95
|
+
|
96
|
+
balance_sheets = fetched_data['balance_sheets']
|
97
|
+
|
98
|
+
# 將value與percentage跟著年分季度一筆筆取出
|
99
|
+
for data in balance_sheets:
|
100
|
+
year = data['year']
|
101
|
+
|
102
|
+
time_index = f"{year}Q{target_season}"
|
103
|
+
|
104
|
+
# 蒐集整體的keys
|
105
|
+
index_names += list(data['balance_sheet'].keys())
|
106
|
+
balance_sheet = data['balance_sheet']
|
107
|
+
|
108
|
+
for index_name, value_dict in balance_sheet.items():
|
109
|
+
for item_name, item in value_dict.items():
|
110
|
+
try: # table_dict[項目][(2020Q1, '%')]
|
111
|
+
if (item_name == 'percentage'):
|
112
|
+
if (isinstance(item, (float, int))):
|
113
|
+
item = np.round(item, 2)
|
114
|
+
if ("YoY" in item_name):
|
115
|
+
if (isinstance(item, (float, int))):
|
116
|
+
item = np.round(item * 100, 2)
|
117
|
+
table_dict[index_name][(time_index, item_name)] = item
|
118
|
+
|
119
|
+
except KeyError:
|
120
|
+
if (index_name not in table_dict.keys()):
|
121
|
+
table_dict[index_name] = dict()
|
122
|
+
|
123
|
+
table_dict[index_name][(time_index, item_name)] = item
|
124
|
+
|
125
|
+
total_table = pd.DataFrame.from_dict(table_dict, orient='index')
|
126
|
+
total_table.columns = pd.MultiIndex.from_tuples(total_table.columns)
|
127
|
+
|
128
|
+
for name, setting in self.table_settings.items():
|
129
|
+
return_dict[name] = StatsProcessor.slice_multi_col_table(
|
130
|
+
total_table=total_table,
|
131
|
+
mode=setting['mode'],
|
132
|
+
target_index=setting['target_index']
|
133
|
+
if "target_index" in setting.keys() else None)
|
134
|
+
|
135
|
+
return return_dict
|
@@ -0,0 +1,184 @@
|
|
1
|
+
from .base import StatsFetcher, StatsDateTime
|
2
|
+
import json
|
3
|
+
import numpy as np
|
4
|
+
import pandas as pd
|
5
|
+
from ..utils import StatsDateTime, StatsProcessor
|
6
|
+
import importlib.resources as pkg_resources
|
7
|
+
import yaml
|
8
|
+
|
9
|
+
class CashFlowFetcher(StatsFetcher):
|
10
|
+
def __init__(self, ticker, db_client):
|
11
|
+
super().__init__(ticker, db_client)
|
12
|
+
|
13
|
+
self.cash_flow_dict = StatsProcessor.load_yaml(
|
14
|
+
"cash_flow_percentage.yaml"
|
15
|
+
) # 計算子表格用
|
16
|
+
|
17
|
+
def prepare_query(self, target_season):
|
18
|
+
pipeline = super().prepare_query()
|
19
|
+
|
20
|
+
pipeline.append({
|
21
|
+
"$project": {
|
22
|
+
"_id": 0,
|
23
|
+
"ticker": 1,
|
24
|
+
"company_name": 1,
|
25
|
+
"cash_flows": {
|
26
|
+
"$sortArray": {
|
27
|
+
"input": {
|
28
|
+
"$map": {
|
29
|
+
"input": {
|
30
|
+
"$filter": {
|
31
|
+
"input": "$seasonal_data",
|
32
|
+
"as": "season",
|
33
|
+
"cond": {
|
34
|
+
"$eq":
|
35
|
+
["$$season.season", target_season]
|
36
|
+
}
|
37
|
+
}
|
38
|
+
},
|
39
|
+
"as": "target_season_data",
|
40
|
+
"in": {
|
41
|
+
"year":
|
42
|
+
"$$target_season_data.year",
|
43
|
+
"season":
|
44
|
+
"$$target_season_data.season",
|
45
|
+
"cash_flow":
|
46
|
+
"$$target_season_data.cash_flow"
|
47
|
+
}
|
48
|
+
}
|
49
|
+
},
|
50
|
+
"sortBy": {
|
51
|
+
"year": -1
|
52
|
+
} # 按 year 降序排序
|
53
|
+
}
|
54
|
+
}
|
55
|
+
}
|
56
|
+
})
|
57
|
+
|
58
|
+
return pipeline
|
59
|
+
|
60
|
+
def collect_data(self, target_season):
|
61
|
+
pipeline = self.prepare_query(target_season)
|
62
|
+
|
63
|
+
fetched_data = self.collection.aggregate(pipeline)
|
64
|
+
|
65
|
+
return list(fetched_data)[0]
|
66
|
+
|
67
|
+
def query_data(self):
|
68
|
+
today = StatsDateTime.get_today()
|
69
|
+
|
70
|
+
target_season = today.season - 1 if (today.season > 1) else 4
|
71
|
+
|
72
|
+
fetched_data = self.collect_data(target_season)
|
73
|
+
|
74
|
+
return self.process_data(fetched_data, target_season)
|
75
|
+
|
76
|
+
def process_data(self, fetched_data, target_season):
|
77
|
+
"""
|
78
|
+
處理現金流量表頁面的所有表格
|
79
|
+
金流表本身沒有比例 但是Ifa有算,
|
80
|
+
項目所屬的情況也不一(分別所屬營業,投資,籌資三個活動)
|
81
|
+
所以這裡選擇不用slicing處理
|
82
|
+
"""
|
83
|
+
cash_flows = fetched_data['cash_flows']
|
84
|
+
|
85
|
+
index_names = []
|
86
|
+
column_names = []
|
87
|
+
|
88
|
+
table_dict = dict()
|
89
|
+
CASHO_dict = dict()
|
90
|
+
CASHI_dict = dict()
|
91
|
+
CASHF_dict = dict()
|
92
|
+
|
93
|
+
return_dict = {
|
94
|
+
"ticker": fetched_data['ticker'],
|
95
|
+
"company_name": fetched_data['company_name'],
|
96
|
+
"cash_flow": dict(),
|
97
|
+
"CASHO": dict(),
|
98
|
+
"CASHI": dict(),
|
99
|
+
"CASHF": dict()
|
100
|
+
}
|
101
|
+
|
102
|
+
checkpoints = ["營業活動之現金流量-間接法", "投資活動之現金流量", "籌資活動之現金流量", "匯率變動對現金及約當現金之影響"]
|
103
|
+
main_cash_flows = [
|
104
|
+
"營業活動之淨現金流入(流出)", "投資活動之淨現金流入(流出)", "籌資活動之淨現金流入(流出)", None
|
105
|
+
] # 主要的比例對象
|
106
|
+
partial_cash_flows = [CASHO_dict, CASHI_dict, CASHF_dict, dict()]
|
107
|
+
|
108
|
+
# 作法: dictionary中也有checkpoints,如果出現了就換下一個index去計算
|
109
|
+
|
110
|
+
for data in cash_flows:
|
111
|
+
year = data['year']
|
112
|
+
|
113
|
+
time_index = f"{year}Q{target_season}"
|
114
|
+
|
115
|
+
cash_flow = data['cash_flow']
|
116
|
+
main_cash_flow_name = None
|
117
|
+
partial_cash_flow = None
|
118
|
+
next_checkpoint = 0
|
119
|
+
|
120
|
+
for index_name, value in cash_flow.items():
|
121
|
+
if (next_checkpoint < 3
|
122
|
+
and index_name == checkpoints[next_checkpoint]): # 找到了主要的變動點
|
123
|
+
main_cash_flow_name = main_cash_flows[next_checkpoint]
|
124
|
+
partial_cash_flow = partial_cash_flows[next_checkpoint]
|
125
|
+
next_checkpoint += 1
|
126
|
+
try:
|
127
|
+
table_dict[time_index][index_name]['value'] = value[
|
128
|
+
'value']
|
129
|
+
if (value['value']):
|
130
|
+
table_dict[time_index][index_name][
|
131
|
+
'percentage'] = np.round(
|
132
|
+
(value['value'] / cash_flow[
|
133
|
+
main_cash_flow_name]['value']) * 100, 2)
|
134
|
+
else:
|
135
|
+
table_dict[time_index][index_name][
|
136
|
+
'percentage'] = None
|
137
|
+
except:
|
138
|
+
if (time_index not in table_dict.keys()):
|
139
|
+
table_dict[time_index] = dict()
|
140
|
+
table_dict[time_index][index_name] = dict()
|
141
|
+
|
142
|
+
table_dict[time_index][index_name]['value'] = value[
|
143
|
+
'value']
|
144
|
+
if (value['value']):
|
145
|
+
table_dict[time_index][index_name][
|
146
|
+
'percentage'] = np.round(
|
147
|
+
(value['value'] / cash_flow[
|
148
|
+
main_cash_flow_name]['value']) * 100, 2)
|
149
|
+
else:
|
150
|
+
table_dict[time_index][index_name][
|
151
|
+
'percentage'] = None
|
152
|
+
|
153
|
+
try:
|
154
|
+
partial_cash_flow[time_index][index_name] = table_dict[
|
155
|
+
time_index][index_name]
|
156
|
+
except:
|
157
|
+
if (time_index not in partial_cash_flow.keys()):
|
158
|
+
partial_cash_flow[time_index] = dict()
|
159
|
+
partial_cash_flow[time_index][index_name] = table_dict[
|
160
|
+
time_index][index_name]
|
161
|
+
|
162
|
+
index_names += list(cash_flow.keys())
|
163
|
+
|
164
|
+
# 轉成dictionary keys
|
165
|
+
index_names = list(dict.fromkeys(index_names))
|
166
|
+
|
167
|
+
cash_flow_table = pd.DataFrame(table_dict)
|
168
|
+
cash_flow_table = StatsProcessor.expand_value_percentage(cash_flow_table)
|
169
|
+
|
170
|
+
CASHO_table = pd.DataFrame(CASHO_dict)
|
171
|
+
CASHO_table = StatsProcessor.expand_value_percentage(CASHO_table)
|
172
|
+
|
173
|
+
CASHI_table = pd.DataFrame(CASHI_dict)
|
174
|
+
CASHI_table = StatsProcessor.expand_value_percentage(CASHI_table)
|
175
|
+
|
176
|
+
CASHF_table = pd.DataFrame(CASHF_dict)
|
177
|
+
CASHF_table = StatsProcessor.expand_value_percentage(CASHF_table)
|
178
|
+
|
179
|
+
return_dict['cash_flow'] = cash_flow_table
|
180
|
+
return_dict['CASHO'] = CASHO_table
|
181
|
+
return_dict['CASHI'] = CASHI_table
|
182
|
+
return_dict['CASHF'] = CASHF_table
|
183
|
+
|
184
|
+
return return_dict
|