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.
@@ -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
- "$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
- }]
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
- "as": "target_daily_data",
52
- "in": "$$target_daily_data"
53
- }
54
- },
55
- "institution_trading": {
56
- "$map": {
57
- "input": {
58
- "$filter": {
59
- "input": "$institution_trading",
60
- "as": "institution",
61
- "cond": {
62
- "$and": [{
63
- "$gte":
64
- ["$$institution.date", start_date]
65
- }, {
66
- "$lte":
67
- ["$$institution.date", end_date]
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
- "as": "target_institution_data",
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)['last_update_time']
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(hour=0,
94
- minute=0,
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 = date.replace(hour=0, minute=0, second=0, microsecond=0)
112
+ end_date = end_date.replace(
113
+ hour=0, minute=0, second=0, microsecond=0
114
+ )
103
115
 
104
- if (date.hour < 17): # 拿不到今天的資料
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(fetched_data['daily_data'],
112
- key=lambda x: x['date'],
113
- reverse=True)
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['institution_trading'],
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(latest_trading,
164
- latest_daily_data['volume'])
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
- annual_trading = {
223
+ annual_trading_dates = sorted(list(annual_trading.keys()))
224
+ annual_trading_skip = {
186
225
  date: {
187
- "close": annual_closes[date],
188
- "volume": annual_volumes[date],
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 annual_dates
230
+ for date in annual_trading_dates
192
231
  }
193
232
 
194
233
  table_dict['annual_trading'] = self.process_annual_trading(
195
- annual_dates, annual_trading)
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(latest_trading,
210
- latest_table['foreign'], key, volume)
249
+ self.target_institution(
250
+ latest_trading, latest_table['foreign'], key, volume
251
+ )
211
252
  elif (key.find("自營商") >= 0):
212
- self.target_institution(latest_trading, latest_table['prop'],
213
- key, volume)
253
+ self.target_institution(
254
+ latest_trading, latest_table['prop'], key, volume
255
+ )
214
256
  elif (key.find("投信") >= 0):
215
- self.target_institution(latest_trading, latest_table['mutual'],
216
- key, volume)
257
+ self.target_institution(
258
+ latest_trading, latest_table['mutual'], key, volume
259
+ )
217
260
  elif (key.find("三大法人") >= 0):
218
- self.target_institution(latest_trading,
219
- latest_table['institutional_investor'],
220
- key, volume)
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(latest_df,
247
- id_vars=['type', 'category'],
248
- var_name='variable',
249
- value_name='value')
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(index=['category', 'variable'],
252
- columns='type',
253
- values='value',
254
- aggfunc='first')
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(old_table, new_table['over_buy_sell'], key,
272
- volume)
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
+ ]