Beispiel #1
0
    def __init__(self, portfolio: Portfolio, p_value: float = P_VALUE):
        """Учитывается градиент, его ошибку и ликвидность бумаг.

        :param portfolio:
            Оптимизируемый портфель.
        :param p_value:
            Требуемая значимость отклонения градиента от нуля.
        """
        self._portfolio = portfolio
        self._p_value = p_value
        self._metrics = metrics.MetricsResample(portfolio)
    def __init__(self,
                 portfolio: Portfolio,
                 *,
                 p_value: float = config.P_VALUE,
                 for_sell: int = 1):
        """Учитывается градиент, его ошибку и ликвидность бумаг.

        :param portfolio:
            Оптимизируемый портфель.
        :param p_value:
            Требуемая значимость отклонения градиента от нуля.
        :param for_sell:
            Количество претендентов на продажу.
        """
        self._portfolio = portfolio
        self._p_value = p_value
        self._metrics = metrics.MetricsResample(portfolio)
        self._for_sell = for_sell - 1
Beispiel #3
0
    def __init__(self, portfolio: Portfolio, wl_portfolio: Portfolio = None,
                 step_sz=0.01, turnover_thresh=0.1):
        """Учитывается градиент, его ошибку и ликвидность бумаг.

        :param portfolio:
            Оптимизируемый портфель.
        :param wl_portfolio:
            Портфель, содержащий список всех допустимых тикеров (white list), используется для фильтрации рекомендаций.
        :param step_sz:
            Размер оптимизационного шага в доле от портфеля
        :param turnover_thresh:
            Максимальны суммарный оборот в доле от портфеля в процессе оптимизации
        """
        self._portfolio = portfolio
        self._wl_portfolio = wl_portfolio
        self._metrics = metrics.MetricsResample(portfolio)
        self._logger = logging.getLogger()
        self.rec = None
        self._turnover_thresh = turnover_thresh * self.portfolio.value["PORTFOLIO"]
        self._step_sz = step_sz * self.portfolio.value["PORTFOLIO"]
Beispiel #4
0
def make_resample():
    """Данне для тестов."""
    positions = {"BSPB": 4890, "FESH": 1300}
    port = portfolio.Portfolio("test", "2020-05-14", 84449, positions)

    mean1 = pd.Series([0.09, 0.06], index=list(positions))
    cov1 = np.array([[0.04, 0.005], [0.005, 0.0625]])

    mean2 = pd.Series([0.05, 0.09], index=list(positions))
    cov12 = np.array([[0.0225, 0.0042], [0.0042, 0.0196]])

    def fake_get_forecasts(*_):
        yield from (
            SimpleNamespace(
                mean=mean1,
                cov=cov1,
                history_days=1,
                cor=0.4,
                shrinkage=0.3,
                risk_aversion=1,
                error_tolerance=0,
            ),
            SimpleNamespace(
                mean=mean2,
                cov=cov12,
                history_days=2,
                cor=0.5,
                shrinkage=0.2,
                risk_aversion=1,
                error_tolerance=0,
            ),
        )

    saved_get_forecast = metrics.evolve.get_forecasts
    metrics.evolve.get_forecasts = fake_get_forecasts

    yield metrics.MetricsResample(port)

    metrics.evolve.get_forecasts = saved_get_forecast
Beispiel #5
0
    def _for_trade(self) -> dict[str, pd.DataFrame]:
        """Осуществляет расчет рекомендуемых операций."""
        self._logger.info("\nОПТИМИЗАЦИЯ ПОРТФЕЛЯ\n")

        cur_prot = self.portfolio
        rec, op = None, None
        # используется для определения цикла в операциях (получение портфеля который был ранее)
        ports_set = set()
        turnover = None
        while True:
            cur_metrics = metrics.MetricsResample(cur_prot)
            grads = cur_metrics.all_gradients.iloc[:-2]
            # гармоническое среднее квантилей градиентов вместо бутстрапа
            # вычислительно существенно быстрее
            q_trans_grads = quantile_transform(grads, n_quantiles=grads.shape[0])
            # обработка (маскировка) возможных NA от плохих моделей
            q_trans_grads = np.ma.array(q_trans_grads, mask=~(q_trans_grads > 0))
            # гармоническое среднее сильнее штрафует за низкие значения (близкие к 0),
            # но его использование не принципиально - можно заменить на просто среднее или медиану
            hmean_q_trans_grads = stats.hmean(q_trans_grads, axis=1)
            # учёт оборота при ранжировании
            if turnover is None:
                turnover = quantile_transform(cur_prot.turnover_factor.loc[grads.index].values.reshape(-1, 1),
                                              n_quantiles=grads.shape[0])
            priority = stats.hmean(np.hstack([hmean_q_trans_grads.reshape(-1, 1), turnover]), axis=1)
            rec = pd.Series(data=priority, index=grads.index).to_frame(name="PRIORITY")
            rec.sort_values(["PRIORITY"], ascending=[False], inplace=True)

            # так как все операции производятся в лотах, нужно знать стоимость лота и текущее количество лотов
            rec["LOT_size"] = cur_prot.lot_size.loc[rec.index]
            rec["lots"] = (cur_prot.shares.loc[rec.index] / rec["LOT_size"]).fillna(0).astype(int)
            rec["LOT_price"] = (cur_prot.lot_size.loc[rec.index] * cur_prot.price.loc[rec.index]).round(
                2
            )

            rec['is_acceptable'] = rec['LOT_price'] < self._step_sz
            if self._wl_portfolio is not None:
                # помечаем все тикеры, которых нет в white list portfolio
                # также продаём все недопустимые позиции
                banned_tickers = rec.index.difference(self._wl_portfolio.index)
                rec.loc[banned_tickers, 'is_acceptable'] = False
                rec.loc[banned_tickers, 'lots'] = 0

            cash = cur_prot.value[CASH] + self.portfolio.value["PORTFOLIO"] - cur_prot.value["PORTFOLIO"]

            # определяем операцию:
            # покупка, если на покупку лучшего тикера хватает CASH
            # иначе - продажа худшго тикера из тех, что в наличии

            top_share = rec.loc[rec['is_acceptable']].index[0]
            top_share_lots = self._step_sz // rec.loc[top_share, "LOT_price"]
            top_share_sum = top_share_lots * rec.loc[top_share, "LOT_price"]

            if cash > top_share_sum:
                rec.loc[top_share, "lots"] += top_share_lots
                cash -= top_share_sum
                op = ("BUY", top_share)
                self._turnover_thresh -= top_share_sum
            else:
                bot_share = rec.loc[rec["lots"] > 0].index[-1]
                bot_share_lots = int(np.ceil((top_share_sum - cash) / rec.loc[bot_share, 'LOT_price']))
                bot_share_lots = min(rec.loc[bot_share, 'lots'], bot_share_lots)
                bot_share_sum = rec.loc[bot_share, 'LOT_price'] * bot_share_lots
                self._turnover_thresh -= bot_share_sum
                rec.loc[bot_share, "lots"] -= bot_share_lots
                cash += bot_share_sum
                op = ("SELL", bot_share)
            cur_prot = self._update_portfolio(rec, cash)
            log_str = '\t'.join([f'{str(len(ports_set) + 1): <7}',
                                 f'{op[0]: <4}', f'{op[1]: <7}',
                                 f"PRIORITY: {rec.loc[op[1], 'PRIORITY']:.3f}",
                                 f"CASH: {cash:.0f}"])
            self._logger.info(log_str)
            # проверка цикла
            port_tuple = tuple(cur_prot.shares.drop(CASH).tolist())
            if port_tuple in ports_set or self._turnover_thresh < 0:
                break
            ports_set.add(port_tuple)

        # оптимизированный портфель получен
        # сортируем по новому весу от портфеля для наглядности и приоритезации сделок на покупку
        rec["SUM"] = (rec["lots"] * rec["LOT_price"]).round(2)
        rec.sort_values("SUM", ascending=False, inplace=True)
        # найдем разницу с текущим портфелем
        rec["LOTS_exists"] = (
            (self.portfolio.shares.loc[rec.index] / rec["LOT_size"]).fillna(0).astype(int)
        )
        rec["SHARES_after"] = rec["lots"] * rec["LOT_size"]
        report = dict()
        report["current_port_summary"] = self.portfolio._main_info_df()

        report["SELL"] = rec.loc[rec["lots"] < rec["LOTS_exists"]].copy()
        report["SELL"]["lots"] = report["SELL"]["LOTS_exists"] - report["SELL"]["lots"]

        report["BUY"] = rec.loc[rec["lots"] > rec["LOTS_exists"]].copy()
        report["BUY"]["lots"] = report["BUY"]["lots"] - report["BUY"]["LOTS_exists"]

        report["new_port_summary"] = cur_prot._main_info_df()

        for op in ["SELL", "BUY"]:
            report[op]["SHARES"] = report[op]["lots"] * report[op]["LOT_size"]
            report[op]["SHARES_exists"] = report[op]["LOTS_exists"] * report[op]["LOT_size"]
            # корректируем сумму учитывая целое количество лотов
            report[op]["SUM"] = (report[op]["lots"] * report[op]["LOT_price"]).round(2)
            # изменение порядка столбцов
            report[op] = report[op][
                [
                    "LOT_size",
                    "LOT_price",
                    "SHARES_exists",
                    "LOTS_exists",
                    "lots",
                    "SHARES",
                    "SUM",
                    "SHARES_after",
                ]
            ]
            report[op].rename(
                {"lots": f"LOTS_to_{op}", "SHARES": f"SHARES_to_{op}", "SUM": f"SUM_to_{op}"},
                inplace=True,
                axis="columns",
            )
        return report