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
@@ -23,70 +23,84 @@ class MarginTradingFetcher(StatsFetcher):
23
23
  def prepare_query(self, start_date, end_date):
24
24
  pipeline = super().prepare_query()
25
25
 
26
- pipeline.append({
27
- "$project": {
28
- "_id": 0,
29
- "ticker": 1,
30
- "company_name": 1,
31
- "daily_data": {
32
- "$map": {
33
- "input": {
34
- "$filter": {
35
- "input": "$daily_data",
36
- "as": "daliy",
37
- "cond": {
38
- "$and": [{
39
- "$gte": ["$$daliy.date", start_date]
40
- }, {
41
- "$lte": ["$$daliy.date", end_date]
42
- }]
26
+ pipeline.append(
27
+ {
28
+ "$project": {
29
+ "_id": 0,
30
+ "ticker": 1,
31
+ "company_name": 1,
32
+ "daily_data": {
33
+ "$map": {
34
+ "input": {
35
+ "$filter": {
36
+ "input": "$daily_data",
37
+ "as": "daliy",
38
+ "cond": {
39
+ "$and": [
40
+ {
41
+ "$gte":
42
+ ["$$daliy.date", start_date]
43
+ }, {
44
+ "$lte":
45
+ ["$$daliy.date", end_date]
46
+ }
47
+ ]
48
+ }
43
49
  }
44
- }
45
- },
46
- "as": "target_daliy_data",
47
- "in": "$$target_daliy_data"
48
- }
49
- },
50
- "margin_trading": {
51
- "$map": {
52
- "input": {
53
- "$filter": {
54
- "input": "$margin_trading",
55
- "as": "margin",
56
- "cond": {
57
- "$and": [{
58
- "$gte": ["$$margin.date", start_date]
59
- }, {
60
- "$lte": ["$$margin.date", end_date]
61
- }]
50
+ },
51
+ "as": "target_daliy_data",
52
+ "in": "$$target_daliy_data"
53
+ }
54
+ },
55
+ "margin_trading": {
56
+ "$map": {
57
+ "input": {
58
+ "$filter": {
59
+ "input": "$margin_trading",
60
+ "as": "margin",
61
+ "cond": {
62
+ "$and": [
63
+ {
64
+ "$gte":
65
+ ["$$margin.date", start_date]
66
+ }, {
67
+ "$lte":
68
+ ["$$margin.date", end_date]
69
+ }
70
+ ]
71
+ }
62
72
  }
63
- }
64
- },
65
- "as": "target_margin_data",
66
- "in": "$$target_margin_data"
67
- }
68
- },
69
- "security_lending": {
70
- "$map": {
71
- "input": {
72
- "$filter": {
73
- "input": "$security_lending",
74
- "as": "lending",
75
- "cond": {
76
- "$and": [{
77
- "$gte": ["$$lending.date", start_date]
78
- }, {
79
- "$lte": ["$$lending.date", end_date]
80
- }]
73
+ },
74
+ "as": "target_margin_data",
75
+ "in": "$$target_margin_data"
76
+ }
77
+ },
78
+ "security_lending": {
79
+ "$map": {
80
+ "input": {
81
+ "$filter": {
82
+ "input": "$security_lending",
83
+ "as": "lending",
84
+ "cond": {
85
+ "$and": [
86
+ {
87
+ "$gte":
88
+ ["$$lending.date", start_date]
89
+ }, {
90
+ "$lte":
91
+ ["$$lending.date", end_date]
92
+ }
93
+ ]
94
+ }
81
95
  }
82
- }
83
- },
84
- "as": "target_lending_data",
85
- "in": "$$target_lending_data"
96
+ },
97
+ "as": "target_lending_data",
98
+ "in": "$$target_lending_data"
99
+ }
86
100
  }
87
101
  }
88
102
  }
89
- })
103
+ )
90
104
 
91
105
  return pipeline
92
106
 
@@ -100,39 +114,39 @@ class MarginTradingFetcher(StatsFetcher):
100
114
  def query_data(self):
101
115
  try:
102
116
  latest_time = StatsDateTime.get_latest_time(
103
- self.ticker, self.collection)['last_update_time']
117
+ self.ticker, self.collection
118
+ )['last_update_time']
104
119
  latest_date = latest_time['margin_trading']['latest_date']
105
- end_date = latest_date.replace(hour=0,
106
- minute=0,
107
- second=0,
108
- microsecond=0)
109
- except Exception as e:
110
- print(
111
- f"No updated time for institution_trading in {self.ticker}, use current time instead"
120
+ end_date = latest_date.replace(
121
+ hour=0, minute=0, second=0, microsecond=0
112
122
  )
123
+ except Exception as e:
113
124
  end_date = datetime.now(self.timezone)
114
- end_date = end_date.replace(hour=0,
115
- minute=0,
116
- second=0,
117
- microsecond=0)
125
+ end_date = end_date.replace(
126
+ hour=0, minute=0, second=0, microsecond=0
127
+ )
118
128
 
119
- if (end_date.hour < 22): # 拿不到今天的資料
129
+ if (end_date.hour < 22): # 拿不到今天的資料
120
130
  end_date = end_date - timedelta(days=1)
121
131
 
122
132
  start_date = end_date - timedelta(days=365)
123
133
 
124
134
  fetched_data = self.collect_data(start_date, end_date)
125
135
 
126
- fetched_data['daily_data'] = sorted(fetched_data['daily_data'],
127
- key=lambda x: x['date'],
128
- reverse=True)
129
- fetched_data['margin_trading'] = sorted(fetched_data['margin_trading'],
130
- key=lambda x: x['date'],
131
- reverse=True)
136
+ fetched_data['daily_data'] = sorted(
137
+ fetched_data['daily_data'], key=lambda x: x['date'], reverse=True
138
+ )
139
+ fetched_data['margin_trading'] = sorted(
140
+ fetched_data['margin_trading'],
141
+ key=lambda x: x['date'],
142
+ reverse=True
143
+ ) if (fetched_data['margin_trading']) else []
144
+
132
145
  fetched_data['security_lending'] = sorted(
133
146
  fetched_data['security_lending'],
134
147
  key=lambda x: x['date'],
135
- reverse=True)
148
+ reverse=True
149
+ )if (fetched_data['security_lending']) else []
136
150
 
137
151
  table_dict = self.process_data(fetched_data)
138
152
 
@@ -149,19 +163,18 @@ class MarginTradingFetcher(StatsFetcher):
149
163
  price_dict = {
150
164
  "open": latest_data['open'],
151
165
  'close': latest_data['close'],
152
- 'range': f"{latest_data['low']} - {latest_data['high']}",
153
- 'volume': latest_data['volume'] / 1000,
166
+ 'range': f"{latest_data['low']:.2f} - {latest_data['high']:.2f}",
167
+ 'volume': round(float(latest_data['volume']) / 1000, 2),
154
168
  'last_open': yesterday_data['open'],
155
169
  'last_close': yesterday_data['close'],
156
- 'last_range':
157
- f"{yesterday_data['low']} - {yesterday_data['high']}",
158
- 'last_volume': yesterday_data['volume'] / 1000
170
+ 'last_range': f"{yesterday_data['low']:.2f} - {yesterday_data['high']:.2f}",
171
+ 'last_volume': round(float(yesterday_data['volume']) / 1000, 2)
159
172
  }
160
173
  annual_lows = [data['low'] for data in daily_datas]
161
174
  annual_highs = [data['high'] for data in daily_datas]
162
175
  lowest = np.min(annual_lows).item()
163
176
  highest = np.max(annual_highs).item()
164
- price_dict['52weeks_range'] = f"{lowest} - {highest}"
177
+ price_dict['52weeks_range'] = f"{lowest:.2f} - {highest:.2f}"
165
178
 
166
179
  return_dict['price'] = price_dict
167
180
 
@@ -169,6 +182,18 @@ class MarginTradingFetcher(StatsFetcher):
169
182
  margin_trading = fetched_data['margin_trading']
170
183
  security_lending = fetched_data['security_lending']
171
184
 
185
+ return_dict['margin_trading'] = pd.DataFrame()
186
+ return_dict['stock_lending'] = pd.DataFrame()
187
+ return_dict['latest_trading'] = {
188
+ 'date': date.today(),
189
+ "margin_trading": pd.DataFrame(),
190
+ "stock_lending": pd.DataFrame()
191
+ }
192
+ return_dict['annual_margin'] = pd.DataFrame()
193
+ return_dict['security_offset'] = 0.0
194
+ if (not margin_trading):
195
+ return return_dict
196
+
172
197
  latest_margin_date = margin_trading[0]['date']
173
198
  latest_lending_date = security_lending[0]['date']
174
199
  ## 融資融券
@@ -176,7 +201,8 @@ class MarginTradingFetcher(StatsFetcher):
176
201
  for trading in margin_trading:
177
202
  trading['financing']['現償'] = trading['financing'].pop('現金償還')
178
203
  trading['short_selling']['現償'] = trading['short_selling'].pop(
179
- '現券償還')
204
+ '現券償還'
205
+ )
180
206
  ### 轉換
181
207
  latest_margin_trading = margin_trading[0]
182
208
  latest_margin_trading_df = {
@@ -185,7 +211,8 @@ class MarginTradingFetcher(StatsFetcher):
185
211
  if (isinstance(sub_dict, dict))
186
212
  }
187
213
  latest_margin_trading_df = pd.DataFrame.from_dict(
188
- latest_margin_trading_df)
214
+ latest_margin_trading_df
215
+ )
189
216
 
190
217
  ## 借券表格
191
218
  latest_stock_lending = security_lending[0]['stock_lending']
@@ -195,9 +222,9 @@ class MarginTradingFetcher(StatsFetcher):
195
222
  for type_name, value in latest_stock_lending.items()
196
223
  }
197
224
  latest_stock_lending.pop("前日餘額")
198
- latest_stock_lending_df = pd.DataFrame.from_dict(latest_stock_lending,
199
- orient="index",
200
- columns=['stock_lending'])
225
+ latest_stock_lending_df = pd.DataFrame.from_dict(
226
+ latest_stock_lending, orient="index", columns=['stock_lending']
227
+ )
201
228
 
202
229
  latest_dict = {
203
230
  "date": latest_margin_date,
@@ -262,14 +289,14 @@ class MarginTradingFetcher(StatsFetcher):
262
289
 
263
290
  annual_dict = {
264
291
  date: {
265
- "close": close_prices[date],
266
- "volume": volumes[date],
292
+ "close": close_prices.get(date, 0.0),
293
+ "volume": volumes.get(date, 0.0),
267
294
  **financings[date],
268
295
  **short_sellings[date],
269
296
  **stock_lendings[date],
270
- "資券互抵":security_offsets[date]
297
+ "資券互抵": security_offsets[date]
271
298
  }
272
- for date in annual_dates
299
+ for date in financings.keys()
273
300
  }
274
301
 
275
302
  annual_table = pd.DataFrame.from_dict(annual_dict)
@@ -4,6 +4,8 @@ import pandas as pd
4
4
  from ..utils import StatsDateTime, StatsProcessor, YoY_Calculator
5
5
  import importlib.resources as pkg_resources
6
6
  import yaml
7
+ import traceback
8
+ import logging
7
9
 
8
10
 
9
11
  class MonthRevenueFetcher(StatsFetcher):
@@ -11,151 +13,183 @@ class MonthRevenueFetcher(StatsFetcher):
11
13
  iFa.ai: 財務分析 -> 每月營收
12
14
  """
13
15
 
14
- def __init__(self, ticker, db_client):
16
+ def __init__(self, ticker, db_client, logger = None):
15
17
  super().__init__(ticker, db_client)
18
+ self.logger = logger or logging.getLogger(__name__)
16
19
 
17
- def prepare_query(self, target_year, target_month):
20
+ def _prepare_query(self, target_year, target_month):
18
21
  pipeline = super().prepare_query()
19
22
 
20
- pipeline.append({
21
- "$project": {
22
- "_id": 0,
23
- "ticker": 1,
24
- "company_name": 1,
25
- "monthly_data": {
26
- "$sortArray": {
27
- "input": "$monthly_data",
28
- "sortBy": {
29
- "year": -1,
30
- "month": -1
23
+ pipeline.append(
24
+ {
25
+ "$project": {
26
+ "_id": 0,
27
+ "ticker": 1,
28
+ "company_name": 1,
29
+ "monthly_data": {
30
+ "$sortArray": {
31
+ "input": "$monthly_data",
32
+ "sortBy": {
33
+ "year": -1,
34
+ "month": -1
35
+ }
31
36
  }
32
- }
33
- },
37
+ },
38
+ }
34
39
  }
35
- })
40
+ )
36
41
 
37
42
  return pipeline
38
43
 
39
44
  def collect_data(self, target_year, target_month):
40
- pipeline = self.prepare_query(target_year, target_month)
41
-
45
+ pipeline = self._prepare_query(target_year, target_month)
42
46
  fetched_data = self.collection.aggregate(pipeline)
47
+ fetched_data = fetched_data.to_list()
43
48
 
44
- fetched_data = list(fetched_data)
45
-
46
- return fetched_data[-1]
49
+ return fetched_data
47
50
 
48
51
  def query_data(self):
49
- try:
50
- latest_time = StatsDateTime.get_latest_time(
51
- self.ticker, self.collection)['last_update_time']
52
- target_year = latest_time['monthly_data']['latest_year']
53
- target_month = latest_time['monthly_data']['latest_month']
54
- except Exception as e:
55
- today = StatsDateTime.get_today()
56
- target_month = today.month
57
- target_year = today.year
52
+ target_year, target_month = self._get_target_year_and_month()
58
53
 
59
54
  # Query data
60
55
  fetched_data = self.collect_data(target_year, target_month)
61
56
 
62
- return self.process_data(fetched_data)
57
+ try:
58
+ return self._process_data(fetched_data[-1])
59
+ except Exception:
60
+ recent_date = []
61
+ for _ in range(12):
62
+ recent_date.append(f"{target_year}/{target_month}")
63
+ target_year, target_month = (
64
+ target_year - 1, 12
65
+ ) if target_month == 1 else (target_year, target_month - 1)
66
+
67
+ # logging.warning(f"{self.ticker}: No monthly revenue data in TWSE mongoDB", exc_info=True)
68
+ return self._get_empty_structure(target_year, target_month)
69
+
70
+ def _process_data(self, fetched_data):
63
71
 
64
- def process_data(self, fetched_data):
72
+ monthly_data = fetched_data.get('monthly_data', [])
73
+ if not monthly_data:
74
+ raise ValueError("monthly_data is empty or missing")
65
75
 
66
- monthly_data = fetched_data['monthly_data']
67
76
  for data in monthly_data:
68
77
  for key, value in data.items():
69
- if ("YoY" in key):
78
+ if "YoY" in key:
70
79
  data[key] = StatsProcessor.cal_percentage(value)
71
- elif ("ratio" in key or 'percentage' in key):
72
- data[key] = StatsProcessor.cal_non_percentage(value,
73
- to_str=True,
74
- postfix="%")
75
- elif (key not in ('year', 'month')):
76
- data[key] = StatsProcessor.cal_non_percentage(value,
77
- postfix="千元")
78
- target_month = monthly_data[0]['month']
79
- monthly_df = pd.DataFrame(monthly_data)
80
+ elif "ratio" in key or "percentage" in key:
81
+ data[key] = StatsProcessor.cal_non_percentage(value, to_str=True, postfix="%")
82
+ elif key not in ('year', 'month'):
83
+ data[key] = StatsProcessor.cal_non_percentage(value, postfix="千元")
80
84
 
85
+ monthly_df = pd.DataFrame(monthly_data)
86
+ target_month = monthly_data[0]['month']
81
87
  target_month_df = monthly_df[monthly_df['month'] == target_month]
82
88
  annual_month_df = monthly_df[monthly_df['month'] == 12]
83
- month_revenue_df = monthly_df.pivot(index='month',
84
- columns='year',
85
- values='revenue')
86
-
87
- grand_total_df = target_month_df.pivot(index='month',
88
- columns='year',
89
- values='grand_total')
90
89
 
91
- annual_total_df = annual_month_df.pivot(index='month',
92
- columns='year',
93
- values='grand_total')
94
-
95
- grand_total_df.rename(index={target_month: f"grand_total"},
96
- inplace=True)
90
+
91
+ month_revenue_df = monthly_df.pivot(
92
+ index='month', columns='year', values='revenue'
93
+ )
97
94
  month_revenue_df = month_revenue_df.sort_index(ascending=False)
98
- month_revenue_df = pd.concat([grand_total_df, month_revenue_df],
99
- axis=0)
100
-
101
- fetched_data['month_revenue'] = month_revenue_df[sorted(
102
- month_revenue_df.columns, reverse=True)]
103
- # 歷年月營收
104
- fetched_data[
105
- 'this_month_revenue_over_years'] = target_month_df.set_index(
106
- "year")[[
107
- "revenue", "revenue_increment_ratio", "YoY_1", "YoY_3",
108
- "YoY_5", "YoY_10"
109
- ]].T
110
- # 歷年營收成長量
111
- fetched_data['grand_total_over_years'] = target_month_df.set_index(
112
- "year")[[
113
- "grand_total", "grand_total_increment_ratio",
114
- "grand_total_YoY_1", "grand_total_YoY_3", "grand_total_YoY_5",
115
- "grand_total_YoY_10"
116
- ]].T
117
95
 
118
- fetched_data.pop("monthly_data")
96
+ grand_total_df = target_month_df.pivot(
97
+ index='month', columns='year', values='grand_total'
98
+ )
99
+ grand_total_df.rename(
100
+ index={target_month: f"grand_total"}, inplace=True
101
+ )
102
+ month_revenue_df = pd.concat([grand_total_df, month_revenue_df], axis=0)
119
103
 
120
- fetched_data['recent_month_revenue'] = self.get_recent_revenue_grwoth(
121
- monthly_data, grand_total_dict=annual_total_df.to_dict(), interval = 12
104
+ annual_total_df = annual_month_df.pivot(
105
+ index='month', columns='year', values='grand_total'
106
+ )
107
+
108
+ fetched_data.update(
109
+ {
110
+ "month_revenue": month_revenue_df[sorted(month_revenue_df.columns, reverse=True)],
111
+ "this_month_revenue_over_years": target_month_df.set_index("year")[[
112
+ "revenue", "revenue_increment_ratio", "YoY_1", "YoY_3", "YoY_5", "YoY_10"
113
+ ]].T,
114
+ "grand_total_over_years": target_month_df.set_index("year")[[
115
+ "grand_total", "grand_total_increment_ratio", "grand_total_YoY_1", "grand_total_YoY_3", "grand_total_YoY_5", "grand_total_YoY_10"
116
+ ]].T,
117
+ "recent_month_revenue": self._get_recent_growth(monthly_data, grand_total_dict=annual_total_df.to_dict(), interval=12)
118
+ }
122
119
  )
123
120
 
121
+ fetched_data.pop("monthly_data")
124
122
  return fetched_data
125
123
 
126
- def get_recent_revenue_grwoth(self, monthly_data, grand_total_dict, interval: int = 12):
127
- recent_month_data = monthly_data[:interval + 1]
124
+ def _get_recent_growth(self, monthly_data, grand_total_dict, interval=12):
125
+ last_month_data = monthly_data[1:interval + 1] + [{}] * max(0, interval - len(monthly_data) + 1)
128
126
 
129
127
  MoMs = [
130
- YoY_Calculator.cal_growth(this_value['revenue'], last_value['revenue'], delta = 1)
131
- for this_value, last_value in zip(
132
- recent_month_data[:12], recent_month_data[1:13]
133
- )
128
+ YoY_Calculator.cal_growth(this.get('revenue'), last.get('revenue'), delta = 1)
129
+ for this, last in zip(monthly_data[:interval], last_month_data[:interval])
134
130
  ]
135
131
 
136
- recent_month_data = {
137
- "date" : [f"{data['year']}/{data['month']}" for data in recent_month_data[:interval]],
138
- "revenue" : [data['revenue'] for data in recent_month_data[:interval]],
139
- "MoM" : [f"{(data * 100):.2f}%" for data in MoMs],
140
- "YoY" : [f"{data['revenue_increment_ratio']}" for data in recent_month_data[:interval]],
141
- "total_YoY": [f"{data['grand_total_increment_ratio']}" for data in recent_month_data[:interval]],
142
- }
143
-
144
- # accum_YoY
145
- # accum_YoY 為 Davis提出的定義
146
- # 2024/6的累計YoY(accum_YoY) 為 2024累計到6月為止的總營收/2023年度總營收
147
- accum_YoYs = []
148
- for data in monthly_data[:interval]:
132
+ def safe_accum_yoy(data):
149
133
  try:
150
134
  year = data['year'] - 1
151
135
  total = grand_total_dict[year][12]
152
- accum_YoY = round(((data['grand_total'] - total) / total) * 100, 2)
153
- accum_YoYs.append(f"{accum_YoY}%")
154
- except Exception as e:
155
- accum_YoYs.append(None)
156
-
157
- recent_month_data['accum_YoY'] = accum_YoYs
136
+ grand_total = data.get('grand_total')
137
+ return f"{round(((grand_total - total) / total) * 100, 2)}%"
138
+ except Exception:
139
+ self.logger.debug(f"accum_YoY calc failed for year={data.get('year')} / ticker={self.ticker}", exc_info=True)
140
+ return None
158
141
 
159
- recent_month_df = pd.DataFrame(recent_month_data).set_index('date').T
142
+ recent_month_data = {
143
+ "date": [f"{d.get('year', 0)}/{d.get('month', 0)}" for d in monthly_data[:interval]],
144
+ "revenue": [d.get('revenue') for d in monthly_data[:interval]],
145
+ "MoM": [f"{(m * 100):.2f}%" if isinstance(m, float) else None for m in MoMs],
146
+ "YoY": [d.get('revenue_increment_ratio') for d in monthly_data[:interval]],
147
+ "total_YoY": [d.get('grand_total_increment_ratio') for d in monthly_data[:interval]],
148
+ # accum_YoY
149
+ # accum_YoY 為 Davis提出的定義
150
+ # 2024/6的累計YoY(accum_YoY) 為 2024累計到6月為止的總營收/2023年度總營收
151
+ "accum_YoY": [safe_accum_yoy(d) for d in monthly_data[:interval]]
152
+ }
160
153
 
161
- return recent_month_df
154
+ df = pd.DataFrame(recent_month_data)
155
+ return df[df['date'] != "0/0"].set_index('date').T
156
+
157
+
158
+ def _get_empty_structure(self, target_year, target_month):
159
+ """
160
+ Exception 發生時回傳
161
+ """
162
+ recent_date = [f"{target_year}/{target_month}"]
163
+ for _ in range(11):
164
+ target_year, target_month = (target_year - 1, 12) if target_month == 1 else (target_year, target_month - 1)
165
+ recent_date.append(f"{target_year}/{target_month}")
166
+
167
+ def empty_df(index, columns):
168
+ return pd.DataFrame(index=index, columns=columns)
169
+
170
+ return {
171
+ "month_revenue": empty_df(
172
+ index=pd.Index(['grand_total', 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1], dtype='object', name='month'),
173
+ columns=pd.Index([f"{target_year - i}" for i in range(10)], dtype=object, name='year')
174
+ ),
175
+ "this_month_revenue_over_years": empty_df(
176
+ index=pd.Index(['revenue', 'revenue_increment_ratio', 'YoY_1', 'YoY_3', 'YoY_5', 'YoY_10'], dtype='object'),
177
+ columns=pd.Index([f"{target_year - i}" for i in range(10)], dtype='int64', name='year')
178
+ ),
179
+ "grand_total_over_years": empty_df(
180
+ index=pd.Index(['grand_total', 'grand_total_increment_ratio', 'grand_total_YoY_1', 'grand_total_YoY_3', 'grand_total_YoY_5', 'grand_total_YoY_10'], dtype='object'),
181
+ columns=pd.Index([f"{target_year - i}" for i in range(10)], dtype='int64', name='year')
182
+ ),
183
+ "recent_month_revenue": empty_df(
184
+ index=pd.Index(['revenue', 'MoM', 'YoY', 'total_YoY', 'accum_YoY'], dtype='object'),
185
+ columns=pd.Index([], dtype = 'object', name = 'date')
186
+ )
187
+ }
188
+
189
+ def _get_target_year_and_month(self):
190
+ try:
191
+ latest_time = StatsDateTime.get_latest_time(self.ticker, self.collection)['last_update_time']
192
+ return latest_time['monthly_data']['latest_year'], latest_time['monthly_data']['latest_month']
193
+ except Exception:
194
+ today = StatsDateTime.get_today()
195
+ return today.year, today.month