neurostats-API 0.0.22__py3-none-any.whl → 0.0.23__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 +1 -1
- neurostats_API/fetchers/balance_sheet.py +59 -36
- neurostats_API/fetchers/base.py +6 -2
- neurostats_API/fetchers/cash_flow.py +87 -66
- neurostats_API/fetchers/finance_overview.py +26 -26
- neurostats_API/fetchers/institution.py +211 -97
- neurostats_API/fetchers/margin_trading.py +121 -94
- neurostats_API/fetchers/month_revenue.py +139 -105
- neurostats_API/fetchers/profit_lose.py +100 -80
- neurostats_API/fetchers/tech.py +61 -26
- neurostats_API/fetchers/tej_finance_report.py +23 -8
- neurostats_API/fetchers/value_invest.py +32 -12
- neurostats_API/tools/tej_db/tej_db_percent_index.yaml +0 -3
- neurostats_API/tools/tej_db/tej_db_skip_index.yaml +12 -1
- neurostats_API/tools/tej_db/tej_db_thousand_index.yaml +0 -4
- neurostats_API/utils/calculate_value.py +5 -2
- neurostats_API/utils/data_process.py +11 -5
- neurostats_API/utils/logger.py +21 -0
- {neurostats_API-0.0.22.dist-info → neurostats_API-0.0.23.dist-info}/METADATA +2 -2
- neurostats_API-0.0.23.dist-info/RECORD +35 -0
- neurostats_API-0.0.22.dist-info/RECORD +0 -34
- {neurostats_API-0.0.22.dist-info → neurostats_API-0.0.23.dist-info}/WHEEL +0 -0
- {neurostats_API-0.0.22.dist-info → neurostats_API-0.0.23.dist-info}/top_level.txt +0 -0
@@ -1,5 +1,5 @@
|
|
1
1
|
from .base import StatsFetcher
|
2
|
-
from datetime import datetime, timedelta
|
2
|
+
from datetime import datetime, timedelta, date
|
3
3
|
import json
|
4
4
|
import numpy as np
|
5
5
|
import pandas as pd
|
@@ -19,6 +19,7 @@ class InstitutionFetcher(StatsFetcher):
|
|
19
19
|
|
20
20
|
def __init__(self, ticker, db_client):
|
21
21
|
super().__init__(ticker, db_client)
|
22
|
+
self.tej_collection = self.db['TWN/APISHRACT'] # TEJ備援
|
22
23
|
|
23
24
|
def prepare_query(self, start_date, end_date):
|
24
25
|
pipeline = super().prepare_query()
|
@@ -28,53 +29,65 @@ class InstitutionFetcher(StatsFetcher):
|
|
28
29
|
# "institution_trading": "$$target_season_data.institution_trading"
|
29
30
|
# }
|
30
31
|
|
31
|
-
pipeline.append(
|
32
|
-
|
33
|
-
"
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
"
|
38
|
-
"
|
39
|
-
"
|
40
|
-
"
|
41
|
-
|
42
|
-
|
43
|
-
"
|
44
|
-
"$
|
45
|
-
|
46
|
-
|
47
|
-
|
32
|
+
pipeline.append(
|
33
|
+
{
|
34
|
+
"$project": {
|
35
|
+
"_id": 0,
|
36
|
+
"ticker": 1,
|
37
|
+
"company_name": 1,
|
38
|
+
"daily_data": {
|
39
|
+
"$map": {
|
40
|
+
"input": {
|
41
|
+
"$filter": {
|
42
|
+
"input": "$daily_data",
|
43
|
+
"as": "daily",
|
44
|
+
"cond": {
|
45
|
+
"$and": [
|
46
|
+
{
|
47
|
+
"$gte":
|
48
|
+
["$$daily.date", start_date]
|
49
|
+
}, {
|
50
|
+
"$lte":
|
51
|
+
["$$daily.date", end_date]
|
52
|
+
}
|
53
|
+
]
|
54
|
+
}
|
48
55
|
}
|
49
|
-
}
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
}
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
56
|
+
},
|
57
|
+
"as": "target_daily_data",
|
58
|
+
"in": "$$target_daily_data"
|
59
|
+
}
|
60
|
+
},
|
61
|
+
"institution_trading": {
|
62
|
+
"$map": {
|
63
|
+
"input": {
|
64
|
+
"$filter": {
|
65
|
+
"input": "$institution_trading",
|
66
|
+
"as": "institution",
|
67
|
+
"cond": {
|
68
|
+
"$and": [
|
69
|
+
{
|
70
|
+
"$gte": [
|
71
|
+
"$$institution.date",
|
72
|
+
start_date
|
73
|
+
]
|
74
|
+
}, {
|
75
|
+
"$lte": [
|
76
|
+
"$$institution.date",
|
77
|
+
end_date
|
78
|
+
]
|
79
|
+
}
|
80
|
+
]
|
81
|
+
}
|
69
82
|
}
|
70
|
-
}
|
83
|
+
},
|
84
|
+
"as": "target_institution_data",
|
85
|
+
"in": "$$target_institution_data"
|
71
86
|
},
|
72
|
-
|
73
|
-
"in": "$$target_institution_data"
|
74
|
-
},
|
87
|
+
}
|
75
88
|
}
|
76
89
|
}
|
77
|
-
|
90
|
+
)
|
78
91
|
|
79
92
|
return pipeline
|
80
93
|
|
@@ -88,33 +101,42 @@ class InstitutionFetcher(StatsFetcher):
|
|
88
101
|
def query_data(self):
|
89
102
|
try:
|
90
103
|
latest_time = StatsDateTime.get_latest_time(
|
91
|
-
self.ticker, self.collection
|
104
|
+
self.ticker, self.collection
|
105
|
+
)['last_update_time']
|
92
106
|
latest_date = latest_time['institution_trading']['latest_date']
|
93
|
-
end_date = latest_date.replace(
|
94
|
-
|
95
|
-
second=0,
|
96
|
-
microsecond=0)
|
97
|
-
except Exception as e:
|
98
|
-
print(
|
99
|
-
f"No updated time for institution_trading in {self.ticker}, use current time instead"
|
107
|
+
end_date = latest_date.replace(
|
108
|
+
hour=0, minute=0, second=0, microsecond=0
|
100
109
|
)
|
110
|
+
except Exception as e:
|
101
111
|
end_date = datetime.now(self.timezone)
|
102
|
-
end_date =
|
112
|
+
end_date = end_date.replace(
|
113
|
+
hour=0, minute=0, second=0, microsecond=0
|
114
|
+
)
|
103
115
|
|
104
|
-
if (
|
116
|
+
if (end_date.hour < 17): # 拿不到今天的資料
|
105
117
|
end_date = end_date - timedelta(days=1)
|
106
118
|
|
107
119
|
start_date = end_date - timedelta(days=365)
|
108
120
|
|
109
121
|
fetched_data = self.collect_data(start_date, end_date)
|
110
122
|
|
111
|
-
fetched_data['daily_data'] = sorted(
|
112
|
-
|
113
|
-
|
123
|
+
fetched_data['daily_data'] = sorted(
|
124
|
+
fetched_data.get("daily_data", []), key=lambda x: x['date'], reverse=True
|
125
|
+
)
|
126
|
+
|
127
|
+
if (not fetched_data['institution_trading']):
|
128
|
+
# 找 TEJ 備援
|
129
|
+
fetched_data.update(
|
130
|
+
{
|
131
|
+
'institution_trading': self.collect_tej(start_date, end_date)
|
132
|
+
}
|
133
|
+
)
|
134
|
+
|
114
135
|
fetched_data['institution_trading'] = sorted(
|
115
|
-
fetched_data
|
136
|
+
fetched_data.get('institution_trading', []),
|
116
137
|
key=lambda x: x['date'],
|
117
|
-
reverse=True
|
138
|
+
reverse=True
|
139
|
+
) if fetched_data['institution_trading'] else None
|
118
140
|
|
119
141
|
table_dict = self.process_data(fetched_data)
|
120
142
|
|
@@ -131,16 +153,16 @@ class InstitutionFetcher(StatsFetcher):
|
|
131
153
|
|
132
154
|
# 交易價格與昨天交易
|
133
155
|
price_dict = {
|
134
|
-
"open": latest_daily_data['open'],
|
135
|
-
'close': latest_daily_data['close'],
|
156
|
+
"open": round(latest_daily_data['open'], 2),
|
157
|
+
'close': round(latest_daily_data['close'], 2),
|
136
158
|
'range':
|
137
|
-
f"{latest_daily_data['low']} - {latest_daily_data['high']}",
|
138
|
-
'volume': latest_daily_data['volume'] / 1000,
|
139
|
-
'last_open': yesterday_daily_data['open'],
|
140
|
-
'last_close': yesterday_daily_data['close'],
|
159
|
+
f"{latest_daily_data['low']:.2f} - {latest_daily_data['high']:.2f}",
|
160
|
+
'volume': round(latest_daily_data['volume'] / 1000, 2),
|
161
|
+
'last_open': round(yesterday_daily_data['open'], 2),
|
162
|
+
'last_close': round(yesterday_daily_data['close'], 2),
|
141
163
|
'last_range':
|
142
|
-
f"{yesterday_daily_data['low']} - {yesterday_daily_data['high']}",
|
143
|
-
'last_volume': yesterday_daily_data['volume'] / 1000
|
164
|
+
f"{yesterday_daily_data['low']:.2f} - {yesterday_daily_data['high']:.2f}",
|
165
|
+
'last_volume': round(yesterday_daily_data['volume'] / 1000, 2)
|
144
166
|
}
|
145
167
|
# 一年範圍
|
146
168
|
annual_lows = [data['low'] for data in daily_datas]
|
@@ -148,20 +170,36 @@ class InstitutionFetcher(StatsFetcher):
|
|
148
170
|
lowest = np.min(annual_lows).item()
|
149
171
|
highest = np.max(annual_highs).item()
|
150
172
|
|
151
|
-
price_dict['52weeks_range'] = f"{lowest} - {highest}"
|
173
|
+
price_dict['52weeks_range'] = f"{lowest:.2f} - {highest:.2f}"
|
152
174
|
table_dict['price'] = price_dict
|
153
175
|
|
154
176
|
# 發行股數 & 市值
|
155
177
|
# 沒有實作
|
156
178
|
|
179
|
+
table_dict['latest_trading'] = {
|
180
|
+
'date': date.today(),
|
181
|
+
'table': pd.DataFrame(
|
182
|
+
columns = ['category', 'variable', 'close', 'volume']
|
183
|
+
)
|
184
|
+
}
|
185
|
+
table_dict['annual_trading'] = pd.DataFrame(
|
186
|
+
columns = ['date', 'close', 'volume']
|
187
|
+
)
|
188
|
+
|
189
|
+
if (not institution_tradings):
|
190
|
+
return table_dict
|
191
|
+
|
157
192
|
# 今日法人買賣
|
158
|
-
latest_trading = institution_tradings[0]
|
193
|
+
latest_trading = institution_tradings[0] if (institution_tradings) else {
|
194
|
+
'date': date.today()
|
195
|
+
}
|
159
196
|
table_dict['latest_trading'] = {
|
160
197
|
"date":
|
161
198
|
latest_trading['date'],
|
162
199
|
"table":
|
163
|
-
self.process_latest_trading(
|
164
|
-
|
200
|
+
self.process_latest_trading(
|
201
|
+
latest_trading, latest_daily_data['volume']
|
202
|
+
)
|
165
203
|
}
|
166
204
|
# 一年內法人
|
167
205
|
annual_dates = [
|
@@ -182,17 +220,19 @@ class InstitutionFetcher(StatsFetcher):
|
|
182
220
|
for data in institution_tradings
|
183
221
|
}
|
184
222
|
|
185
|
-
|
223
|
+
annual_trading_dates = sorted(list(annual_trading.keys()))
|
224
|
+
annual_trading_skip = {
|
186
225
|
date: {
|
187
|
-
"close": annual_closes
|
188
|
-
"volume": annual_volumes
|
226
|
+
"close": annual_closes.get(date, 0.0),
|
227
|
+
"volume": annual_volumes.get(date, 0.0),
|
189
228
|
**annual_trading[date]
|
190
229
|
}
|
191
|
-
for date in
|
230
|
+
for date in annual_trading_dates
|
192
231
|
}
|
193
232
|
|
194
233
|
table_dict['annual_trading'] = self.process_annual_trading(
|
195
|
-
annual_dates,
|
234
|
+
annual_dates, annual_trading_skip
|
235
|
+
)
|
196
236
|
|
197
237
|
return table_dict
|
198
238
|
|
@@ -206,30 +246,36 @@ class InstitutionFetcher(StatsFetcher):
|
|
206
246
|
|
207
247
|
for key in latest_trading.keys():
|
208
248
|
if (key.find("外陸資") >= 0 or key.find("外資") >= 0):
|
209
|
-
self.target_institution(
|
210
|
-
|
249
|
+
self.target_institution(
|
250
|
+
latest_trading, latest_table['foreign'], key, volume
|
251
|
+
)
|
211
252
|
elif (key.find("自營商") >= 0):
|
212
|
-
self.target_institution(
|
213
|
-
|
253
|
+
self.target_institution(
|
254
|
+
latest_trading, latest_table['prop'], key, volume
|
255
|
+
)
|
214
256
|
elif (key.find("投信") >= 0):
|
215
|
-
self.target_institution(
|
216
|
-
|
257
|
+
self.target_institution(
|
258
|
+
latest_trading, latest_table['mutual'], key, volume
|
259
|
+
)
|
217
260
|
elif (key.find("三大法人") >= 0):
|
218
|
-
self.target_institution(
|
219
|
-
|
220
|
-
|
261
|
+
self.target_institution(
|
262
|
+
latest_trading, latest_table['institutional_investor'], key,
|
263
|
+
volume
|
264
|
+
)
|
221
265
|
# 計算合計
|
222
266
|
for unit in ['stock', 'percentage']:
|
223
267
|
# 買進總和
|
224
268
|
latest_table['institutional_investor']['buy'][unit] = (
|
225
269
|
latest_table['foreign']['buy'][unit] +
|
226
270
|
latest_table['prop']['buy'][unit] +
|
227
|
-
latest_table['mutual']['buy'][unit]
|
271
|
+
latest_table['mutual']['buy'][unit]
|
272
|
+
)
|
228
273
|
# 賣出總和
|
229
274
|
latest_table['institutional_investor']['sell'][unit] = (
|
230
275
|
latest_table['foreign']['sell'][unit] +
|
231
276
|
latest_table['prop']['sell'][unit] +
|
232
|
-
latest_table['mutual']['sell'][unit]
|
277
|
+
latest_table['mutual']['sell'][unit]
|
278
|
+
)
|
233
279
|
|
234
280
|
frames = []
|
235
281
|
for category, trades in latest_table.items():
|
@@ -243,18 +289,22 @@ class InstitutionFetcher(StatsFetcher):
|
|
243
289
|
'type', 'category', 'stock', 'price', 'average_price', 'percentage'
|
244
290
|
]]
|
245
291
|
|
246
|
-
latest_df = pd.melt(
|
247
|
-
|
248
|
-
|
249
|
-
|
292
|
+
latest_df = pd.melt(
|
293
|
+
latest_df,
|
294
|
+
id_vars=['type', 'category'],
|
295
|
+
var_name='variable',
|
296
|
+
value_name='value'
|
297
|
+
)
|
250
298
|
|
251
|
-
latest_df = latest_df.pivot_table(
|
252
|
-
|
253
|
-
|
254
|
-
|
299
|
+
latest_df = latest_df.pivot_table(
|
300
|
+
index=['category', 'variable'],
|
301
|
+
columns='type',
|
302
|
+
values='value',
|
303
|
+
aggfunc='first'
|
304
|
+
)
|
255
305
|
|
256
306
|
# 重設列名,去除多層索引
|
257
|
-
latest_df.columns.name = None
|
307
|
+
latest_df.columns.name = None # 去除列名稱
|
258
308
|
latest_df = latest_df.reset_index()
|
259
309
|
|
260
310
|
return latest_df
|
@@ -268,8 +318,9 @@ class InstitutionFetcher(StatsFetcher):
|
|
268
318
|
elif (key.find("賣出") >= 0):
|
269
319
|
self.cal_institution(old_table, new_table['sell'], key, volume)
|
270
320
|
elif (key.find("買賣超") >= 0):
|
271
|
-
self.cal_institution(
|
272
|
-
|
321
|
+
self.cal_institution(
|
322
|
+
old_table, new_table['over_buy_sell'], key, volume
|
323
|
+
)
|
273
324
|
|
274
325
|
def cal_institution(self, old_table, new_table, key, volume):
|
275
326
|
new_table['stock'] = np.round(old_table[key] / 1000, 2).item()
|
@@ -297,3 +348,66 @@ class InstitutionFetcher(StatsFetcher):
|
|
297
348
|
"percentage": 0
|
298
349
|
},
|
299
350
|
}
|
351
|
+
|
352
|
+
def collect_tej(self, start_date, end_date):
|
353
|
+
pipeline = self.prepare_tej_query(start_date, end_date)
|
354
|
+
|
355
|
+
result = self.tej_collection.aggregate(pipeline)
|
356
|
+
result = result.to_list()
|
357
|
+
|
358
|
+
result_df = pd.DataFrame(result[0]['filtered_data'])
|
359
|
+
|
360
|
+
proj_columns = {
|
361
|
+
"mdate": "date",
|
362
|
+
"qfii_buy": "外資買進股數",
|
363
|
+
"qfii_sell": "外資賣出股數",
|
364
|
+
"qfii_ex": "外資買賣超股數",
|
365
|
+
"fund_buy" : "投信買進股數",
|
366
|
+
"fund_sell" : "投信賣出股數",
|
367
|
+
"fund_ex" : "投信買賣超股數",
|
368
|
+
"dlrp_buy" : "自營商買賣超股數(自行)",
|
369
|
+
"dlrp_sell" : "自營商買進股數(自行)",
|
370
|
+
"dlrp_ex" : "自營商賣出股數(自行)",
|
371
|
+
"dlrh_buy" : "自營商買進股數(避險)",
|
372
|
+
"dlrh_sell" : "自營商賣出股數(避險)",
|
373
|
+
"dlrh_ex" : "自營買賣超股數(避險)",
|
374
|
+
"tot_ex" : "三大法人買賣超股數"
|
375
|
+
}
|
376
|
+
|
377
|
+
target_index = list(proj_columns.keys())[1:]
|
378
|
+
result_df.loc[:, target_index] = result_df.loc[:, target_index].map(lambda x : 1000 * x) # TEJ單位為千股
|
379
|
+
|
380
|
+
result_df = result_df.loc[:, list(proj_columns.keys())]
|
381
|
+
result_df = result_df.reindex(columns = list(proj_columns.keys()), fill_value = None)
|
382
|
+
result_df = result_df.rename(columns = proj_columns)
|
383
|
+
|
384
|
+
return result_df.to_dict(orient = 'records')
|
385
|
+
|
386
|
+
def prepare_tej_query(self, start_date, end_date):
|
387
|
+
return [
|
388
|
+
{
|
389
|
+
"$match": {
|
390
|
+
"data.mdate": {
|
391
|
+
"$gte": start_date,
|
392
|
+
"$lte": end_date
|
393
|
+
}
|
394
|
+
}
|
395
|
+
},
|
396
|
+
{
|
397
|
+
"$unwind": "$data"
|
398
|
+
},
|
399
|
+
{
|
400
|
+
"$match": {
|
401
|
+
"data.mdate": {
|
402
|
+
"$gte": start_date,
|
403
|
+
"$lte": end_date
|
404
|
+
}
|
405
|
+
}
|
406
|
+
},
|
407
|
+
{
|
408
|
+
"$group": {
|
409
|
+
"_id": "$ticker",
|
410
|
+
"filtered_data": { "$push": "$data" }
|
411
|
+
}
|
412
|
+
}
|
413
|
+
]
|