neurostats-API 0.0.24__py3-none-any.whl → 0.0.25__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 +2 -1
- neurostats_API/fetchers/__init__.py +1 -1
- neurostats_API/fetchers/balance_sheet.py +1 -1
- neurostats_API/fetchers/finance_overview.py +513 -194
- neurostats_API/fetchers/institution.py +5 -2
- neurostats_API/tools/company_list/us_TradingView_list.json +49922 -0
- neurostats_API/utils/__init__.py +2 -1
- neurostats_API/utils/data_process.py +2 -2
- neurostats_API/utils/exception.py +3 -0
- {neurostats_API-0.0.24.dist-info → neurostats_API-0.0.25.dist-info}/METADATA +3 -2
- {neurostats_API-0.0.24.dist-info → neurostats_API-0.0.25.dist-info}/RECORD +13 -13
- neurostats_API/fetchers/macro_daily_event.py +0 -8
- neurostats_API/utils/logger.py +0 -21
- {neurostats_API-0.0.24.dist-info → neurostats_API-0.0.25.dist-info}/WHEEL +0 -0
- {neurostats_API-0.0.24.dist-info → neurostats_API-0.0.25.dist-info}/top_level.txt +0 -0
@@ -1,8 +1,17 @@
|
|
1
1
|
from .base import StatsFetcher, StatsDateTime
|
2
|
+
from datetime import datetime
|
2
3
|
import json
|
3
4
|
import numpy as np
|
4
5
|
import pandas as pd
|
5
|
-
from
|
6
|
+
from pymongo import ASCENDING, DESCENDING
|
7
|
+
import pytz
|
8
|
+
import holidays
|
9
|
+
import warnings
|
10
|
+
warnings.filterwarnings(
|
11
|
+
"ignore",
|
12
|
+
category=holidays.deprecations.v1_incompatibility.FutureIncompatibilityWarning
|
13
|
+
)
|
14
|
+
from ..utils import StatsDateTime, StatsProcessor, NoCompanyError
|
6
15
|
import importlib.resources as pkg_resources
|
7
16
|
import yaml
|
8
17
|
|
@@ -16,9 +25,11 @@ class FinanceOverviewFetcher(StatsFetcher):
|
|
16
25
|
super().__init__(ticker, db_client)
|
17
26
|
|
18
27
|
self.target_fields = StatsProcessor.load_yaml(
|
19
|
-
"twse/finance_overview_dict.yaml"
|
28
|
+
"twse/finance_overview_dict.yaml"
|
29
|
+
)
|
20
30
|
self.inverse_dict = StatsProcessor.load_txt(
|
21
|
-
"twse/seasonal_data_field_dict.txt", json_load=True
|
31
|
+
"twse/seasonal_data_field_dict.txt", json_load=True
|
32
|
+
)
|
22
33
|
|
23
34
|
def prepare_query(self, target_year, target_season):
|
24
35
|
|
@@ -33,47 +44,56 @@ class FinanceOverviewFetcher(StatsFetcher):
|
|
33
44
|
try:
|
34
45
|
small_targets = target_sets['field']
|
35
46
|
|
36
|
-
value_index = target_sets['value']
|
47
|
+
value_index = target_sets['value'] # "金額" or "%"
|
37
48
|
|
38
49
|
for small_target in small_targets:
|
39
50
|
big_target = self.inverse_dict[
|
40
|
-
|
51
|
+
small_target] # balance_sheet/profit_lose/cash_flow
|
41
52
|
if (small_target == "利息費用_bank"):
|
42
53
|
small_target = small_target[:small_target.find("_bank")]
|
43
|
-
target_query.update(
|
44
|
-
|
45
|
-
|
46
|
-
|
54
|
+
target_query.update(
|
55
|
+
{
|
56
|
+
f"{key}":
|
57
|
+
f"$$target_season_data.{big_target}.{small_target}.{value_index}"
|
58
|
+
}
|
59
|
+
)
|
47
60
|
except Exception:
|
48
61
|
continue
|
49
62
|
|
50
|
-
pipeline.append(
|
51
|
-
|
52
|
-
"
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
"
|
57
|
-
"
|
58
|
-
"
|
59
|
-
"
|
60
|
-
|
61
|
-
|
62
|
-
"
|
63
|
-
"$
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
63
|
+
pipeline.append(
|
64
|
+
{
|
65
|
+
"$project": {
|
66
|
+
"_id": 0,
|
67
|
+
"ticker": 1,
|
68
|
+
"company_name": 1,
|
69
|
+
"seasonal_data": {
|
70
|
+
"$map": {
|
71
|
+
"input": {
|
72
|
+
"$filter": {
|
73
|
+
"input": "$seasonal_data",
|
74
|
+
"as": "season",
|
75
|
+
"cond": {
|
76
|
+
"$and": [
|
77
|
+
{
|
78
|
+
"$eq":
|
79
|
+
["$$season.year", target_year]
|
80
|
+
}, {
|
81
|
+
"$eq": [
|
82
|
+
"$$season.season",
|
83
|
+
target_season
|
84
|
+
]
|
85
|
+
}
|
86
|
+
]
|
87
|
+
}
|
68
88
|
}
|
69
|
-
}
|
70
|
-
|
71
|
-
|
72
|
-
|
89
|
+
},
|
90
|
+
"as": "target_season_data",
|
91
|
+
"in": target_query
|
92
|
+
}
|
73
93
|
}
|
74
94
|
}
|
75
95
|
}
|
76
|
-
|
96
|
+
)
|
77
97
|
|
78
98
|
return pipeline
|
79
99
|
|
@@ -90,7 +110,8 @@ class FinanceOverviewFetcher(StatsFetcher):
|
|
90
110
|
|
91
111
|
try:
|
92
112
|
latest_time = StatsDateTime.get_latest_time(
|
93
|
-
self.ticker, self.collection
|
113
|
+
self.ticker, self.collection
|
114
|
+
)['last_update_time']
|
94
115
|
year = latest_time['seasonal_data']['latest_year']
|
95
116
|
season = latest_time['seasonal_data']['latest_season']
|
96
117
|
except Exception as e:
|
@@ -107,7 +128,7 @@ class FinanceOverviewFetcher(StatsFetcher):
|
|
107
128
|
fetched_data['seasonal_data'] = finance_dict
|
108
129
|
|
109
130
|
return fetched_data
|
110
|
-
|
131
|
+
|
111
132
|
def fill_nan_index(self, finance_dict):
|
112
133
|
for key in self.target_fields.keys():
|
113
134
|
if (key not in finance_dict.keys()):
|
@@ -121,68 +142,76 @@ class FinanceOverviewProcessor(StatsProcessor):
|
|
121
142
|
for key in finance_dict:
|
122
143
|
if ('YoY' in key):
|
123
144
|
finance_dict[key] = StatsProcessor.cal_percentage(
|
124
|
-
finance_dict[key]
|
145
|
+
finance_dict[key]
|
146
|
+
)
|
125
147
|
elif ("rate" in key or 'ratio' in key):
|
126
148
|
finance_dict[key] = StatsProcessor.cal_non_percentage(
|
127
|
-
finance_dict[key], to_str=True, postfix='%'
|
149
|
+
finance_dict[key], to_str=True, postfix='%'
|
150
|
+
)
|
128
151
|
else:
|
129
152
|
finance_dict[key] = StatsProcessor.cal_non_percentage(
|
130
|
-
finance_dict[key]
|
131
|
-
|
132
|
-
|
153
|
+
finance_dict[key]
|
154
|
+
)
|
155
|
+
|
133
156
|
@classmethod
|
134
157
|
def process_thousand_dollar(cls, finance_dict):
|
135
158
|
process_index = [
|
136
|
-
"revenue",
|
137
|
-
"
|
138
|
-
"
|
139
|
-
|
140
|
-
"
|
141
|
-
"invest_cash_flow",
|
142
|
-
"financing_cash_flow",
|
143
|
-
"fcf",
|
144
|
-
|
145
|
-
'current_assets',
|
146
|
-
'current_liabilities',
|
147
|
-
'non_current_assets',
|
148
|
-
'non_current_liabilities',
|
149
|
-
'total_assets',
|
150
|
-
"total_liabilities",
|
151
|
-
"equity"
|
159
|
+
"revenue", "gross_profit", "operating_income", "net_income",
|
160
|
+
"operating_cash_flow", "invest_cash_flow", "financing_cash_flow",
|
161
|
+
"fcf", 'current_assets', 'current_liabilities',
|
162
|
+
'non_current_assets', 'non_current_liabilities', 'total_assets',
|
163
|
+
"total_liabilities", "equity"
|
152
164
|
]
|
153
|
-
|
165
|
+
|
154
166
|
for index in process_index:
|
155
167
|
try:
|
156
|
-
finance_dict[index] = StatsProcessor.cal_non_percentage(
|
168
|
+
finance_dict[index] = StatsProcessor.cal_non_percentage(
|
169
|
+
finance_dict[index], postfix="千元"
|
170
|
+
)
|
157
171
|
except Exception as e:
|
158
172
|
finance_dict[index] = None
|
159
|
-
|
160
173
|
|
161
174
|
@classmethod
|
162
175
|
def process_all(cls, finance_dict):
|
163
176
|
methods = [
|
164
|
-
cls.cal_EBIT,
|
165
|
-
cls.
|
166
|
-
cls.
|
167
|
-
cls.
|
168
|
-
cls.
|
169
|
-
cls.
|
170
|
-
cls.
|
171
|
-
cls.
|
172
|
-
cls.
|
177
|
+
cls.cal_EBIT,
|
178
|
+
cls.cal_share_outstanding,
|
179
|
+
cls.cal_fcf,
|
180
|
+
cls.cal_interest_bearing_debt,
|
181
|
+
cls.cal_revenue_per_share,
|
182
|
+
cls.cal_gross_per_share,
|
183
|
+
cls.cal_operating_income_per_share,
|
184
|
+
cls.cal_operating_cash_flow_per_share,
|
185
|
+
cls.fcf_per_share,
|
186
|
+
cls.cal_roa,
|
187
|
+
cls.cal_roe,
|
188
|
+
cls.cal_gross_over_asset,
|
189
|
+
cls.cal_roce,
|
190
|
+
cls.cal_gross_profit_marginal,
|
191
|
+
cls.cal_operation_profit_rate,
|
192
|
+
cls.cal_operating_cash_flow_profit_rate,
|
193
|
+
cls.cal_dso,
|
194
|
+
cls.cal_account_receive_over_revenue,
|
195
|
+
cls.cal_dpo,
|
196
|
+
cls.cal_inventories_cycle_ratio,
|
197
|
+
cls.cal_dio,
|
173
198
|
cls.cal_inventories_revenue_ratio,
|
174
|
-
cls.cal_cash_of_conversion_cycle,
|
175
|
-
cls.
|
176
|
-
cls.
|
177
|
-
cls.
|
199
|
+
cls.cal_cash_of_conversion_cycle,
|
200
|
+
cls.cal_asset_turnover,
|
201
|
+
cls.cal_application_turnover,
|
202
|
+
cls.cal_current_ratio,
|
203
|
+
cls.cal_quick_ratio,
|
204
|
+
cls.cal_debt_to_equity_ratio,
|
205
|
+
cls.cal_net_debt_to_equity_ratio,
|
206
|
+
cls.cal_interest_coverage_ratio,
|
178
207
|
cls.cal_debt_to_operating_cash_flow,
|
179
|
-
cls.cal_debt_to_free_cash_flow,
|
208
|
+
cls.cal_debt_to_free_cash_flow,
|
209
|
+
cls.cal_cash_flow_ratio,
|
180
210
|
]
|
181
211
|
|
182
212
|
for method in methods:
|
183
213
|
method(finance_dict)
|
184
214
|
|
185
|
-
|
186
215
|
@classmethod
|
187
216
|
def cal_EBIT(cls, finance_dict):
|
188
217
|
"""
|
@@ -190,9 +219,10 @@ class FinanceOverviewProcessor(StatsProcessor):
|
|
190
219
|
EBIT = 營業收入 - 營業成本 - 營業費用 - 所得稅費用
|
191
220
|
"""
|
192
221
|
try:
|
193
|
-
EBIT = (
|
194
|
-
|
195
|
-
|
222
|
+
EBIT = (
|
223
|
+
finance_dict['revenue'] - finance_dict['operating_cost'] -
|
224
|
+
finance_dict['operating_expenses'] - finance_dict['tax_fee']
|
225
|
+
)
|
196
226
|
finance_dict['EBIT'] = StatsProcessor.cal_non_percentage(EBIT)
|
197
227
|
except (KeyError, ZeroDivisionError, TypeError) as e:
|
198
228
|
finance_dict['EBIT'] = None
|
@@ -205,8 +235,10 @@ class FinanceOverviewProcessor(StatsProcessor):
|
|
205
235
|
自由現金流 = 營業現金流 + 投資現金流
|
206
236
|
"""
|
207
237
|
try:
|
208
|
-
fcf = (
|
209
|
-
|
238
|
+
fcf = (
|
239
|
+
finance_dict["operating_cash_flow"] +
|
240
|
+
finance_dict["financing_cash_flow"]
|
241
|
+
)
|
210
242
|
finance_dict["fcf"] = StatsProcessor.cal_non_percentage(fcf)
|
211
243
|
except (KeyError, ZeroDivisionError, TypeError) as e:
|
212
244
|
finance_dict['fcf'] = None
|
@@ -238,8 +270,9 @@ class FinanceOverviewProcessor(StatsProcessor):
|
|
238
270
|
流通股數 = 本期淨利 ÷ 基本每股盈餘
|
239
271
|
"""
|
240
272
|
try:
|
241
|
-
finance_dict["share_outstanding"] = (
|
242
|
-
|
273
|
+
finance_dict["share_outstanding"] = (
|
274
|
+
finance_dict['net_income'] / finance_dict['eps']
|
275
|
+
)
|
243
276
|
except (KeyError, ZeroDivisionError, TypeError) as e:
|
244
277
|
finance_dict['share_outstanding'] = None
|
245
278
|
# print(f"share_outstanding failed because of {str(e)}")
|
@@ -251,11 +284,13 @@ class FinanceOverviewProcessor(StatsProcessor):
|
|
251
284
|
每股營收 = 營業收入 / 在外流通股數
|
252
285
|
"""
|
253
286
|
try:
|
254
|
-
revenue_per_share = (
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
287
|
+
revenue_per_share = (
|
288
|
+
finance_dict['revenue'] / finance_dict['share_outstanding']
|
289
|
+
)
|
290
|
+
finance_dict['revenue_per_share'
|
291
|
+
] = StatsProcessor.cal_non_percentage(
|
292
|
+
revenue_per_share, False
|
293
|
+
)
|
259
294
|
except (KeyError, ZeroDivisionError, TypeError) as e:
|
260
295
|
finance_dict['revenue_per_share'] = None
|
261
296
|
# print(f"revenue_per_share failed because of {str(e)}")
|
@@ -269,17 +304,17 @@ class FinanceOverviewProcessor(StatsProcessor):
|
|
269
304
|
if ('gross_profit' not in finance_dict.keys()):
|
270
305
|
try:
|
271
306
|
finance_dict['gross_profit'] = (
|
272
|
-
finance_dict['revenue'] -
|
273
|
-
finance_dict['operating_cost']
|
307
|
+
finance_dict['revenue'] - finance_dict['operating_cost']
|
274
308
|
)
|
275
309
|
except:
|
276
310
|
finance_dict['gross_profit'] = None
|
277
311
|
try:
|
278
|
-
gross_per_share = (
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
312
|
+
gross_per_share = (
|
313
|
+
finance_dict['gross_profit'] / finance_dict['share_outstanding']
|
314
|
+
)
|
315
|
+
finance_dict['gross_per_share'] = StatsProcessor.cal_non_percentage(
|
316
|
+
gross_per_share, False
|
317
|
+
)
|
283
318
|
|
284
319
|
except (KeyError, ZeroDivisionError, TypeError) as e:
|
285
320
|
finance_dict['gross_per_share'] = None
|
@@ -292,11 +327,14 @@ class FinanceOverviewProcessor(StatsProcessor):
|
|
292
327
|
每股營業利益= (當期營業利益)÷(當期在外流通股數)
|
293
328
|
"""
|
294
329
|
try:
|
295
|
-
operating_income_per_share = (
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
330
|
+
operating_income_per_share = (
|
331
|
+
finance_dict['operating_income'] /
|
332
|
+
finance_dict['share_outstanding']
|
333
|
+
)
|
334
|
+
finance_dict['operating_income_per_share'
|
335
|
+
] = StatsProcessor.cal_non_percentage(
|
336
|
+
operating_income_per_share
|
337
|
+
)
|
300
338
|
except (KeyError, ZeroDivisionError, TypeError) as e:
|
301
339
|
finance_dict['operating_income_per_share'] = None
|
302
340
|
# print(f"operating_income_per_share failed because of {str(e)}")
|
@@ -310,10 +348,12 @@ class FinanceOverviewProcessor(StatsProcessor):
|
|
310
348
|
try:
|
311
349
|
operating_cash_flow_per_share = (
|
312
350
|
finance_dict["operating_cash_flow"] /
|
313
|
-
finance_dict['share_outstanding']
|
314
|
-
|
315
|
-
|
316
|
-
|
351
|
+
finance_dict['share_outstanding']
|
352
|
+
)
|
353
|
+
finance_dict["operating_cash_flow_per_share"
|
354
|
+
] = StatsProcessor.cal_non_percentage(
|
355
|
+
operating_cash_flow_per_share
|
356
|
+
)
|
317
357
|
except (KeyError, ZeroDivisionError, TypeError) as e:
|
318
358
|
finance_dict['operating_cash_flow_per_share'] = None
|
319
359
|
# print(f'operating_cash_flow_per_share because of {str(e)}')
|
@@ -325,10 +365,12 @@ class FinanceOverviewProcessor(StatsProcessor):
|
|
325
365
|
每股自由現金流 = (當期自由現金流) ÷(當期在外流通股數)
|
326
366
|
"""
|
327
367
|
try:
|
328
|
-
fcf_per_share = (
|
329
|
-
|
368
|
+
fcf_per_share = (
|
369
|
+
finance_dict['fcf'] / finance_dict['share_outstanding']
|
370
|
+
)
|
330
371
|
finance_dict['fcf_per_share'] = StatsProcessor.cal_non_percentage(
|
331
|
-
fcf_per_share
|
372
|
+
fcf_per_share
|
373
|
+
)
|
332
374
|
except (KeyError, ZeroDivisionError, TypeError) as e:
|
333
375
|
finance_dict['fcf_per_share'] = None
|
334
376
|
# print(f"fcf_per_share failed because of {str(e)}")
|
@@ -344,7 +386,7 @@ class FinanceOverviewProcessor(StatsProcessor):
|
|
344
386
|
try:
|
345
387
|
roa = (
|
346
388
|
finance_dict['net_income'] + finance_dict['interest'] +
|
347
|
-
(1 * 0.1)
|
389
|
+
(1 * 0.1) # 有效稅率需要改,這裡先設0.1
|
348
390
|
) / finance_dict['inventories']
|
349
391
|
|
350
392
|
finance_dict["roa"] = StatsProcessor.cal_percentage(roa)
|
@@ -369,10 +411,12 @@ class FinanceOverviewProcessor(StatsProcessor):
|
|
369
411
|
計算營業毛利/總資產
|
370
412
|
"""
|
371
413
|
try:
|
372
|
-
gross_over_asset = (
|
373
|
-
|
414
|
+
gross_over_asset = (
|
415
|
+
finance_dict['gross_profit'] / finance_dict['total_assets']
|
416
|
+
)
|
374
417
|
finance_dict['gross_over_asset'] = StatsProcessor.cal_percentage(
|
375
|
-
gross_over_asset
|
418
|
+
gross_over_asset
|
419
|
+
)
|
376
420
|
except (KeyError, ZeroDivisionError, TypeError) as e:
|
377
421
|
finance_dict['gross_over_asset'] = None
|
378
422
|
# print(f"營業毛利/總資產 failed because of {str(e)}")
|
@@ -384,10 +428,15 @@ class FinanceOverviewProcessor(StatsProcessor):
|
|
384
428
|
ROCE = (稅前淨利+利息費用) / (資產總額-流動負債)
|
385
429
|
"""
|
386
430
|
try:
|
387
|
-
roce = (
|
388
|
-
|
389
|
-
|
390
|
-
|
431
|
+
roce = (
|
432
|
+
(
|
433
|
+
finance_dict['net_income_before_tax'] +
|
434
|
+
finance_dict['interest']
|
435
|
+
) / (
|
436
|
+
finance_dict['total_assets'] -
|
437
|
+
finance_dict['current_liabilities']
|
438
|
+
)
|
439
|
+
)
|
391
440
|
finance_dict['roce'] = StatsProcessor.cal_percentage(roce)
|
392
441
|
|
393
442
|
except (KeyError, ZeroDivisionError, TypeError) as e:
|
@@ -401,11 +450,12 @@ class FinanceOverviewProcessor(StatsProcessor):
|
|
401
450
|
營業毛利率 = 營業毛利 ÷ 營業收入
|
402
451
|
"""
|
403
452
|
try:
|
404
|
-
gross_profit_margin = (
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
453
|
+
gross_profit_margin = (
|
454
|
+
finance_dict['gross_profit'] / finance_dict['revenue']
|
455
|
+
)
|
456
|
+
finance_dict['gross_profit_margin'] = StatsProcessor.cal_percentage(
|
457
|
+
gross_profit_margin
|
458
|
+
)
|
409
459
|
except Exception as e:
|
410
460
|
finance_dict['gross_profit_margin'] = None
|
411
461
|
# print(f"gross_profit_margin failed because of {str(e)}")
|
@@ -419,10 +469,12 @@ class FinanceOverviewProcessor(StatsProcessor):
|
|
419
469
|
try:
|
420
470
|
operation_profit_rate = (
|
421
471
|
finance_dict['revenue'] - finance_dict['operating_cost'] -
|
422
|
-
finance_dict['operating_expenses']
|
423
|
-
finance_dict[
|
424
|
-
|
425
|
-
|
472
|
+
finance_dict['operating_expenses']
|
473
|
+
) / finance_dict['revenue']
|
474
|
+
finance_dict["operation_profit_rate"
|
475
|
+
] = StatsProcessor.cal_percentage(
|
476
|
+
operation_profit_rate
|
477
|
+
)
|
426
478
|
except (KeyError, ZeroDivisionError, TypeError) as e:
|
427
479
|
finance_dict["operation_profit_rate"] = None
|
428
480
|
# print(f"operation_profit failed because of {str(e)}")
|
@@ -435,10 +487,12 @@ class FinanceOverviewProcessor(StatsProcessor):
|
|
435
487
|
"""
|
436
488
|
try:
|
437
489
|
operating_cash_flow_profit_rate = (
|
438
|
-
finance_dict["operating_cash_flow"] / finance_dict["revenue"]
|
439
|
-
|
440
|
-
|
441
|
-
|
490
|
+
finance_dict["operating_cash_flow"] / finance_dict["revenue"]
|
491
|
+
)
|
492
|
+
finance_dict["operating_cash_flow_profit_rate"
|
493
|
+
] = StatsProcessor.cal_percentage(
|
494
|
+
operating_cash_flow_profit_rate
|
495
|
+
)
|
442
496
|
except (KeyError, ZeroDivisionError, TypeError) as e:
|
443
497
|
finance_dict["operating_cash_flow_profit_rate"] = None
|
444
498
|
# print(
|
@@ -460,10 +514,12 @@ class FinanceOverviewProcessor(StatsProcessor):
|
|
460
514
|
DSO = 365 × (應收帳款平均餘額 ÷ 營業收入)
|
461
515
|
"""
|
462
516
|
try:
|
463
|
-
dso = (
|
464
|
-
|
517
|
+
dso = (
|
518
|
+
365 * (finance_dict['account_pay'] / finance_dict['revenue'])
|
519
|
+
)
|
465
520
|
finance_dict['dso'] = StatsProcessor.cal_non_percentage(
|
466
|
-
dso, to_str=True, postfix="日"
|
521
|
+
dso, to_str=True, postfix="日"
|
522
|
+
)
|
467
523
|
except Exception as e:
|
468
524
|
finance_dict['dso'] = None
|
469
525
|
# print(f"Error calculating 應收帳款收現天數 because of {str(e)}")
|
@@ -475,14 +531,15 @@ class FinanceOverviewProcessor(StatsProcessor):
|
|
475
531
|
= 應收帳款平均餘額 ÷ 營業收入
|
476
532
|
"""
|
477
533
|
try:
|
478
|
-
account_receive_over_revenue = (
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
534
|
+
account_receive_over_revenue = (
|
535
|
+
finance_dict['account_receive'] / finance_dict['revenue']
|
536
|
+
)
|
537
|
+
finance_dict["account_receive_over_revenue"
|
538
|
+
] = StatsProcessor.cal_percentage(
|
539
|
+
account_receive_over_revenue
|
540
|
+
)
|
483
541
|
except Exception as e:
|
484
|
-
finance_dict[
|
485
|
-
"account_receive_over_revenue"] = None
|
542
|
+
finance_dict["account_receive_over_revenue"] = None
|
486
543
|
|
487
544
|
@classmethod
|
488
545
|
def cal_dpo(cls, finance_dict):
|
@@ -493,9 +550,11 @@ class FinanceOverviewProcessor(StatsProcessor):
|
|
493
550
|
try:
|
494
551
|
dpo = (
|
495
552
|
365 *
|
496
|
-
(finance_dict['account_pay'] / finance_dict['operating_cost'])
|
553
|
+
(finance_dict['account_pay'] / finance_dict['operating_cost'])
|
554
|
+
)
|
497
555
|
finance_dict["dpo"] = StatsProcessor.cal_non_percentage(
|
498
|
-
dpo, to_str=True, postfix="日"
|
556
|
+
dpo, to_str=True, postfix="日"
|
557
|
+
)
|
499
558
|
except (KeyError, ZeroDivisionError, TypeError) as e:
|
500
559
|
finance_dict["dpo"] = None
|
501
560
|
# print(f"應付帳款週轉天數 failed because of {str(e)}")
|
@@ -507,12 +566,14 @@ class FinanceOverviewProcessor(StatsProcessor):
|
|
507
566
|
= 銷貨成本 ÷ 存貨
|
508
567
|
"""
|
509
568
|
try:
|
510
|
-
inventories_cycle_ratio = (
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
569
|
+
inventories_cycle_ratio = (
|
570
|
+
finance_dict['operating_cost'] / finance_dict['inventories']
|
571
|
+
)
|
572
|
+
|
573
|
+
finance_dict["inventories_cycle_ratio"
|
574
|
+
] = StatsProcessor.cal_percentage(
|
575
|
+
inventories_cycle_ratio
|
576
|
+
)
|
516
577
|
except (KeyError, ZeroDivisionError, TypeError) as e:
|
517
578
|
finance_dict["inventories_cycle_ratio"] = None
|
518
579
|
# print(f"Error calculating 存貨周轉率 because of {str(e)}")
|
@@ -525,10 +586,12 @@ class FinanceOverviewProcessor(StatsProcessor):
|
|
525
586
|
MUDA MUDA MUDA !!!
|
526
587
|
"""
|
527
588
|
try:
|
528
|
-
dio = 365 * (
|
529
|
-
|
589
|
+
dio = 365 * (
|
590
|
+
finance_dict["inventories"] / finance_dict["operating_cost"]
|
591
|
+
)
|
530
592
|
finance_dict["dio"] = StatsProcessor.cal_non_percentage(
|
531
|
-
dio, to_str=True, postfix="日"
|
593
|
+
dio, to_str=True, postfix="日"
|
594
|
+
)
|
532
595
|
except (KeyError, ZeroDivisionError, TypeError) as e:
|
533
596
|
finance_dict["dio"] = None
|
534
597
|
# print(f"Error calculating 存貨週轉天數 because of {str(e)}")
|
@@ -540,12 +603,14 @@ class FinanceOverviewProcessor(StatsProcessor):
|
|
540
603
|
存貨佔營收比= 存貨 ÷ 營業收入
|
541
604
|
"""
|
542
605
|
try:
|
543
|
-
inventories_revenue_ratio = (
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
606
|
+
inventories_revenue_ratio = (
|
607
|
+
finance_dict['inventories'] / finance_dict['revenue']
|
608
|
+
)
|
609
|
+
|
610
|
+
finance_dict["inventories_revenue_ratio"
|
611
|
+
] = StatsProcessor.cal_percentage(
|
612
|
+
inventories_revenue_ratio
|
613
|
+
)
|
549
614
|
except (KeyError, ZeroDivisionError, TypeError) as e:
|
550
615
|
finance_dict["inventories_revenue_ratio"] = None
|
551
616
|
# print(f"Error calculating 存貨佔營收比率 because of {str(e)}")
|
@@ -557,12 +622,13 @@ class FinanceOverviewProcessor(StatsProcessor):
|
|
557
622
|
存貨週轉天數 + 應收帳款週轉天數 - 應付帳款週轉天數
|
558
623
|
"""
|
559
624
|
try:
|
560
|
-
cash_of_conversion_cycle = (
|
561
|
-
|
562
|
-
|
563
|
-
finance_dict[
|
564
|
-
|
565
|
-
|
625
|
+
cash_of_conversion_cycle = (
|
626
|
+
finance_dict["dio"] + finance_dict["dso"] - finance_dict['dpo']
|
627
|
+
)
|
628
|
+
finance_dict["cash_of_conversion_cycle"
|
629
|
+
] = StatsProcessor.cal_non_percentage(
|
630
|
+
cash_of_conversion_cycle, to_str=True, postfix="日"
|
631
|
+
)
|
566
632
|
except (KeyError, ZeroDivisionError, TypeError) as e:
|
567
633
|
finance_dict["cash_of_conversion_cycle"] = None
|
568
634
|
|
@@ -573,10 +639,12 @@ class FinanceOverviewProcessor(StatsProcessor):
|
|
573
639
|
營業收入 ÷ 資產總額
|
574
640
|
"""
|
575
641
|
try:
|
576
|
-
asset_turnover = (
|
577
|
-
|
642
|
+
asset_turnover = (
|
643
|
+
finance_dict["revenue"] / finance_dict["inventories"]
|
644
|
+
)
|
578
645
|
finance_dict["asset_turnover"] = StatsProcessor.cal_percentage(
|
579
|
-
asset_turnover
|
646
|
+
asset_turnover
|
647
|
+
)
|
580
648
|
except Exception as e:
|
581
649
|
finance_dict["asset_turnover"] = None
|
582
650
|
|
@@ -587,15 +655,14 @@ class FinanceOverviewProcessor(StatsProcessor):
|
|
587
655
|
營業收入 ÷ 不動產、廠房與設備平均餘額
|
588
656
|
"""
|
589
657
|
try:
|
590
|
-
applcation_turnover = (
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
658
|
+
applcation_turnover = (
|
659
|
+
finance_dict['revenue'] / finance_dict["application"]
|
660
|
+
)
|
661
|
+
finance_dict['application_turnover'
|
662
|
+
] = StatsProcessor.cal_percentage(applcation_turnover)
|
595
663
|
|
596
664
|
except Exception as e:
|
597
665
|
finance_dict['application_turnover'] = None
|
598
|
-
|
599
666
|
|
600
667
|
@classmethod
|
601
668
|
def cal_current_ratio(cls, finance_dict):
|
@@ -603,10 +670,13 @@ class FinanceOverviewProcessor(StatsProcessor):
|
|
603
670
|
計算流動比率 = 流動資產 / 流動負債
|
604
671
|
"""
|
605
672
|
try:
|
606
|
-
current_ratio = (
|
607
|
-
|
673
|
+
current_ratio = (
|
674
|
+
finance_dict['current_assets'] /
|
675
|
+
finance_dict['current_liabilities']
|
676
|
+
)
|
608
677
|
finance_dict['current_ratio'] = StatsProcessor.cal_percentage(
|
609
|
-
current_ratio
|
678
|
+
current_ratio
|
679
|
+
)
|
610
680
|
except (KeyError, ZeroDivisionError, TypeError) as e:
|
611
681
|
finance_dict['current_ratio'] = None
|
612
682
|
# print(f"Error calculating current ratio: {e}")
|
@@ -618,11 +688,12 @@ class FinanceOverviewProcessor(StatsProcessor):
|
|
618
688
|
(流動資產 - 存貨) / 流動負債
|
619
689
|
"""
|
620
690
|
try:
|
621
|
-
quick_ratio = (
|
622
|
-
|
623
|
-
|
691
|
+
quick_ratio = (
|
692
|
+
finance_dict['current_assets'] - finance_dict['inventories']
|
693
|
+
) / finance_dict['current_liabilities']
|
624
694
|
finance_dict['quick_ratio'] = StatsProcessor.cal_percentage(
|
625
|
-
quick_ratio
|
695
|
+
quick_ratio
|
696
|
+
)
|
626
697
|
except (KeyError, ZeroDivisionError, TypeError) as e:
|
627
698
|
finance_dict['quick_ratio'] = None
|
628
699
|
# print(f"Error calculating quick ratio: {e}")
|
@@ -633,11 +704,12 @@ class FinanceOverviewProcessor(StatsProcessor):
|
|
633
704
|
# 負債權益比率 = 總負債 / 股東權益
|
634
705
|
"""
|
635
706
|
try:
|
636
|
-
debt_to_equity_ratio = finance_dict[
|
637
|
-
|
638
|
-
finance_dict[
|
639
|
-
|
640
|
-
|
707
|
+
debt_to_equity_ratio = finance_dict['total_liabilities'
|
708
|
+
] / finance_dict['equity']
|
709
|
+
finance_dict['debt_to_equity_ratio'
|
710
|
+
] = StatsProcessor.cal_percentage(
|
711
|
+
debt_to_equity_ratio
|
712
|
+
)
|
641
713
|
except (KeyError, ZeroDivisionError, TypeError) as e:
|
642
714
|
finance_dict['debt_to_equity_ratio'] = None
|
643
715
|
# print(f"Error calculating debt to equity ratio: {e}")
|
@@ -652,9 +724,10 @@ class FinanceOverviewProcessor(StatsProcessor):
|
|
652
724
|
finance_dict['total_liabilities'] -
|
653
725
|
finance_dict['cash_and_cash_equivalents']
|
654
726
|
) / finance_dict['equity']
|
655
|
-
finance_dict[
|
656
|
-
|
657
|
-
|
727
|
+
finance_dict['net_debt_to_equity_ratio'
|
728
|
+
] = StatsProcessor.cal_percentage(
|
729
|
+
net_debt_to_equity_ratio
|
730
|
+
)
|
658
731
|
except (KeyError, ZeroDivisionError, TypeError) as e:
|
659
732
|
finance_dict['net_debt_to_equity_ratio'] = None
|
660
733
|
# print(f"Error calculating net debt to equity ratio: {e}")
|
@@ -667,9 +740,10 @@ class FinanceOverviewProcessor(StatsProcessor):
|
|
667
740
|
try:
|
668
741
|
interest_coverage_ratio = finance_dict['EBIT'] / finance_dict[
|
669
742
|
'interest_expense']
|
670
|
-
finance_dict[
|
671
|
-
|
672
|
-
|
743
|
+
finance_dict['interest_coverage_ratio'
|
744
|
+
] = StatsProcessor.cal_non_percentage(
|
745
|
+
interest_coverage_ratio, to_str=True, postfix="倍"
|
746
|
+
)
|
673
747
|
except (KeyError, ZeroDivisionError, TypeError) as e:
|
674
748
|
finance_dict['interest_coverage_ratio'] = None
|
675
749
|
# print(f"Error calculating interest coverage ratio: {e}")
|
@@ -682,9 +756,10 @@ class FinanceOverviewProcessor(StatsProcessor):
|
|
682
756
|
try:
|
683
757
|
debt_to_operating_cash_flow = finance_dict[
|
684
758
|
'interest_bearing_debt'] / finance_dict['operating_cash_flow']
|
685
|
-
finance_dict[
|
686
|
-
|
687
|
-
|
759
|
+
finance_dict['debt_to_operating_cash_flow'
|
760
|
+
] = StatsProcessor.cal_percentage(
|
761
|
+
debt_to_operating_cash_flow
|
762
|
+
)
|
688
763
|
except (KeyError, ZeroDivisionError, TypeError) as e:
|
689
764
|
finance_dict['debt_to_operating_cash_flow'] = None
|
690
765
|
# print(f"Error calculating debt to operating cash flow: {e}")
|
@@ -695,11 +770,12 @@ class FinanceOverviewProcessor(StatsProcessor):
|
|
695
770
|
# 有息負債 / 自由現金流
|
696
771
|
"""
|
697
772
|
try:
|
698
|
-
debt_to_free_cash_flow = finance_dict[
|
699
|
-
|
700
|
-
finance_dict[
|
701
|
-
|
702
|
-
|
773
|
+
debt_to_free_cash_flow = finance_dict['interest_bearing_debt'
|
774
|
+
] / finance_dict['fcf']
|
775
|
+
finance_dict['debt_to_free_cash_flow'
|
776
|
+
] = StatsProcessor.cal_percentage(
|
777
|
+
debt_to_free_cash_flow
|
778
|
+
)
|
703
779
|
except (KeyError, ZeroDivisionError, TypeError) as e:
|
704
780
|
finance_dict['debt_to_free_cash_flow'] = None
|
705
781
|
# print(f"Error calculating debt to free cash flow: {e}")
|
@@ -713,7 +789,250 @@ class FinanceOverviewProcessor(StatsProcessor):
|
|
713
789
|
cash_flow_ratio = finance_dict[
|
714
790
|
'operating_cash_flow'] / finance_dict['current_liabilities']
|
715
791
|
finance_dict['cash_flow_ratio'] = StatsProcessor.cal_percentage(
|
716
|
-
cash_flow_ratio
|
792
|
+
cash_flow_ratio
|
793
|
+
)
|
717
794
|
except (KeyError, ZeroDivisionError, TypeError) as e:
|
718
795
|
finance_dict['cash_flow_ratio'] = None
|
719
796
|
# print(f"Error calculating cash flow ratio: {e}")
|
797
|
+
|
798
|
+
|
799
|
+
class AgentFinanceOverviewFetcher(FinanceOverviewFetcher):
|
800
|
+
"""
|
801
|
+
目前僅適用美股
|
802
|
+
"""
|
803
|
+
|
804
|
+
def __init__(self, ticker, db_client):
|
805
|
+
self.ticker = ticker
|
806
|
+
self.timezone = pytz.timezone("Asia/Taipei")
|
807
|
+
self.tw_company_list = StatsProcessor.load_json("company_list/tw.json")
|
808
|
+
self.us_company_list = StatsProcessor.load_json("company_list/us_TradingView_list.json")
|
809
|
+
|
810
|
+
if (ticker not in self.us_company_list.keys()):
|
811
|
+
raise NoCompanyError("class \"AgentFinanceOverviewFetcher\" only supports US company now")
|
812
|
+
|
813
|
+
db_name = "company" if self.ticker in self.tw_company_list else "company_us"
|
814
|
+
self.db = db_client[db_name]
|
815
|
+
|
816
|
+
daily_collection_name_map = {
|
817
|
+
"company": "twse_daily_share_price",
|
818
|
+
'company_us': "us_technical"
|
819
|
+
}
|
820
|
+
|
821
|
+
seasonal_collection_name_map = {
|
822
|
+
'company': "twse_seasonal_report",
|
823
|
+
"company_us": "us_fundamentals"
|
824
|
+
}
|
825
|
+
|
826
|
+
self.daily_collection_name = daily_collection_name_map.get(db_name)
|
827
|
+
self.seasonal_collection_name = seasonal_collection_name_map.get(
|
828
|
+
db_name
|
829
|
+
)
|
830
|
+
|
831
|
+
assert self.daily_collection_name != "unknown", f"請確認 {ticker} 是否是 {','.join(list(daily_collection_name_map.values()))}"
|
832
|
+
assert self.seasonal_collection_name != "unknown", f"請確認 {ticker} 是否是 {','.join(list(seasonal_collection_name_map.values()))}"
|
833
|
+
self.daily_collection = db_client[db_name][self.daily_collection_name]
|
834
|
+
self.seasonal_collection = db_client[db_name][
|
835
|
+
self.seasonal_collection_name]
|
836
|
+
|
837
|
+
def query_data(self, date=None):
|
838
|
+
|
839
|
+
query_set = self._prepare_query(date)
|
840
|
+
if (date is None):
|
841
|
+
str_date = datetime.today().strftime("%Y-%m-%d")
|
842
|
+
if (isinstance(date, datetime)):
|
843
|
+
str_date = date.strftime("%Y-%m-%d")
|
844
|
+
elif (isinstance(date, str)):
|
845
|
+
str_date = date
|
846
|
+
|
847
|
+
daily_query, daily_projection, daily_sorting = query_set['daily']
|
848
|
+
seasonal_query, seasonal_projection, seasonal_sorting = query_set[
|
849
|
+
'seasonal']
|
850
|
+
|
851
|
+
daily_datas = self.daily_collection.find(daily_query, daily_projection
|
852
|
+
).sort(daily_sorting)
|
853
|
+
seasonal_datas = self.seasonal_collection.find(
|
854
|
+
seasonal_query, seasonal_projection
|
855
|
+
).sort(seasonal_sorting)
|
856
|
+
|
857
|
+
daily_data = [data for data in daily_datas]
|
858
|
+
seasonal_data = [
|
859
|
+
{
|
860
|
+
**data['balance_sheet'],
|
861
|
+
**data['income_statement'],
|
862
|
+
**data['cash_flow']
|
863
|
+
} for data in seasonal_datas
|
864
|
+
]
|
865
|
+
|
866
|
+
seasonal_df = pd.DataFrame(seasonal_data)
|
867
|
+
TTM_data = seasonal_df.mean()
|
868
|
+
|
869
|
+
return_dict = {
|
870
|
+
"date": str_date,
|
871
|
+
"ticker": self.ticker,
|
872
|
+
"company_name": self._get_company_name(),
|
873
|
+
"category": self._get_category(),
|
874
|
+
"market_open": self._is_us_market_open(),
|
875
|
+
"latest_close":
|
876
|
+
self._get_latest_close(daily_data),
|
877
|
+
"close_offset":
|
878
|
+
self._get_latest_offset(daily_data).get("value", "0.0"),
|
879
|
+
"close_offset_percentage":
|
880
|
+
self._get_latest_offset(daily_data).get("percentage", "0.0%"),
|
881
|
+
"latest_volume":
|
882
|
+
self._get_latest_volume(daily_data),
|
883
|
+
"average_volume":
|
884
|
+
self._get_latest_average_volume(daily_data, 30),
|
885
|
+
"market_capitalzation":
|
886
|
+
self._get_market_capitalization(daily_data, seasonal_data),
|
887
|
+
"P_E":
|
888
|
+
self._get_PE(daily_data, TTM_data),
|
889
|
+
"P_S":
|
890
|
+
self._get_PS(daily_data, TTM_data, seasonal_data),
|
891
|
+
}
|
892
|
+
|
893
|
+
return return_dict
|
894
|
+
|
895
|
+
def _prepare_query(self, date=None):
|
896
|
+
if (date is None):
|
897
|
+
date = datetime.today()
|
898
|
+
elif (isinstance(date, str)):
|
899
|
+
date = datetime.strptime(date, "%Y-%m-%d")
|
900
|
+
|
901
|
+
daily_query = {"ticker": self.ticker, "date": {"$lte": date}}
|
902
|
+
|
903
|
+
seasons = []
|
904
|
+
year = date.year
|
905
|
+
season = (date.month - 1) // 3 + 1
|
906
|
+
|
907
|
+
for _ in range(4):
|
908
|
+
seasons.append({"year": year, "season": season})
|
909
|
+
year = year if season != 1 else year - 1
|
910
|
+
season = season - 1 if season != 1 else 4
|
911
|
+
|
912
|
+
seasonal_query = {"ticker": self.ticker, "$or": seasons}
|
913
|
+
|
914
|
+
daily_projection = {"_id": 0}
|
915
|
+
seasonal_projection = {"_id": 0}
|
916
|
+
|
917
|
+
daily_sorting = {"date": ASCENDING}
|
918
|
+
seasonal_sorting = {"year": ASCENDING, "season": ASCENDING}
|
919
|
+
|
920
|
+
query_set = {
|
921
|
+
"daily": [daily_query, daily_projection, daily_sorting],
|
922
|
+
"seasonal": [seasonal_query, seasonal_projection, seasonal_sorting]
|
923
|
+
}
|
924
|
+
|
925
|
+
return query_set
|
926
|
+
|
927
|
+
def _get_latest_close(self, daily_data):
|
928
|
+
latest_close = daily_data[-1]['close']
|
929
|
+
latest_close = str(round(latest_close, 2))
|
930
|
+
return latest_close
|
931
|
+
|
932
|
+
def _get_latest_offset(self, daily_data):
|
933
|
+
latest_close = daily_data[-1]['close']
|
934
|
+
previous = daily_data[-2]['close']
|
935
|
+
|
936
|
+
offset = latest_close - previous
|
937
|
+
percentage = offset / previous
|
938
|
+
|
939
|
+
offset = str(np.round(offset, 2))
|
940
|
+
|
941
|
+
return {'value': offset, "percentage": f"{percentage:.2f}%"}
|
942
|
+
|
943
|
+
def _get_latest_volume(self, daily_data):
|
944
|
+
try:
|
945
|
+
volume = round(daily_data[-1]['volume'], 2)
|
946
|
+
return str(volume)
|
947
|
+
except Exception as e:
|
948
|
+
return "Error"
|
949
|
+
|
950
|
+
def _get_latest_average_volume(self, daily_data, length=30):
|
951
|
+
try:
|
952
|
+
daily_data = pd.DataFrame(daily_data).tail(length)
|
953
|
+
|
954
|
+
value = daily_data['volume'].mean().item()
|
955
|
+
value = round(value, 2)
|
956
|
+
|
957
|
+
return str(value)
|
958
|
+
|
959
|
+
except Exception as e:
|
960
|
+
return "Error"
|
961
|
+
|
962
|
+
def _get_market_capitalization(self, daily_data, seasonal_data):
|
963
|
+
try:
|
964
|
+
latest_close = float(self._get_latest_close(daily_data))
|
965
|
+
latest_common_share = seasonal_data[-1][
|
966
|
+
'Common Stock']
|
967
|
+
|
968
|
+
percentage = round((latest_close * latest_common_share), 2)
|
969
|
+
|
970
|
+
return str(percentage)
|
971
|
+
|
972
|
+
except Exception as e:
|
973
|
+
return "Error"
|
974
|
+
|
975
|
+
def _get_PE(self, daily_data, TTM_data):
|
976
|
+
try:
|
977
|
+
latest_close = float(self._get_latest_close(daily_data))
|
978
|
+
TTM_EPS = TTM_data['Diluted EPS']
|
979
|
+
|
980
|
+
value = round((latest_close / TTM_EPS), 2)
|
981
|
+
|
982
|
+
return str(value)
|
983
|
+
|
984
|
+
except Exception as e:
|
985
|
+
return "Error"
|
986
|
+
|
987
|
+
def _get_PS(self, daily_data, TTM_data, seasonal_data):
|
988
|
+
try:
|
989
|
+
market_capitalzation = self._get_market_capitalization(daily_data, seasonal_data)
|
990
|
+
market_capitalzation = float(market_capitalzation)
|
991
|
+
TTM_revenue = TTM_data['Total Revenue']
|
992
|
+
|
993
|
+
value = round((market_capitalzation / TTM_revenue), 2)
|
994
|
+
|
995
|
+
return str(value)
|
996
|
+
except Exception as e:
|
997
|
+
return "Error"
|
998
|
+
|
999
|
+
def _is_us_market_open(self):
|
1000
|
+
|
1001
|
+
taiwan_dt = datetime.now(pytz.timezone('Asia/Taipei'))
|
1002
|
+
|
1003
|
+
# 轉成美東時間(會自動處理夏令時間)
|
1004
|
+
eastern = pytz.timezone('US/Eastern')
|
1005
|
+
us_dt = taiwan_dt.astimezone(eastern)
|
1006
|
+
|
1007
|
+
# 假日判斷
|
1008
|
+
us_holidays = holidays.NYSE(years=us_dt.year)
|
1009
|
+
if us_dt.date() in us_holidays:
|
1010
|
+
return False
|
1011
|
+
|
1012
|
+
# 週末
|
1013
|
+
if us_dt.weekday() >= 5: # 5 = Saturday, 6 = Sunday
|
1014
|
+
return False
|
1015
|
+
|
1016
|
+
# 判斷是否在開盤時間
|
1017
|
+
market_open = us_dt.replace(hour=9, minute=30, second=0, microsecond=0)
|
1018
|
+
market_close = us_dt.replace(hour=16, minute=0, second=0, microsecond=0)
|
1019
|
+
|
1020
|
+
return market_open <= us_dt <= market_close
|
1021
|
+
|
1022
|
+
def _get_category(self):
|
1023
|
+
category = self.us_company_list.get(self.ticker,{}).get('en_industry')
|
1024
|
+
|
1025
|
+
if (category):
|
1026
|
+
return category
|
1027
|
+
|
1028
|
+
else:
|
1029
|
+
raise ValueError("No company")
|
1030
|
+
|
1031
|
+
def _get_company_name(self):
|
1032
|
+
company_name = self.us_company_list.get(self.ticker,{}).get('name')
|
1033
|
+
|
1034
|
+
if (company_name):
|
1035
|
+
return company_name
|
1036
|
+
|
1037
|
+
else:
|
1038
|
+
raise ValueError("No company")
|