neurostats-API 0.0.25rc1__py3-none-any.whl → 1.0.0rc1__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.
Files changed (105) hide show
  1. neurostats_API/__init__.py +1 -1
  2. neurostats_API/async_mode/__init__.py +13 -0
  3. neurostats_API/async_mode/db/__init__.py +3 -0
  4. neurostats_API/async_mode/db/base.py +24 -0
  5. neurostats_API/async_mode/db/tej.py +10 -0
  6. neurostats_API/async_mode/db/twse.py +8 -0
  7. neurostats_API/async_mode/db/us.py +9 -0
  8. neurostats_API/async_mode/db_extractors/__init__.py +20 -0
  9. neurostats_API/async_mode/db_extractors/base.py +66 -0
  10. neurostats_API/async_mode/db_extractors/daily/__init__.py +7 -0
  11. neurostats_API/async_mode/db_extractors/daily/base.py +89 -0
  12. neurostats_API/async_mode/db_extractors/daily/tej_chip.py +14 -0
  13. neurostats_API/async_mode/db_extractors/daily/tej_tech.py +12 -0
  14. neurostats_API/async_mode/db_extractors/daily/twse_chip.py +44 -0
  15. neurostats_API/async_mode/db_extractors/daily/value.py +93 -0
  16. neurostats_API/async_mode/db_extractors/daily/yf.py +12 -0
  17. neurostats_API/async_mode/db_extractors/month_revenue/__init__.py +1 -0
  18. neurostats_API/async_mode/db_extractors/month_revenue/base.py +140 -0
  19. neurostats_API/async_mode/db_extractors/month_revenue/twse.py +5 -0
  20. neurostats_API/async_mode/db_extractors/seasonal/__init__.py +4 -0
  21. neurostats_API/async_mode/db_extractors/seasonal/balance_sheet.py +19 -0
  22. neurostats_API/async_mode/db_extractors/seasonal/base.py +159 -0
  23. neurostats_API/async_mode/db_extractors/seasonal/cashflow.py +10 -0
  24. neurostats_API/async_mode/db_extractors/seasonal/profit_lose.py +17 -0
  25. neurostats_API/async_mode/db_extractors/seasonal/tej.py +87 -0
  26. neurostats_API/async_mode/factory/__init__.py +1 -0
  27. neurostats_API/async_mode/factory/extractor_factory.py +168 -0
  28. neurostats_API/async_mode/factory/transformer_factory.py +164 -0
  29. neurostats_API/async_mode/fetchers/__init__.py +10 -0
  30. neurostats_API/async_mode/fetchers/balance_sheet.py +31 -0
  31. neurostats_API/async_mode/fetchers/base.py +48 -0
  32. neurostats_API/async_mode/fetchers/cash_flow.py +56 -0
  33. neurostats_API/async_mode/fetchers/finance_overview.py +134 -0
  34. neurostats_API/async_mode/fetchers/month_revenue.py +35 -0
  35. neurostats_API/async_mode/fetchers/profit_lose.py +46 -0
  36. neurostats_API/async_mode/fetchers/tech.py +205 -0
  37. neurostats_API/async_mode/fetchers/tej.py +88 -0
  38. neurostats_API/async_mode/fetchers/twse_institution.py +62 -0
  39. neurostats_API/async_mode/fetchers/twse_margin.py +100 -0
  40. neurostats_API/async_mode/fetchers/value.py +76 -0
  41. neurostats_API/config/company_list/ticker_index_industry_map.json +7946 -0
  42. neurostats_API/config/company_list/us.json +9986 -0
  43. neurostats_API/{tools → config}/tej_db/tej_db_skip_index.yaml +0 -2
  44. neurostats_API/{tools → config}/twse/profit_lose.yaml +0 -6
  45. neurostats_API/fetchers/finance_overview.py +27 -5
  46. neurostats_API/transformers/__init__.py +40 -0
  47. neurostats_API/transformers/balance_sheet/__init__.py +2 -0
  48. neurostats_API/transformers/balance_sheet/base.py +51 -0
  49. neurostats_API/transformers/balance_sheet/twse.py +76 -0
  50. neurostats_API/transformers/balance_sheet/us.py +30 -0
  51. neurostats_API/transformers/base.py +110 -0
  52. neurostats_API/transformers/cash_flow/__init__.py +2 -0
  53. neurostats_API/transformers/cash_flow/base.py +114 -0
  54. neurostats_API/transformers/cash_flow/twse.py +68 -0
  55. neurostats_API/transformers/cash_flow/us.py +38 -0
  56. neurostats_API/transformers/daily_chip/__init__.py +1 -0
  57. neurostats_API/transformers/daily_chip/base.py +5 -0
  58. neurostats_API/transformers/daily_chip/tej.py +0 -0
  59. neurostats_API/transformers/daily_chip/twse_chip.py +412 -0
  60. neurostats_API/transformers/daily_chip/utils/__init__.py +0 -0
  61. neurostats_API/transformers/daily_chip/utils/institution.py +90 -0
  62. neurostats_API/transformers/daily_chip/utils/margin_trading.py +2 -0
  63. neurostats_API/transformers/daily_chip/utils/security_lending.py +0 -0
  64. neurostats_API/transformers/daily_tech/__init__.py +1 -0
  65. neurostats_API/transformers/daily_tech/base.py +5 -0
  66. neurostats_API/transformers/daily_tech/tech.py +84 -0
  67. neurostats_API/transformers/daily_tech/utils/__init__.py +1 -0
  68. neurostats_API/transformers/daily_tech/utils/processor.py +251 -0
  69. neurostats_API/transformers/finance_overview/__init__.py +2 -0
  70. neurostats_API/transformers/finance_overview/agent_overview.py +55 -0
  71. neurostats_API/transformers/finance_overview/base.py +824 -0
  72. neurostats_API/transformers/finance_overview/stats_overview.py +64 -0
  73. neurostats_API/transformers/month_revenue/__init__.py +1 -0
  74. neurostats_API/transformers/month_revenue/base.py +60 -0
  75. neurostats_API/transformers/month_revenue/twse.py +129 -0
  76. neurostats_API/transformers/profit_lose/__init__.py +2 -0
  77. neurostats_API/transformers/profit_lose/base.py +82 -0
  78. neurostats_API/transformers/profit_lose/twse.py +133 -0
  79. neurostats_API/transformers/profit_lose/us.py +25 -0
  80. neurostats_API/transformers/tej/__init__.py +1 -0
  81. neurostats_API/transformers/tej/base.py +149 -0
  82. neurostats_API/transformers/tej/finance_statement.py +80 -0
  83. neurostats_API/transformers/value/__init__.py +1 -0
  84. neurostats_API/transformers/value/base.py +5 -0
  85. neurostats_API/transformers/value/tej.py +8 -0
  86. neurostats_API/transformers/value/twse.py +48 -0
  87. neurostats_API/utils/__init__.py +1 -1
  88. neurostats_API/utils/data_process.py +10 -6
  89. neurostats_API/utils/exception.py +8 -0
  90. neurostats_API/utils/logger.py +21 -0
  91. neurostats_API-1.0.0rc1.dist-info/METADATA +102 -0
  92. neurostats_API-1.0.0rc1.dist-info/RECORD +119 -0
  93. neurostats_API-0.0.25rc1.dist-info/METADATA +0 -858
  94. neurostats_API-0.0.25rc1.dist-info/RECORD +0 -36
  95. /neurostats_API/{tools → config}/company_list/tw.json +0 -0
  96. /neurostats_API/{tools → config}/company_list/us_TradingView_list.json +0 -0
  97. /neurostats_API/{tools → config}/tej_db/tej_db_index.yaml +0 -0
  98. /neurostats_API/{tools → config}/tej_db/tej_db_percent_index.yaml +0 -0
  99. /neurostats_API/{tools → config}/tej_db/tej_db_thousand_index.yaml +0 -0
  100. /neurostats_API/{tools → config}/twse/balance_sheet.yaml +0 -0
  101. /neurostats_API/{tools → config}/twse/cash_flow_percentage.yaml +0 -0
  102. /neurostats_API/{tools → config}/twse/finance_overview_dict.yaml +0 -0
  103. /neurostats_API/{tools → config}/twse/seasonal_data_field_dict.txt +0 -0
  104. {neurostats_API-0.0.25rc1.dist-info → neurostats_API-1.0.0rc1.dist-info}/WHEEL +0 -0
  105. {neurostats_API-0.0.25rc1.dist-info → neurostats_API-1.0.0rc1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,412 @@
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 (len(fetched_data) < 1):
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": latest_data.get('date', datetime.today().strftime("%Y-%m-%d")),
66
+ **self._process_latest_trading(latest_trade, latest_tech, key)
67
+ }
68
+
69
+ def _process_annual(self, fetched_dict: dict, tech_dict: dict, key: str):
70
+ if (fetched_dict):
71
+ fetched_data = [
72
+ {
73
+ 'date': date,
74
+ 'close': tech_dict.get(date, {}).get('close', 0.0),
75
+ 'volume': tech_dict.get(date, {}).get('volume', 0.0),
76
+ **trading
77
+ } for date, trading in fetched_dict.items()
78
+ ]
79
+ fetched_df = self._process_annual_trading(fetched_data, key)
80
+
81
+ else:
82
+ fetched_data = [
83
+ {
84
+ 'date': date,
85
+ 'close': tech_dict.get(date, {}).get('close', 0.0),
86
+ 'volume': tech_dict.get(date, {}).get('volume', 0.0),
87
+ } for date in tech_dict.keys()
88
+ ]
89
+ fetched_df = pd.DataFrame(fetched_data)
90
+
91
+ return {key: fetched_df}
92
+
93
+ def _process_latest_trading(self, latest_trading, latest_tech, key):
94
+ FN_MAP = {
95
+ 'institution_trading': self._process_latest_institution,
96
+ 'margin_trading': self._process_latest_margin,
97
+ 'security_lending': self._process_latest_security
98
+ }
99
+
100
+ process_fn = FN_MAP.get(key)
101
+ return process_fn(latest_trading, latest_tech)
102
+
103
+ def _process_annual_trading(self, fetched_data, key):
104
+ FN_MAP = {
105
+ 'institution_trading': self._process_annual_institution,
106
+ 'margin_trading': self._process_annual_margin,
107
+ 'security_lending': self._process_annual_security
108
+ }
109
+
110
+ process_fn = FN_MAP.get(key)
111
+ return process_fn(fetched_data)
112
+
113
+ def _process_tech(self, tech_df):
114
+ latest_daily_data = tech_df.iloc[-1]
115
+ yesterday_daily_data = tech_df.iloc[-2]
116
+
117
+ price_dict = {
118
+ "open": round(latest_daily_data['open'], 2),
119
+ 'close': round(latest_daily_data['close'], 2),
120
+ 'range':
121
+ f"{latest_daily_data['low']:.2f} - {latest_daily_data['high']:.2f}",
122
+ 'volume': round(latest_daily_data['volume'] / 1000, 2),
123
+ 'last_open': round(yesterday_daily_data['open'], 2),
124
+ 'last_close': round(yesterday_daily_data['close'], 2),
125
+ 'last_range':
126
+ f"{yesterday_daily_data['low']:.2f} - {yesterday_daily_data['high']:.2f}",
127
+ 'last_volume': round(yesterday_daily_data['volume'] / 1000, 2)
128
+ }
129
+
130
+ lowest = pd.to_numeric(tech_df["low"], errors="coerce").min().item()
131
+ highest = pd.to_numeric(tech_df["high"], errors="coerce").max().item()
132
+ price_dict['52weeks_range'] = f"{lowest:.2f} - {highest:.2f}"
133
+
134
+ return price_dict
135
+
136
+ def _process_latest_institution(self, latest_trading, latest_tech):
137
+
138
+ def _default_institution_chart():
139
+ return {
140
+ "buy": {
141
+ "stock": None,
142
+ "price": None,
143
+ "average_price": None,
144
+ "percentage": None
145
+ },
146
+ "sell": {
147
+ "stock": None,
148
+ "price": None,
149
+ "average_price": None,
150
+ "percentage": None
151
+ },
152
+ "over_buy_sell": {
153
+ "stock": None,
154
+ "price": None,
155
+ "average_price": None,
156
+ "percentage": None
157
+ },
158
+ }
159
+
160
+ latest_table = {
161
+ "foreign": _default_institution_chart(),
162
+ "mutual": _default_institution_chart(),
163
+ "prop": _default_institution_chart(),
164
+ "institutional_investor": _default_institution_chart(),
165
+ }
166
+
167
+ volume = latest_tech['volume']
168
+
169
+ if (latest_trading is not None):
170
+ for key in latest_trading.keys():
171
+ if (key.find("外陸資") >= 0 or key.find("外資") >= 0):
172
+ self._target_institution(
173
+ latest_trading, latest_table['foreign'], key, volume
174
+ )
175
+ elif (key.find("自營商") >= 0):
176
+ self._target_institution(
177
+ latest_trading, latest_table['prop'], key, volume
178
+ )
179
+ elif (key.find("投信") >= 0):
180
+ self._target_institution(
181
+ latest_trading, latest_table['mutual'], key, volume
182
+ )
183
+ elif (key.find("三大法人") >= 0):
184
+ self._target_institution(
185
+ latest_trading, latest_table['institutional_investor'], key,
186
+ volume
187
+ )
188
+ # 計算合計
189
+ for unit in ['stock', 'percentage']:
190
+ # 買進總和
191
+ latest_table['institutional_investor']['buy'][unit] = (
192
+ latest_table['foreign']['buy'][unit] +
193
+ latest_table['prop']['buy'][unit] +
194
+ latest_table['mutual']['buy'][unit]
195
+ )
196
+ # 賣出總和
197
+ latest_table['institutional_investor']['sell'][unit] = (
198
+ latest_table['foreign']['sell'][unit] +
199
+ latest_table['prop']['sell'][unit] +
200
+ latest_table['mutual']['sell'][unit]
201
+ )
202
+
203
+ frames = []
204
+ for category, trades in latest_table.items():
205
+ temp_df = pd.DataFrame(trades).T
206
+ temp_df['category'] = category
207
+ frames.append(temp_df)
208
+
209
+ latest_df = pd.concat(frames)
210
+ latest_df = latest_df.reset_index().rename(columns={'index': 'type'})
211
+ latest_df = latest_df[[
212
+ 'type', 'category', 'stock', 'price', 'average_price', 'percentage'
213
+ ]]
214
+
215
+ latest_df = pd.melt(
216
+ latest_df,
217
+ id_vars=['type', 'category'],
218
+ var_name='variable',
219
+ value_name='value'
220
+ )
221
+
222
+ latest_df = latest_df.pivot_table(
223
+ index=['category', 'variable'],
224
+ columns='type',
225
+ values='value',
226
+ aggfunc='first'
227
+ )
228
+
229
+ # 重設列名,去除多層索引
230
+ latest_df.columns.name = None # 去除列名稱
231
+ latest_df = latest_df.reset_index()
232
+
233
+ return {"institution_trading": latest_df}
234
+
235
+ def _process_annual_institution(self, trading_datas):
236
+
237
+ return pd.DataFrame(trading_datas)
238
+
239
+ def _process_annual_margin(self, trading_datas):
240
+ # 融資
241
+ def _process(data):
242
+ if ('現金償還' in data['financing'].keys()):
243
+ data['financing']['現償'] = data['financing'].pop('現金償還')
244
+ data['short_selling']['現償'] = data['short_selling'].pop(
245
+ '現券償還', data['financing']['現償']
246
+ )
247
+ financing = {
248
+ f"融資_{type_name}": num_of_stocks
249
+ for type_name, num_of_stocks in data['financing'].items()
250
+ }
251
+
252
+ short_selling = {
253
+ f"融券_{type_name}": num_of_stocks
254
+ for type_name, num_of_stocks in data['short_selling'].items()
255
+ }
256
+
257
+ security_offset = data['資券互抵']
258
+
259
+ return {**financing, **short_selling, '資券互抵': security_offset}
260
+
261
+ if (trading_datas):
262
+ return_datas = [
263
+ {
264
+ 'date': data['date'],
265
+ 'close': data['close'],
266
+ 'volume': data['volume'],
267
+ **_process(data)
268
+ } for data in trading_datas
269
+ ]
270
+ else:
271
+ return_datas = []
272
+
273
+ # 接起來
274
+ return_datas = pd.DataFrame(return_datas)
275
+
276
+ return return_datas
277
+
278
+ def _process_annual_security(self, trading_datas):
279
+
280
+ def _process(data):
281
+ stock_lendings = {
282
+ f"借券_{type_name}":
283
+ (num_of_stocks /
284
+ 1000) if isinstance(num_of_stocks,
285
+ (int, float)) else num_of_stocks
286
+ for type_name, num_of_stocks in data['stock_lending'].items()
287
+ }
288
+
289
+ return stock_lendings
290
+
291
+ if (trading_datas):
292
+ return_datas = [
293
+ {
294
+ 'date': data['date'],
295
+ 'close': data['close'],
296
+ 'volume': data['volume'],
297
+ **_process(data)
298
+ } for data in trading_datas
299
+ ]
300
+ else:
301
+ return_datas = []
302
+
303
+ # 接起來
304
+ return_datas = pd.DataFrame(return_datas)
305
+
306
+ return return_datas
307
+
308
+ def _target_institution(self, old_table, new_table, key, volume):
309
+ if (key.find("買進") >= 0):
310
+ self._cal_institution(old_table, new_table['buy'], key, volume)
311
+ elif (key.find("賣出") >= 0):
312
+ self._cal_institution(old_table, new_table['sell'], key, volume)
313
+ elif (key.find("買賣超") >= 0):
314
+ self._cal_institution(
315
+ old_table, new_table['over_buy_sell'], key, volume
316
+ )
317
+
318
+ def _cal_institution(self, old_table, new_table, key, volume):
319
+ new_table['stock'] = np.round(old_table[key] / 1000, 2).item()
320
+ new_table['percentage'] = np.round((old_table[key] / volume) * 100,
321
+ 2).item()
322
+
323
+ def _process_latest_margin(self, latest_trading, *args):
324
+ def _default_margin_chart():
325
+ dafault_dict = {
326
+ "financing": {},
327
+ "short_selling": {}
328
+ }
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
+ def _default_margin_chart():
358
+ return {
359
+ "stock_lending":
360
+ pd.DataFrame(index = ["當日賣出", "現償", "當日還券", "當日調整", "當日餘額", "次一營業日可限額"])
361
+ }
362
+
363
+ if (latest_trading is None):
364
+ return _default_margin_chart()
365
+
366
+ latest_stock_lending = latest_trading['stock_lending']
367
+
368
+ latest_stock_lending = {
369
+ type_name: StatsProcessor.cal_round_int(value / 1000)
370
+ for type_name, value in latest_stock_lending.items()
371
+ }
372
+ latest_stock_lending.pop("前日餘額")
373
+ latest_stock_lending_df = pd.DataFrame.from_dict(
374
+ latest_stock_lending, orient="index", columns=['stock_lending']
375
+ )
376
+
377
+ return latest_stock_lending_df
378
+
379
+ def _get_empty_structure(self):
380
+ return_dict = {
381
+ "ticker": self.ticker,
382
+ "company_name": self.company_name,
383
+ }
384
+
385
+ return_dict['latest_trading'] = {
386
+ 'date': date.today(),
387
+ 'institution_trading': pd.DataFrame(),
388
+ 'margin_trading': pd.DataFrame(),
389
+ 'security_lending': pd.DataFrame()
390
+ }
391
+
392
+ return_dict['annual_trading'] = {
393
+ 'institution_trading':
394
+ pd.DataFrame(columns=['date', 'close', 'volume']),
395
+ 'margin_trading': pd.DataFrame(columns=['date', 'close', 'volume']),
396
+ 'security_lending':
397
+ pd.DataFrame(columns=['date', 'close', 'volume']),
398
+ }
399
+
400
+ return_dict['price'] = {
401
+ "open": None,
402
+ 'close': None,
403
+ 'range': None,
404
+ 'volume': None,
405
+ 'last_open': None,
406
+ 'last_close': None,
407
+ 'last_range': None,
408
+ 'last_volume': None,
409
+ '52weeks_range': None
410
+ }
411
+
412
+ return return_dict
@@ -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()
@@ -0,0 +1,2 @@
1
+ class MarginTradingProcessor:
2
+ pass
@@ -0,0 +1 @@
1
+ from .tech import DailyTechTransformer
@@ -0,0 +1,5 @@
1
+ from ..base import BaseTransformer
2
+
3
+ class BaseDailyTransformer(BaseTransformer):
4
+ def __init__(self, ticker, company_name, zone):
5
+ super().__init__(ticker, company_name, zone)
@@ -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