neurostats-API 0.0.25rc1__py3-none-any.whl → 1.0.0__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.
- neurostats_API/__init__.py +15 -1
- neurostats_API/async_mode/__init__.py +13 -0
- neurostats_API/async_mode/db/__init__.py +3 -0
- neurostats_API/async_mode/db/base.py +24 -0
- neurostats_API/async_mode/db/tej.py +10 -0
- neurostats_API/async_mode/db/twse.py +8 -0
- neurostats_API/async_mode/db/us.py +9 -0
- neurostats_API/async_mode/db_extractors/__init__.py +20 -0
- neurostats_API/async_mode/db_extractors/base.py +66 -0
- neurostats_API/async_mode/db_extractors/daily/__init__.py +7 -0
- neurostats_API/async_mode/db_extractors/daily/base.py +89 -0
- neurostats_API/async_mode/db_extractors/daily/tej_chip.py +14 -0
- neurostats_API/async_mode/db_extractors/daily/tej_tech.py +12 -0
- neurostats_API/async_mode/db_extractors/daily/twse_chip.py +49 -0
- neurostats_API/async_mode/db_extractors/daily/value.py +93 -0
- neurostats_API/async_mode/db_extractors/daily/yf.py +12 -0
- neurostats_API/async_mode/db_extractors/month_revenue/__init__.py +1 -0
- neurostats_API/async_mode/db_extractors/month_revenue/base.py +140 -0
- neurostats_API/async_mode/db_extractors/month_revenue/twse.py +5 -0
- neurostats_API/async_mode/db_extractors/seasonal/__init__.py +4 -0
- neurostats_API/async_mode/db_extractors/seasonal/balance_sheet.py +19 -0
- neurostats_API/async_mode/db_extractors/seasonal/base.py +152 -0
- neurostats_API/async_mode/db_extractors/seasonal/cashflow.py +10 -0
- neurostats_API/async_mode/db_extractors/seasonal/profit_lose.py +17 -0
- neurostats_API/async_mode/db_extractors/seasonal/tej.py +87 -0
- neurostats_API/async_mode/factory/__init__.py +1 -0
- neurostats_API/async_mode/factory/extractor_factory.py +168 -0
- neurostats_API/async_mode/factory/transformer_factory.py +164 -0
- neurostats_API/async_mode/fetchers/__init__.py +10 -0
- neurostats_API/async_mode/fetchers/balance_sheet.py +31 -0
- neurostats_API/async_mode/fetchers/base.py +48 -0
- neurostats_API/async_mode/fetchers/cash_flow.py +56 -0
- neurostats_API/async_mode/fetchers/finance_overview.py +134 -0
- neurostats_API/async_mode/fetchers/month_revenue.py +35 -0
- neurostats_API/async_mode/fetchers/profit_lose.py +46 -0
- neurostats_API/async_mode/fetchers/tech.py +205 -0
- neurostats_API/async_mode/fetchers/tej/__init__.py +2 -0
- neurostats_API/async_mode/fetchers/tej/seasonal_data.py +88 -0
- neurostats_API/async_mode/fetchers/tej/tech.py +34 -0
- neurostats_API/async_mode/fetchers/twse_institution.py +62 -0
- neurostats_API/async_mode/fetchers/twse_margin.py +100 -0
- neurostats_API/async_mode/fetchers/value.py +76 -0
- neurostats_API/config/company_list/ticker_index_industry_map.json +7946 -0
- neurostats_API/config/company_list/us.json +9986 -0
- neurostats_API/{tools → config}/tej_db/tej_db_skip_index.yaml +0 -2
- neurostats_API/{tools → config}/twse/profit_lose.yaml +0 -6
- neurostats_API/fetchers/finance_overview.py +22 -2
- neurostats_API/transformers/__init__.py +40 -0
- neurostats_API/transformers/balance_sheet/__init__.py +2 -0
- neurostats_API/transformers/balance_sheet/base.py +51 -0
- neurostats_API/transformers/balance_sheet/twse.py +81 -0
- neurostats_API/transformers/balance_sheet/us.py +38 -0
- neurostats_API/transformers/base.py +158 -0
- neurostats_API/transformers/cash_flow/__init__.py +2 -0
- neurostats_API/transformers/cash_flow/base.py +114 -0
- neurostats_API/transformers/cash_flow/twse.py +70 -0
- neurostats_API/transformers/cash_flow/us.py +38 -0
- neurostats_API/transformers/daily_chip/__init__.py +1 -0
- neurostats_API/transformers/daily_chip/base.py +5 -0
- neurostats_API/transformers/daily_chip/tej.py +0 -0
- neurostats_API/transformers/daily_chip/twse_chip.py +415 -0
- neurostats_API/transformers/daily_chip/utils/__init__.py +0 -0
- neurostats_API/transformers/daily_chip/utils/institution.py +90 -0
- neurostats_API/transformers/daily_chip/utils/margin_trading.py +2 -0
- neurostats_API/transformers/daily_chip/utils/security_lending.py +0 -0
- neurostats_API/transformers/daily_tech/__init__.py +1 -0
- neurostats_API/transformers/daily_tech/base.py +5 -0
- neurostats_API/transformers/daily_tech/tech.py +84 -0
- neurostats_API/transformers/daily_tech/utils/__init__.py +1 -0
- neurostats_API/transformers/daily_tech/utils/processor.py +251 -0
- neurostats_API/transformers/finance_overview/__init__.py +2 -0
- neurostats_API/transformers/finance_overview/agent_overview.py +55 -0
- neurostats_API/transformers/finance_overview/base.py +824 -0
- neurostats_API/transformers/finance_overview/stats_overview.py +79 -0
- neurostats_API/transformers/month_revenue/__init__.py +1 -0
- neurostats_API/transformers/month_revenue/base.py +60 -0
- neurostats_API/transformers/month_revenue/twse.py +134 -0
- neurostats_API/transformers/profit_lose/__init__.py +2 -0
- neurostats_API/transformers/profit_lose/base.py +82 -0
- neurostats_API/transformers/profit_lose/twse.py +133 -0
- neurostats_API/transformers/profit_lose/us.py +26 -0
- neurostats_API/transformers/tej/__init__.py +1 -0
- neurostats_API/transformers/tej/base.py +149 -0
- neurostats_API/transformers/tej/finance_statement.py +80 -0
- neurostats_API/transformers/value/__init__.py +1 -0
- neurostats_API/transformers/value/base.py +5 -0
- neurostats_API/transformers/value/tej.py +8 -0
- neurostats_API/transformers/value/twse.py +48 -0
- neurostats_API/utils/__init__.py +1 -1
- neurostats_API/utils/data_process.py +10 -6
- neurostats_API/utils/exception.py +8 -0
- neurostats_API/utils/logger.py +21 -0
- neurostats_API-1.0.0.dist-info/METADATA +102 -0
- neurostats_API-1.0.0.dist-info/RECORD +121 -0
- neurostats_API-0.0.25rc1.dist-info/METADATA +0 -858
- neurostats_API-0.0.25rc1.dist-info/RECORD +0 -36
- /neurostats_API/{tools → config}/company_list/tw.json +0 -0
- /neurostats_API/{tools → config}/company_list/us_TradingView_list.json +0 -0
- /neurostats_API/{tools → config}/tej_db/tej_db_index.yaml +0 -0
- /neurostats_API/{tools → config}/tej_db/tej_db_percent_index.yaml +0 -0
- /neurostats_API/{tools → config}/tej_db/tej_db_thousand_index.yaml +0 -0
- /neurostats_API/{tools → config}/twse/balance_sheet.yaml +0 -0
- /neurostats_API/{tools → config}/twse/cash_flow_percentage.yaml +0 -0
- /neurostats_API/{tools → config}/twse/finance_overview_dict.yaml +0 -0
- /neurostats_API/{tools → config}/twse/seasonal_data_field_dict.txt +0 -0
- {neurostats_API-0.0.25rc1.dist-info → neurostats_API-1.0.0.dist-info}/WHEEL +0 -0
- {neurostats_API-0.0.25rc1.dist-info → neurostats_API-1.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,38 @@
|
|
1
|
+
from .base import BaseCashFlowTransformer
|
2
|
+
from neurostats_API.utils import StatsProcessor, YoY_Calculator
|
3
|
+
import pandas as pd
|
4
|
+
|
5
|
+
class USCashFlowTransformer(BaseCashFlowTransformer):
|
6
|
+
def __init__(self, ticker, company_name, zone):
|
7
|
+
super().__init__(ticker, company_name, zone)
|
8
|
+
self.data_df = None
|
9
|
+
self.return_keys = ['cash_flow', 'cash_flow_YoY']
|
10
|
+
def process_transform(self, fetched_data):
|
11
|
+
if (not fetched_data):
|
12
|
+
return self._get_empty_structure()
|
13
|
+
|
14
|
+
self.data_df = self._process_us_format(fetched_data)
|
15
|
+
target_season = fetched_data[0]['season']
|
16
|
+
|
17
|
+
return_dict = {
|
18
|
+
"ticker": self.ticker,
|
19
|
+
"company_name": self.company_name,
|
20
|
+
"cash_flow": self.data_df,
|
21
|
+
"cash_flow_YoY": self._slice_target_season(self.data_df, target_season)
|
22
|
+
}
|
23
|
+
|
24
|
+
return return_dict
|
25
|
+
|
26
|
+
def process_QoQ(self, fetched_data):
|
27
|
+
if (not fetched_data):
|
28
|
+
return self._get_empty_structure()
|
29
|
+
|
30
|
+
data_df = self._process_twse_to_tej_format(fetched_data)
|
31
|
+
target_season = fetched_data[0]['season']
|
32
|
+
|
33
|
+
data_df_YoY = self._slice_target_season(data_df, target_season)
|
34
|
+
|
35
|
+
return {
|
36
|
+
"cash_flow_all": data_df,
|
37
|
+
"cash_flow_YoY": data_df_YoY
|
38
|
+
}
|
@@ -0,0 +1 @@
|
|
1
|
+
from .twse_chip import TWSEChipTransformer
|
File without changes
|
@@ -0,0 +1,415 @@
|
|
1
|
+
from .base import BaseChipTransformer
|
2
|
+
from datetime import date, datetime
|
3
|
+
import inspect
|
4
|
+
import numpy as np
|
5
|
+
import pandas as pd
|
6
|
+
from neurostats_API.utils import StatsProcessor
|
7
|
+
|
8
|
+
|
9
|
+
class TWSEChipTransformer(BaseChipTransformer):
|
10
|
+
|
11
|
+
def __init__(self, ticker, company_name, zone):
|
12
|
+
super().__init__(ticker, company_name, zone)
|
13
|
+
|
14
|
+
def process_transform(self, tech_data, **fetched_datas):
|
15
|
+
"""
|
16
|
+
fetched_datas:
|
17
|
+
{
|
18
|
+
"institution_trading": {}
|
19
|
+
"margin_trading": {},
|
20
|
+
"security_lending: {},
|
21
|
+
}
|
22
|
+
"""
|
23
|
+
if not fetched_datas:
|
24
|
+
return self._get_empty_structure() # 若查無資料,回傳空表結構
|
25
|
+
|
26
|
+
return_dict = {
|
27
|
+
"ticker": self.ticker,
|
28
|
+
"company_name": self.company_name,
|
29
|
+
'latest_trading': {},
|
30
|
+
'annual_trading': {},
|
31
|
+
'price': {}
|
32
|
+
}
|
33
|
+
|
34
|
+
for key, fetched_data in fetched_datas.items():
|
35
|
+
|
36
|
+
tech_dict = {data['date']: data for data in tech_data}
|
37
|
+
latest_tech = tech_data[-1]
|
38
|
+
if (not fetched_data):
|
39
|
+
return_dict['latest_trading'].update(
|
40
|
+
self._process_latest({}, latest_tech, key)
|
41
|
+
)
|
42
|
+
return_dict['annual_trading'].update(
|
43
|
+
self._process_annual({}, tech_dict, key)
|
44
|
+
)
|
45
|
+
continue
|
46
|
+
|
47
|
+
fetched_dict = {data['date']: data[key] for data in fetched_data}
|
48
|
+
latest_data = fetched_data[-1]
|
49
|
+
|
50
|
+
return_dict['latest_trading'].update(
|
51
|
+
self._process_latest(latest_data, latest_tech, key)
|
52
|
+
)
|
53
|
+
return_dict['annual_trading'].update(
|
54
|
+
self._process_annual(fetched_dict, tech_dict, key)
|
55
|
+
)
|
56
|
+
|
57
|
+
tech_df = pd.DataFrame(tech_data)
|
58
|
+
return_dict['price'] = self._process_tech(tech_df)
|
59
|
+
|
60
|
+
return return_dict
|
61
|
+
|
62
|
+
def _process_latest(self, latest_data: dict, latest_tech: dict, key: str):
|
63
|
+
latest_trade = latest_data.get(key)
|
64
|
+
return {
|
65
|
+
"date":
|
66
|
+
latest_data.get('date',
|
67
|
+
datetime.today().strftime("%Y-%m-%d")),
|
68
|
+
**self._process_latest_trading(latest_trade, latest_tech, key)
|
69
|
+
}
|
70
|
+
|
71
|
+
def _process_annual(self, fetched_dict: dict, tech_dict: dict, key: str):
|
72
|
+
if (fetched_dict):
|
73
|
+
fetched_data = [
|
74
|
+
{
|
75
|
+
'date': date,
|
76
|
+
'close': tech_dict.get(date, {}).get('close', 0.0),
|
77
|
+
'volume': tech_dict.get(date, {}).get('volume', 0.0),
|
78
|
+
**trading
|
79
|
+
} for date, trading in fetched_dict.items()
|
80
|
+
]
|
81
|
+
fetched_df = self._process_annual_trading(fetched_data, key)
|
82
|
+
|
83
|
+
else:
|
84
|
+
fetched_data = [
|
85
|
+
{
|
86
|
+
'date': date,
|
87
|
+
'close': tech_dict.get(date, {}).get('close', 0.0),
|
88
|
+
'volume': tech_dict.get(date, {}).get('volume', 0.0),
|
89
|
+
} for date in tech_dict.keys()
|
90
|
+
]
|
91
|
+
fetched_df = pd.DataFrame(fetched_data)
|
92
|
+
|
93
|
+
return {key: fetched_df}
|
94
|
+
|
95
|
+
def _process_latest_trading(self, latest_trading, latest_tech, key):
|
96
|
+
FN_MAP = {
|
97
|
+
'institution_trading': self._process_latest_institution,
|
98
|
+
'margin_trading': self._process_latest_margin,
|
99
|
+
'security_lending': self._process_latest_security
|
100
|
+
}
|
101
|
+
|
102
|
+
process_fn = FN_MAP.get(key)
|
103
|
+
return process_fn(latest_trading, latest_tech)
|
104
|
+
|
105
|
+
def _process_annual_trading(self, fetched_data, key):
|
106
|
+
FN_MAP = {
|
107
|
+
'institution_trading': self._process_annual_institution,
|
108
|
+
'margin_trading': self._process_annual_margin,
|
109
|
+
'security_lending': self._process_annual_security
|
110
|
+
}
|
111
|
+
|
112
|
+
process_fn = FN_MAP.get(key)
|
113
|
+
return process_fn(fetched_data)
|
114
|
+
|
115
|
+
def _process_tech(self, tech_df):
|
116
|
+
latest_daily_data = tech_df.iloc[-1]
|
117
|
+
yesterday_daily_data = tech_df.iloc[-2]
|
118
|
+
|
119
|
+
price_dict = {
|
120
|
+
"open": round(latest_daily_data['open'], 2),
|
121
|
+
'close': round(latest_daily_data['close'], 2),
|
122
|
+
'range':
|
123
|
+
f"{latest_daily_data['low']:.2f} - {latest_daily_data['high']:.2f}",
|
124
|
+
'volume': round(latest_daily_data['volume'] / 1000, 2),
|
125
|
+
'last_open': round(yesterday_daily_data['open'], 2),
|
126
|
+
'last_close': round(yesterday_daily_data['close'], 2),
|
127
|
+
'last_range':
|
128
|
+
f"{yesterday_daily_data['low']:.2f} - {yesterday_daily_data['high']:.2f}",
|
129
|
+
'last_volume': round(yesterday_daily_data['volume'] / 1000, 2)
|
130
|
+
}
|
131
|
+
|
132
|
+
lowest = pd.to_numeric(tech_df["low"], errors="coerce").min().item()
|
133
|
+
highest = pd.to_numeric(tech_df["high"], errors="coerce").max().item()
|
134
|
+
price_dict['52weeks_range'] = f"{lowest:.2f} - {highest:.2f}"
|
135
|
+
|
136
|
+
return price_dict
|
137
|
+
|
138
|
+
def _process_latest_institution(self, latest_trading, latest_tech):
|
139
|
+
|
140
|
+
def _default_institution_chart():
|
141
|
+
return {
|
142
|
+
"buy": {
|
143
|
+
"stock": None,
|
144
|
+
"price": None,
|
145
|
+
"average_price": None,
|
146
|
+
"percentage": None
|
147
|
+
},
|
148
|
+
"sell": {
|
149
|
+
"stock": None,
|
150
|
+
"price": None,
|
151
|
+
"average_price": None,
|
152
|
+
"percentage": None
|
153
|
+
},
|
154
|
+
"over_buy_sell": {
|
155
|
+
"stock": None,
|
156
|
+
"price": None,
|
157
|
+
"average_price": None,
|
158
|
+
"percentage": None
|
159
|
+
},
|
160
|
+
}
|
161
|
+
|
162
|
+
latest_table = {
|
163
|
+
"foreign": _default_institution_chart(),
|
164
|
+
"mutual": _default_institution_chart(),
|
165
|
+
"prop": _default_institution_chart(),
|
166
|
+
"institutional_investor": _default_institution_chart(),
|
167
|
+
}
|
168
|
+
|
169
|
+
volume = latest_tech['volume']
|
170
|
+
|
171
|
+
if (latest_trading is not None):
|
172
|
+
for key in latest_trading.keys():
|
173
|
+
if (key.find("外陸資") >= 0 or key.find("外資") >= 0):
|
174
|
+
self._target_institution(
|
175
|
+
latest_trading, latest_table['foreign'], key, volume
|
176
|
+
)
|
177
|
+
elif (key.find("自營商") >= 0):
|
178
|
+
self._target_institution(
|
179
|
+
latest_trading, latest_table['prop'], key, volume
|
180
|
+
)
|
181
|
+
elif (key.find("投信") >= 0):
|
182
|
+
self._target_institution(
|
183
|
+
latest_trading, latest_table['mutual'], key, volume
|
184
|
+
)
|
185
|
+
elif (key.find("三大法人") >= 0):
|
186
|
+
self._target_institution(
|
187
|
+
latest_trading, latest_table['institutional_investor'],
|
188
|
+
key, volume
|
189
|
+
)
|
190
|
+
# 計算合計
|
191
|
+
for unit in ['stock', 'percentage']:
|
192
|
+
# 買進總和
|
193
|
+
latest_table['institutional_investor']['buy'][unit] = (
|
194
|
+
latest_table['foreign']['buy'][unit] +
|
195
|
+
latest_table['prop']['buy'][unit] +
|
196
|
+
latest_table['mutual']['buy'][unit]
|
197
|
+
)
|
198
|
+
# 賣出總和
|
199
|
+
latest_table['institutional_investor']['sell'][unit] = (
|
200
|
+
latest_table['foreign']['sell'][unit] +
|
201
|
+
latest_table['prop']['sell'][unit] +
|
202
|
+
latest_table['mutual']['sell'][unit]
|
203
|
+
)
|
204
|
+
|
205
|
+
frames = []
|
206
|
+
for category, trades in latest_table.items():
|
207
|
+
temp_df = pd.DataFrame(trades).T
|
208
|
+
temp_df['category'] = category
|
209
|
+
frames.append(temp_df)
|
210
|
+
|
211
|
+
latest_df = pd.concat(frames)
|
212
|
+
latest_df = latest_df.reset_index().rename(columns={'index': 'type'})
|
213
|
+
latest_df = latest_df[[
|
214
|
+
'type', 'category', 'stock', 'price', 'average_price', 'percentage'
|
215
|
+
]]
|
216
|
+
|
217
|
+
latest_df = pd.melt(
|
218
|
+
latest_df,
|
219
|
+
id_vars=['type', 'category'],
|
220
|
+
var_name='variable',
|
221
|
+
value_name='value'
|
222
|
+
)
|
223
|
+
|
224
|
+
latest_df = latest_df.pivot_table(
|
225
|
+
index=['category', 'variable'],
|
226
|
+
columns='type',
|
227
|
+
values='value',
|
228
|
+
aggfunc='first'
|
229
|
+
)
|
230
|
+
|
231
|
+
# 重設列名,去除多層索引
|
232
|
+
latest_df.columns.name = None # 去除列名稱
|
233
|
+
latest_df = latest_df.reset_index()
|
234
|
+
|
235
|
+
return {"institution_trading": latest_df}
|
236
|
+
|
237
|
+
def _process_annual_institution(self, trading_datas):
|
238
|
+
|
239
|
+
return pd.DataFrame(trading_datas)
|
240
|
+
|
241
|
+
def _process_annual_margin(self, trading_datas):
|
242
|
+
# 融資
|
243
|
+
def _process(data):
|
244
|
+
if ('現金償還' in data['financing'].keys()):
|
245
|
+
data['financing']['現償'] = data['financing'].pop('現金償還')
|
246
|
+
data['short_selling']['現償'] = data['short_selling'].pop(
|
247
|
+
'現券償還', data['financing']['現償']
|
248
|
+
)
|
249
|
+
financing = {
|
250
|
+
f"融資_{type_name}": num_of_stocks
|
251
|
+
for type_name, num_of_stocks in data['financing'].items()
|
252
|
+
}
|
253
|
+
|
254
|
+
short_selling = {
|
255
|
+
f"融券_{type_name}": num_of_stocks
|
256
|
+
for type_name, num_of_stocks in data['short_selling'].items()
|
257
|
+
}
|
258
|
+
|
259
|
+
security_offset = data['資券互抵']
|
260
|
+
|
261
|
+
return {**financing, **short_selling, '資券互抵': security_offset}
|
262
|
+
|
263
|
+
if (trading_datas):
|
264
|
+
return_datas = [
|
265
|
+
{
|
266
|
+
'date': data['date'],
|
267
|
+
'close': data['close'],
|
268
|
+
'volume': data['volume'],
|
269
|
+
**_process(data)
|
270
|
+
} for data in trading_datas
|
271
|
+
]
|
272
|
+
else:
|
273
|
+
return_datas = []
|
274
|
+
|
275
|
+
# 接起來
|
276
|
+
return_datas = pd.DataFrame(return_datas)
|
277
|
+
|
278
|
+
return return_datas
|
279
|
+
|
280
|
+
def _process_annual_security(self, trading_datas):
|
281
|
+
|
282
|
+
def _process(data):
|
283
|
+
stock_lendings = {
|
284
|
+
f"借券_{type_name}":
|
285
|
+
(num_of_stocks /
|
286
|
+
1000) if isinstance(num_of_stocks,
|
287
|
+
(int, float)) else num_of_stocks
|
288
|
+
for type_name, num_of_stocks in data['stock_lending'].items()
|
289
|
+
}
|
290
|
+
|
291
|
+
return stock_lendings
|
292
|
+
|
293
|
+
if (trading_datas):
|
294
|
+
return_datas = [
|
295
|
+
{
|
296
|
+
'date': data['date'],
|
297
|
+
'close': data['close'],
|
298
|
+
'volume': data['volume'],
|
299
|
+
**_process(data)
|
300
|
+
} for data in trading_datas
|
301
|
+
]
|
302
|
+
else:
|
303
|
+
return_datas = []
|
304
|
+
|
305
|
+
# 接起來
|
306
|
+
return_datas = pd.DataFrame(return_datas)
|
307
|
+
|
308
|
+
return return_datas
|
309
|
+
|
310
|
+
def _target_institution(self, old_table, new_table, key, volume):
|
311
|
+
if (key.find("買進") >= 0):
|
312
|
+
self._cal_institution(old_table, new_table['buy'], key, volume)
|
313
|
+
elif (key.find("賣出") >= 0):
|
314
|
+
self._cal_institution(old_table, new_table['sell'], key, volume)
|
315
|
+
elif (key.find("買賣超") >= 0):
|
316
|
+
self._cal_institution(
|
317
|
+
old_table, new_table['over_buy_sell'], key, volume
|
318
|
+
)
|
319
|
+
|
320
|
+
def _cal_institution(self, old_table, new_table, key, volume):
|
321
|
+
new_table['stock'] = np.round(old_table[key] / 1000, 2).item()
|
322
|
+
new_table['percentage'] = np.round((old_table[key] / volume) * 100,
|
323
|
+
2).item()
|
324
|
+
|
325
|
+
def _process_latest_margin(self, latest_trading, *args):
|
326
|
+
|
327
|
+
def _default_margin_chart():
|
328
|
+
dafault_dict = {"financing": {}, "short_selling": {}}
|
329
|
+
return {
|
330
|
+
"margin_trading": pd.DataFrame.from_dict(dafault_dict),
|
331
|
+
"security_offset": None
|
332
|
+
}
|
333
|
+
|
334
|
+
if (latest_trading is None):
|
335
|
+
return _default_margin_chart()
|
336
|
+
|
337
|
+
latest_trading['financing']['現償'] = latest_trading['financing'].pop(
|
338
|
+
'現金償還'
|
339
|
+
)
|
340
|
+
latest_trading['short_selling']['現償'] = latest_trading['short_selling'
|
341
|
+
].pop('現券償還')
|
342
|
+
|
343
|
+
latest_trading_df = {
|
344
|
+
category: sub_dict
|
345
|
+
for category, sub_dict in latest_trading.items()
|
346
|
+
if (isinstance(sub_dict, dict))
|
347
|
+
}
|
348
|
+
|
349
|
+
latest_margin_trading_df = pd.DataFrame.from_dict(latest_trading_df)
|
350
|
+
|
351
|
+
return {
|
352
|
+
"margin_trading": latest_margin_trading_df,
|
353
|
+
"security_offset": latest_trading['資券互抵']
|
354
|
+
}
|
355
|
+
|
356
|
+
def _process_latest_security(self, latest_trading, *args):
|
357
|
+
|
358
|
+
def _default_margin_chart():
|
359
|
+
return {
|
360
|
+
"stock_lending":
|
361
|
+
pd.DataFrame(
|
362
|
+
index=["當日賣出", "現償", "當日還券", "當日調整", "當日餘額", "次一營業日可限額"]
|
363
|
+
)
|
364
|
+
}
|
365
|
+
|
366
|
+
if (latest_trading is None):
|
367
|
+
return _default_margin_chart()
|
368
|
+
|
369
|
+
latest_stock_lending = latest_trading['stock_lending']
|
370
|
+
|
371
|
+
latest_stock_lending = {
|
372
|
+
type_name: StatsProcessor.cal_round_int(value / 1000)
|
373
|
+
for type_name, value in latest_stock_lending.items()
|
374
|
+
}
|
375
|
+
latest_stock_lending.pop("前日餘額")
|
376
|
+
latest_stock_lending_df = pd.DataFrame.from_dict(
|
377
|
+
latest_stock_lending, orient="index", columns=['stock_lending']
|
378
|
+
)
|
379
|
+
|
380
|
+
return latest_stock_lending_df
|
381
|
+
|
382
|
+
def _get_empty_structure(self):
|
383
|
+
return_dict = {
|
384
|
+
"ticker": self.ticker,
|
385
|
+
"company_name": self.company_name,
|
386
|
+
}
|
387
|
+
|
388
|
+
return_dict['latest_trading'] = {
|
389
|
+
'date': date.today(),
|
390
|
+
'institution_trading': pd.DataFrame(),
|
391
|
+
'margin_trading': pd.DataFrame(),
|
392
|
+
'security_lending': pd.DataFrame()
|
393
|
+
}
|
394
|
+
|
395
|
+
return_dict['annual_trading'] = {
|
396
|
+
'institution_trading':
|
397
|
+
pd.DataFrame(columns=['date', 'close', 'volume']),
|
398
|
+
'margin_trading': pd.DataFrame(columns=['date', 'close', 'volume']),
|
399
|
+
'security_lending':
|
400
|
+
pd.DataFrame(columns=['date', 'close', 'volume']),
|
401
|
+
}
|
402
|
+
|
403
|
+
return_dict['price'] = {
|
404
|
+
"open": None,
|
405
|
+
'close': None,
|
406
|
+
'range': None,
|
407
|
+
'volume': None,
|
408
|
+
'last_open': None,
|
409
|
+
'last_close': None,
|
410
|
+
'last_range': None,
|
411
|
+
'last_volume': None,
|
412
|
+
'52weeks_range': None
|
413
|
+
}
|
414
|
+
|
415
|
+
return return_dict
|
File without changes
|
@@ -0,0 +1,90 @@
|
|
1
|
+
import pandas as pd
|
2
|
+
|
3
|
+
|
4
|
+
class InstitutionTradingProcessor:
|
5
|
+
@classmethod
|
6
|
+
def _process_latest_trading(cls, latest_trading):
|
7
|
+
for key in latest_trading.keys():
|
8
|
+
if (key.find("外陸資") >= 0 or key.find("外資") >= 0):
|
9
|
+
cls._target_institution(
|
10
|
+
latest_trading, latest_table['foreign'], key, volume
|
11
|
+
)
|
12
|
+
elif (key.find("自營商") >= 0):
|
13
|
+
cls._target_institution(
|
14
|
+
latest_trading, latest_table['prop'], key, volume
|
15
|
+
)
|
16
|
+
elif (key.find("投信") >= 0):
|
17
|
+
cls._target_institution(
|
18
|
+
latest_trading, latest_table['mutual'], key, volume
|
19
|
+
)
|
20
|
+
elif (key.find("三大法人") >= 0):
|
21
|
+
cls._target_institution(
|
22
|
+
latest_trading, latest_table['institutional_investor'], key,
|
23
|
+
volume
|
24
|
+
)
|
25
|
+
# 計算合計
|
26
|
+
for unit in ['stock', 'percentage']:
|
27
|
+
# 買進總和
|
28
|
+
latest_table['institutional_investor']['buy'][unit] = (
|
29
|
+
latest_table['foreign']['buy'][unit] +
|
30
|
+
latest_table['prop']['buy'][unit] +
|
31
|
+
latest_table['mutual']['buy'][unit]
|
32
|
+
)
|
33
|
+
# 賣出總和
|
34
|
+
latest_table['institutional_investor']['sell'][unit] = (
|
35
|
+
latest_table['foreign']['sell'][unit] +
|
36
|
+
latest_table['prop']['sell'][unit] +
|
37
|
+
latest_table['mutual']['sell'][unit]
|
38
|
+
)
|
39
|
+
|
40
|
+
frames = []
|
41
|
+
for category, trades in latest_table.items():
|
42
|
+
temp_df = pd.DataFrame(trades).T
|
43
|
+
temp_df['category'] = category
|
44
|
+
frames.append(temp_df)
|
45
|
+
|
46
|
+
latest_df = pd.concat(frames)
|
47
|
+
latest_df = latest_df.reset_index().rename(columns={'index': 'type'})
|
48
|
+
latest_df = latest_df[[
|
49
|
+
'type', 'category', 'stock', 'price', 'average_price', 'percentage'
|
50
|
+
]]
|
51
|
+
|
52
|
+
latest_df = pd.melt(
|
53
|
+
latest_df,
|
54
|
+
id_vars=['type', 'category'],
|
55
|
+
var_name='variable',
|
56
|
+
value_name='value'
|
57
|
+
)
|
58
|
+
|
59
|
+
latest_df = latest_df.pivot_table(
|
60
|
+
index=['category', 'variable'],
|
61
|
+
columns='type',
|
62
|
+
values='value',
|
63
|
+
aggfunc='first'
|
64
|
+
)
|
65
|
+
|
66
|
+
# 重設列名,去除多層索引
|
67
|
+
latest_df.columns.name = None # 去除列名稱
|
68
|
+
latest_df = latest_df.reset_index()
|
69
|
+
|
70
|
+
return latest_df
|
71
|
+
@classmethod
|
72
|
+
def _process_annual_trading(cls, trading_datas):
|
73
|
+
return pd.DataFrame(trading_datas)
|
74
|
+
|
75
|
+
@classmethod
|
76
|
+
def _target_institution(cls, old_table, new_table, key, volume):
|
77
|
+
if (key.find("買進") >= 0):
|
78
|
+
cls._cal_institution(old_table, new_table['buy'], key, volume)
|
79
|
+
elif (key.find("賣出") >= 0):
|
80
|
+
cls._cal_institution(old_table, new_table['sell'], key, volume)
|
81
|
+
elif (key.find("買賣超") >= 0):
|
82
|
+
cls._cal_institution(
|
83
|
+
old_table, new_table['over_buy_sell'], key, volume
|
84
|
+
)
|
85
|
+
|
86
|
+
@classmethod
|
87
|
+
def _cal_institution(cls, old_table, new_table, key, volume):
|
88
|
+
new_table['stock'] = np.round(old_table[key] / 1000, 2).item()
|
89
|
+
new_table['percentage'] = np.round((old_table[key] / volume) * 100,
|
90
|
+
2).item()
|
File without changes
|
@@ -0,0 +1 @@
|
|
1
|
+
from .tech import DailyTechTransformer
|
@@ -0,0 +1,84 @@
|
|
1
|
+
from .base import BaseDailyTransformer
|
2
|
+
import inspect
|
3
|
+
import pandas as pd
|
4
|
+
from .utils import TechProcessor
|
5
|
+
|
6
|
+
|
7
|
+
class DailyTechTransformer(BaseDailyTransformer):
|
8
|
+
def __init__(self, ticker, company_name, zone):
|
9
|
+
super().__init__(ticker, company_name, zone)
|
10
|
+
|
11
|
+
self.basic_indexes = [
|
12
|
+
'SMA5', 'SMA20', 'SMA60', 'EMA5', 'EMA20', 'EMA40', 'EMA12',
|
13
|
+
'EMA26', 'RSI7', 'RSI14', 'RSI21', 'MACD', 'Signal Line',
|
14
|
+
'Middle Band', 'Upper Band', 'Lower Band', '%b', 'ATR', 'BBW',
|
15
|
+
'EMA Cycle', 'EMA Cycle Instructions', 'Day Trading Signal'
|
16
|
+
]
|
17
|
+
|
18
|
+
self.fetched_data = None
|
19
|
+
self.daily_index = None
|
20
|
+
self.weekly_index = None
|
21
|
+
self.monthly_index = None
|
22
|
+
self.quarterly_index = None
|
23
|
+
self.yearly_index = None
|
24
|
+
|
25
|
+
def process_transform(self, fetched_data):
|
26
|
+
self.fetched_data = pd.DataFrame(fetched_data)
|
27
|
+
|
28
|
+
self.daily_index = TechProcessor.cal_basic_index(
|
29
|
+
self.fetched_data
|
30
|
+
)
|
31
|
+
|
32
|
+
def _check_daily_index(self):
|
33
|
+
"""
|
34
|
+
檢查 daily_index 是否已經計算。
|
35
|
+
如果 daily_index 為 None,則藉由 inspect 模組取得呼叫該函式的 caller,
|
36
|
+
並拋出錯誤訊息,提示先呼叫 process_transform(fetched_data)
|
37
|
+
再呼叫對應的函數(例如 get_weekly())。
|
38
|
+
"""
|
39
|
+
if self.daily_index is None:
|
40
|
+
# 取得呼叫此 _check_daily_index 的函數名稱
|
41
|
+
caller_name = inspect.stack()[1].function
|
42
|
+
raise ValueError(f"Please call process_transform(fetched_data) before accessing {caller_name}()")
|
43
|
+
|
44
|
+
def _get_resampled(self, period: str):
|
45
|
+
"""
|
46
|
+
根據指定時間間隔對 daily_index 進行重採樣。
|
47
|
+
"""
|
48
|
+
self._check_daily_index() # 如果 daily_index 尚未計算,會拋出錯誤
|
49
|
+
return TechProcessor.resample(
|
50
|
+
self.daily_index,
|
51
|
+
period=period,
|
52
|
+
technical_indicators=self.basic_indexes
|
53
|
+
)
|
54
|
+
|
55
|
+
def get_daily(self):
|
56
|
+
"""
|
57
|
+
回傳日線
|
58
|
+
"""
|
59
|
+
self._check_daily_index()
|
60
|
+
return self.daily_index
|
61
|
+
|
62
|
+
def get_weekly(self):
|
63
|
+
"""
|
64
|
+
回傳週線資料。
|
65
|
+
"""
|
66
|
+
return self._get_resampled('W')
|
67
|
+
|
68
|
+
def get_monthly(self):
|
69
|
+
"""
|
70
|
+
回傳月線資料。
|
71
|
+
"""
|
72
|
+
return self._get_resampled('ME')
|
73
|
+
|
74
|
+
def get_quarterly(self):
|
75
|
+
"""
|
76
|
+
回傳季線資料。
|
77
|
+
"""
|
78
|
+
return self._get_resampled('QE')
|
79
|
+
|
80
|
+
def get_yearly(self):
|
81
|
+
"""
|
82
|
+
回傳年線資料
|
83
|
+
"""
|
84
|
+
return self._get_resampled("YE")
|
@@ -0,0 +1 @@
|
|
1
|
+
from .processor import TechProcessor
|