def _validate(df: pd.DataFrame) -> None: """Проверка заголовков таблицы.""" months, _ = df.shape first_year = df.columns[0] first_month = df.index[0] if months != NUM_OF_MONTH: raise outer.DataError("Таблица должна содержать 12 строк с месяцами") if first_year != FIRST_YEAR: raise outer.DataError("Первый год должен быть 1991") if first_month != FIRST_MONTH: raise outer.DataError("Первый месяц должен быть январь")
async def get(self, table_name: outer.TableName) -> pd.DataFrame: """Получение дивидендов для заданного тикера.""" name = self._log_and_validate_group(table_name, outer.SMART_LAB) if name != outer.SMART_LAB: raise outer.DataError( f"Некорректное имя таблицы для обновления {table_name}") cols_desc = get_col_desc() df = await parser.get_df_from_url(URL, TABLE_INDEX, cols_desc) if FOOTER not in df.index[-1]: raise outer.DataError(f"Некорректная html-таблица {table_name}") return df.dropna()
def _is_common(ticker: str) -> bool: """Определяет является ли акция обыкновенной.""" if len(ticker) == COMMON_TICKER_LENGTH: return True elif len(ticker) == COMMON_TICKER_LENGTH + 1: if ticker[COMMON_TICKER_LENGTH] == PREFERRED_TICKER_ENDING: return False raise outer.DataError(f"Некорректный тикер {ticker}")
def _get_table_from_html(html: str, table_num: int) -> str: """Выбирает таблицу по номеру из html-страницы.""" soup = bs4.BeautifulSoup(html, "lxml") try: table = soup.find_all("table")[table_num] except IndexError: raise outer.DataError(f"На странице нет таблицы {table_num}") return f"<html>{table}</html>"
async def _get_html(url: str) -> str: """Загружает html-код страницы.""" session = resources.get_aiohttp_session() async with session.get(url) as respond: try: respond.raise_for_status() except aiohttp.ClientResponseError: raise outer.DataError(f"Данные {url} не загружены") return await respond.text()
async def get(self, table_name: outer.TableName) -> pd.DataFrame: """Получение данных по инфляции.""" name = self._log_and_validate_group(table_name, outer.CPI) if name != outer.CPI: raise outer.DataError( f"Некорректное имя таблицы для обновления {table_name}") df = await _load_xlsx() _validate(df) return _clean_up(df)
def _validate_header(columns: pd.Index, cols_desc: Descriptions) -> None: """Проверяет, что заголовки соответствуют описанию.""" for desc in cols_desc: header = columns[desc.num] if not isinstance(header, tuple): header = [header] raw_name = desc.raw_name if all(part in name for part, name in zip(raw_name, header)): continue raise outer.DataError( f"Неверный заголовок: {desc.raw_name} не входит в {header}")
def _log_and_validate_group( self, table_name: outer.TableName, loader_group: outer.GroupName, ) -> str: """Проверка корректности таблицы и логирование начала загрузки.""" group, name = table_name if group != loader_group: raise outer.DataError( f"Некорректное имя таблицы для обновления {table_name}") self._logger.info(f"Загрузка {table_name}") return name
async def handle_event( self, queue: EventsQueue, table: Optional[model.Table], ) -> None: """Узнает окончание рабочего дня и запрашивает обновление.""" if table is None: raise outer.DataError("Нужна таблица") end_of_trading_day = services.trading_day_potential_end() await table.update(end_of_trading_day) end_of_trading_day = services.trading_day_real_end(table.df) await queue.put(UpdateTable(self._table_name, end_of_trading_day))
def _validate_data(validate: bool, df_old: pd.DataFrame, df_new: pd.DataFrame) -> None: """Проверка совпадения данных для старых значений индекса.""" if not validate: return if df_old is None: return df_new_val = df_new.reindex(df_old.index) try: pd.testing.assert_frame_equal(df_new_val, df_old) except AssertionError: raise outer.DataError("Новые данные не соответствуют старым")
async def handle_event( self, queue: EventsQueue, table: Optional[model.Table], ) -> None: """Обновляет таблицу и публикует результат. При отсутствии даты принудительно, а при наличии с учетом необходимости. """ if table is None: raise outer.DataError("Нужна таблица") await table.update(self._end_of_trading_day) await queue.put(Result(table.name, table.df))
async def _dispatch_event( db_session: outer.AbstractDBSession, events_queue: events.EventsQueue, ) -> Optional[Tuple[outer.TableName, pd.DataFrame]]: """Выбирает способ обработки события.""" event = await events_queue.get() named_df = None if isinstance(event, events.Result): events_queue.task_done() named_df = (event.name, event.df) elif isinstance(event, events.Command): asyncio.create_task( _handle_one_command(db_session, events_queue, event)) else: raise outer.DataError("Неизвестный тип события") return named_df
async def get(self, table_name: outer.TableName) -> pd.DataFrame: """Получение списка торгуемых акций с регистрационным номером и размером лота.""" name = self._log_and_validate_group(table_name, outer.SECURITIES) if name != outer.SECURITIES: raise outer.DataError( f"Некорректное имя таблицы для обновления {table_name}") async with self._cache_lock: if self._securities_cache is not None: self._logger.info(f"Загрузка из кэша {table_name}") return self._securities_cache columns = ("SECID", "REGNUMBER", "LOTSIZE") http_session = resources.get_aiohttp_session() json = await aiomoex.get_board_securities(http_session, columns=columns) df = pd.DataFrame(json) df.columns = [col.TICKER, col.REG_NUMBER, col.LOT_SIZE] self._securities_cache = df.set_index(col.TICKER) return self._securities_cache
async def get( self, table_name: outer.TableName, last_index: Optional[str] = None, ) -> pd.DataFrame: """Получение цен закрытия индекса MCFTRR.""" name = self._log_and_validate_group(table_name, outer.INDEX) if name != outer.INDEX: raise outer.DataError( f"Некорректное имя таблицы для обновления {table_name}") http_session = resources.get_aiohttp_session() json = await aiomoex.get_board_history( session=http_session, start=last_index, security=outer.INDEX, columns=("TRADEDATE", "CLOSE"), board="RTSI", market="index", ) df = pd.DataFrame(json) df.columns = [col.DATE, col.CLOSE] df[col.DATE] = pd.to_datetime(df[col.DATE]) return df.set_index(col.DATE)
def convent_to_tuple(table: model.Table) -> outer.TableTuple: """Конвертирует таблицу в кортеж.""" group, name = table.name if (timestamp := table.timestamp) is None: raise outer.DataError( f"Попытка сериализации пустой таблицы {table.name}")
def _check_index(check: IndexChecks, index: pd.Index) -> None: """Проверка свойств индекса.""" if check & IndexChecks.UNIQUE and not index.is_unique: raise outer.DataError("Индекс не уникален") if check & IndexChecks.ASCENDING and not index.is_monotonic_increasing: raise outer.DataError("Индекс не возрастает")
header=3, skiprows=[4], skipfooter=3, index_col=0) NUM_OF_MONTH = 12 FIRST_YEAR = 1991 FIRST_MONTH = "январь" async def _get_xlsx_url(session: aiohttp.ClientSession) -> str: """Получить url для файла с инфляцией.""" async with session.get(URL) as resp: html = await resp.text() if match := re.search(FILE_PATTERN, html): return match.group(0) raise outer.DataError("На странице отсутствует URL файла с инфляцией") async def _load_xlsx() -> pd.DataFrame: """Загрузка Excel-файла с данными по инфляции.""" session = resources.get_aiohttp_session() file_url = await _get_xlsx_url(session) async with session.get(file_url) as resp: xls_file = await resp.read() return pd.read_excel(xls_file, **PARSING_PARAMETERS) def _validate(df: pd.DataFrame) -> None: """Проверка заголовков таблицы.""" months, _ = df.shape first_year = df.columns[0]