Ejemplo n.º 1
0
 def _validate_index(self, item: str, df: pd.DataFrame):
     """Проверяет индекс данных с учетом настроек."""
     if self._unique_index and not df.index.is_unique:
         raise POptimizerError(
             f"Индекс {self._mongo.collection.full_name}.{item} не уникальный"
         )
     if self._ascending_index and not df.index.is_monotonic_increasing:
         raise POptimizerError(
             f"Индекс {self._mongo.collection.full_name}.{item} не возрастает"
         )
Ejemplo n.º 2
0
 def _validate(df: pd.DataFrame):
     """Проверка заголовков таблицы"""
     months, _ = df.shape
     first_year = df.columns[0]
     first_month = df.index[0]
     if months != NUM_OF_MONTH:
         raise POptimizerError(
             "Таблица должна содержать 12 строк с месяцами")
     if first_year != FIRST_YEAR:
         raise POptimizerError("Первый год должен быть 1991")
     if first_month != FIRST_MONTH:
         raise POptimizerError("Первый месяц должен быть январь")
Ejemplo n.º 3
0
    def __init__(
        self,
        date: Union[str, pd.Timestamp],
        cash: int,
        positions: Dict[str, int],
        value: Optional[float] = None,
    ):
        """При создании может быть осуществлена проверка совпадения расчетной стоимости и введенной.

        :param date:
            Дата на которую рассчитываются параметры портфеля.
        :param cash:
            Количество наличных в портфеле.
        :param positions:
            Словарь с тикерами и количеством акций.
        :param value:
            Стоимость портфеля на отчетную дату.
        """
        self._date = pd.Timestamp(date)
        self._shares = pd.Series(positions)
        self._shares.sort_index(inplace=True)
        self._shares[CASH] = cash
        self._shares[PORTFOLIO] = 1
        if value is not None and not np.isclose(self.value[PORTFOLIO], value):
            raise POptimizerError(
                f"Введенная стоимость портфеля {value} "
                f"не равна расчетной {self.value[PORTFOLIO]}")
Ejemplo n.º 4
0
 def __init__(self, html: str, table_index: int):
     soup = bs4.BeautifulSoup(html, "lxml")
     try:
         self._table = soup.find_all("table")[table_index]
     except IndexError:
         raise POptimizerError(f"На странице нет таблицы {table_index}")
     self._parsed_table = []
Ejemplo n.º 5
0
 def _download(self, item: str,
               last_index: Optional[Any]) -> List[Dict[str, Any]]:
     if item != SMART_LAB:
         raise POptimizerError(
             f"Отсутствуют данные {self._mongo.collection.full_name}.{item}"
         )
     with self._session.get(URL) as respond:
         try:
             respond.raise_for_status()
         except requests.HTTPError:
             raise POptimizerError(f"Данные {URL} не загружены")
         else:
             html = respond.text
     table = parser.HTMLTableParser(html, TABLE_INDEX)
     columns = [TICKER_COLUMN, DATE_COLUMN, DIVIDENDS_COLUMN]
     return table.get_formatted_data(columns, HEADER_SIZE, FOOTER_SIZE)
Ejemplo n.º 6
0
def update_data(
    report_name: str, date: pd.Timestamp, value: float, inflows: dict, dividends: float
):
    """Обновляет файл с данными статистики изменения стоимости портфеля.

    Проверяет, что в этом месяце статистика еще не вносилась.

    :param report_name:
        Название файла с отчетом.
    :param date:
        Дата отчета.
    :param value:
        Стоимость активов.
    :param inflows:
        Словарь с именами инвесторов и внесенных ими средств за период.
    :param dividends:
        Дивиденды за период.
    """
    df = read_data(report_name)

    last_date = df.index[-1]
    if last_date + pd.DateOffset(months=1, day=1) > date:
        raise POptimizerError("В этом месяце данные уже вносились в отчет")

    total_inflow = 0
    for investor, inflow in inflows.items():
        if investor not in df.columns:
            raise POptimizerError(f"Неверное имя инвестора - {investor}")
        df.loc[date, investor] = inflow
        total_inflow += inflow

    df.loc[date, "Value"] = value

    portfolio_return = (value - total_inflow) / df.loc[last_date, "Value"]
    investors = pdf_upper.get_investors_names(df)
    value_labels = "Value_" + investors
    pre_inflow_value = df.loc[last_date, value_labels] * portfolio_return
    df.loc[date, value_labels] = pre_inflow_value.add(
        df.loc[date, investors].values, fill_value=0
    )

    if dividends == 0:
        df.loc[date, "Dividends"] = np.nan
    else:
        df.loc[date, "Dividends"] = dividends

    df.to_excel(REPORTS_PATH / f"{report_name}.xlsx", SHEET_NAME)
Ejemplo n.º 7
0
def is_common(ticker: str):
    """Определяет является ли акция обыкновенной."""
    if len(ticker) == COMMON_TICKER_LENGTH:
        return True
    elif (len(ticker) == COMMON_TICKER_LENGTH + 1
          and ticker[COMMON_TICKER_LENGTH] == PREFERRED_TICKER_ENDING):
        return False
    raise POptimizerError(f"Некорректный тикер {ticker}")
Ejemplo n.º 8
0
 def _validate_columns(self, columns):
     """Проверка значений в колонках."""
     table = self.parsed_table
     for column in columns:
         for row, value in column.validation_dict.items():
             if value not in table[row][column.index]:
                 raise POptimizerError(
                     f"Значение в таблице {table[row][column.index]!r} - должно быть {value!r}"
                 )
Ejemplo n.º 9
0
 def _validate_new(self, item: str, data: List[Dict[str, Any]],
                   data_new: List[Dict[str, Any]]):
     """Проверяет соответствие старых и новых данных."""
     if self._validate_last:
         data = data[-1:]
         data_new = data_new[:1]
     elif len(data) > len(data_new):
         raise POptimizerError(
             f"{self._mongo.collection.full_name}.{item}: "
             f"Новые {len(data_new)} короче старых {len(data)} данных")
     for old, new in zip(data, data_new):
         for col in old:
             not_float_not_eq = (not isinstance(old[col], float)
                                 and old[col] != new[col])
             float_not_eq = isinstance(
                 old[col], float) and not np.allclose(old[col], new[col])
             if not_float_not_eq or float_not_eq:
                 raise POptimizerError(
                     f"{self._mongo.collection.full_name}.{item}: "
                     f"Новые {new} не соответствуют старым {old} данным")
Ejemplo n.º 10
0
 def _download(self, item: str,
               last_index: Optional[Any]) -> List[Dict[str, Any]]:
     """Загружает полностью данные о всех торгующихся акциях."""
     if item != SECURITIES:
         raise POptimizerError(
             f"Отсутствуют данные {self._mongo.collection.full_name}.{item}"
         )
     columns = ("SECID", "REGNUMBER", "LOTSIZE")
     data = apimoex.get_board_securities(self._session, columns=columns)
     formatters = dict(
         SECID=lambda x: (TICKER, x),
         REGNUMBER=lambda x: (REG_NUMBER, x),
         LOTSIZE=lambda x: (LOT_SIZE, x),
     )
     return manager.data_formatter(data, formatters)
Ejemplo n.º 11
0
    def price(self):
        """Цены позиций.

        CASH - 1 и PORTFOLIO - расчетная стоимость.
        """
        price = data.prices(tuple(self.index[:-2]), self.date)
        try:
            price = price.loc[self.date]
        except KeyError:
            raise POptimizerError(
                f"Для даты {self._date.date()} отсутствуют исторические котировки"
            )
        price[CASH] = 1
        price[PORTFOLIO] = (self.shares[:-1] * price).sum(axis=0)
        return price
Ejemplo n.º 12
0
 def _validate_new(
     self,
     name: str,
     df_old: Union[pd.DataFrame, pd.Series],
     df_new: Union[pd.DataFrame, pd.Series],
 ):
     """Проверяет соответствие старых и новых данных"""
     common_index = df_old.index.intersection(df_new.index)
     if not np.allclose(df_old.loc[common_index], df_new.loc[common_index]):
         raise POptimizerError(
             f"Существующие данные не соответствуют новым:\n"
             f"Категория - {self.category}\n"
             f"Название - {name}\n"
             f"Дата последнего обновления - {self._data[name].timestamp}\n"
             f"Старые значения:\n{df_old.loc[common_index]}\n"
             f"Новые значения:\n{df_new.loc[common_index]}\n")
Ejemplo n.º 13
0
 def _download(self, item: str, last_index: Optional[Any]) -> List[Dict[str, Any]]:
     url = f"https://www.dohod.ru/ik/analytics/dividend/{item.lower()}"
     with self._session.get(url) as respond:
         try:
             respond.raise_for_status()
         except requests.HTTPError:
             raise POptimizerError(f"Данные {url} не загружены")
         else:
             html = respond.text
     table = parser.HTMLTableParser(html, TABLE_INDEX)
     date_col = parser.DataColumn(
         DATE, 0, {0: "Дата закрытия реестра"}, parser.date_parser
     )
     div_col = parser.DataColumn(item, 2, {0: "Дивиденд (руб.)"}, parser.div_parser)
     columns = [date_col, div_col]
     data = table.get_formatted_data(columns, HEADER_SIZE)
     return sort_and_group(item, data)
Ejemplo n.º 14
0
    def _find_aliases(self, ticker: str) -> Tuple[List[str], pd.Timestamp]:
        """Ищет все тикеры с эквивалентным регистрационным номером."""
        securities = SecuritiesListing(self._mongo.db.name)[LISTING]

        reg_date = securities.at[ticker, DATE]
        reg_date = pd.to_datetime(reg_date, format="%d.%m.%Y %H:%M:%S")

        reg_number = securities.at[ticker, REG_NUMBER]
        if reg_number is None:
            raise POptimizerError(
                f"{ticker} - акция без регистрационного номера")
        results = apimoex.find_securities(self._session, reg_number)
        tickers = [
            row["secid"] for row in results if row["regnumber"] == reg_number
        ]

        # noinspection PyTypeChecker
        return tickers, reg_date
Ejemplo n.º 15
0
 async def _download(self, name: str):
     url = f"https://www.dohod.ru/ik/analytics/dividend/{name.lower()}"
     async with aiohttp.ClientSession() as session:
         async with session.get(url) as resp:
             try:
                 resp.raise_for_status()
             except aiohttp.ClientResponseError:
                 raise POptimizerError(f"Данные {url} не загружены")
             else:
                 html = await resp.text()
     table = parser.HTMLTableParser(html, TABLE_INDEX)
     columns = [DATE_COLUMN, DIVIDENDS_COLUMN]
     df = table.make_df(columns, HEADER_SIZE)
     df.columns = [DATE, name]
     df = df.groupby(DATE, as_index=False).sum()
     df.set_index(DATE, inplace=True)
     df.sort_index(inplace=True)
     return df[name]
Ejemplo n.º 16
0
def valid_model(params: dict, examples: Examples) -> dict:
    """Осуществляет валидацию модели по R2.

    Осуществляется проверка, что не достигнут максимум итераций, возвращается RMSE, R2 и параметры модели
    с оптимальным количеством итераций в формате целевой функции hyperopt.

    :param params:
        Словарь с параметрами модели и данных.
    :param examples:
        Класс создания обучающих примеров.
    :return:
        Словарь с результатом в формате hyperopt:

        * ключ 'loss' - нормированная RMSE на кросс-валидации (для hyperopt),
        * ключ 'status' - успешного прохождения (для hyperopt),
        * ключ 'std' - RMSE на кросс-валидации,
        * ключ 'r2' - 1- нормированная RMSE на кросс-валидации в квадрате,
        * ключ 'params' - параметры модели и данных, в которые добавлено оптимальное количество итераций
        градиентного бустинга на кросс-валидации и общие настройки.
    """
    data_params, model_params = params["data"], params["model"]
    train_pool_params, val_pool_params = examples.train_val_pool_params(
        data_params)
    train_pool = catboost.Pool(**train_pool_params)
    val_pool = catboost.Pool(**val_pool_params)
    model_params = make_model_params(data_params, model_params)
    clf = catboost.CatBoostRegressor(**model_params)
    clf.fit(train_pool, eval_set=val_pool)
    if clf.tree_count_ == MAX_ITERATIONS:
        raise POptimizerError(
            f"Необходимо увеличить MAX_ITERATIONS = {MAX_ITERATIONS}")
    model_params["iterations"] = clf.tree_count_
    scores = clf.get_best_score()["validation_0"]
    std = scores["RMSE"]
    r2 = scores["R2"]
    return dict(
        loss=-r2,
        status=hyperopt.STATUS_OK,
        std=std,
        r2=r2,
        data=data_params,
        model=model_params,
    )
Ejemplo n.º 17
0
 def _download(self, item: str,
               last_index: Optional[Any]) -> List[Dict[str, Any]]:
     """Загружает полностью данные о всех торгующихся акциях."""
     if item != LISTING:
         raise POptimizerError(
             f"Отсутствуют данные {self._mongo.collection.full_name}.{item}"
         )
     converters = dict(
         TRADE_CODE=lambda x: x if len(x) else None,
         REGISTRY_NUMBER=lambda x: x if len(x) else None,
         REGISTRY_DATE=lambda x: x if len(x) else None,
     )
     df = pd.read_csv(
         self.URL,
         encoding="CP1251",
         usecols=["TRADE_CODE", "REGISTRY_NUMBER", "REGISTRY_DATE"],
         converters=converters,
     )
     df.columns = [TICKER, REG_NUMBER, DATE]
     return df.to_dict("records")
Ejemplo n.º 18
0
 def _download(self, item: str,
               last_index: Optional[Any]) -> List[Dict[str, Any]]:
     """Загружает полностью данные по инфляции с сайта ФСГС."""
     if item != CPI:
         raise POptimizerError(
             f"Отсутствуют данные {self._mongo.collection.full_name}.{item}"
         )
     df = pd.read_excel(URL_CPI, **PARSING_PARAMETERS)
     self._validate(df)
     df = df.transpose().stack()
     first_year = df.index[0][0]
     df.index = pd.date_range(
         name=utils.DATE,
         freq="M",
         start=pd.Timestamp(year=first_year, month=1, day=31),
         periods=len(df),
     )
     df.name = CPI
     # Данные должны быть не в процентах, а в долях
     df = df.div(100)
     return df.reset_index().to_dict("records")
Ejemplo n.º 19
0
 def _download(self, item: str,
               last_index: Optional[Any]) -> List[Dict[str, Any]]:
     """Поддерживается частичная загрузка данных для обновления."""
     if item != INDEX:
         raise POptimizerError(
             f"Отсутствуют данные {self._mongo.collection.full_name}.{item}"
         )
     if last_index is not None:
         last_index = last_index.date()
     data = apimoex.get_board_history(
         self._session,
         start=last_index,
         security=INDEX,
         columns=("TRADEDATE", "CLOSE"),
         board="RTSI",
         market="index",
     )
     formatters = dict(
         TRADEDATE=lambda x: (DATE, datetime.strptime(x, "%Y-%m-%d")),
         CLOSE=lambda x: (CLOSE, x),
     )
     return manager.data_formatter(data, formatters)
Ejemplo n.º 20
0
 def _validate_index(self, name: str, df):
     """Проверяет индекс данных с учетом настроек."""
     if self.IS_UNIQUE and not df.index.is_unique:
         raise POptimizerError(f"Индекс {name} не уникальный")
     if self.IS_MONOTONIC and not df.index.is_monotonic_increasing:
         raise POptimizerError(f"Индекс {name} не возрастает монотонно")