Python: как повторно использовать базовый метод, допуская модификации внутри метода? - PullRequest
0 голосов
/ 14 января 2019

Фон

Я пытаюсь реализовать модель Black-Litterman как подкласс моей уже реализованной модели Марковица . Основная идея модели Марковица заключается в следующем: вы перебираете date_list, на каждом date вы используете метод скользящего среднего для оценки ожидаемой доходности mu и ковариационной матрицы sigma, затем вычисляете среднюю дисперсию портфель с использованием оптимизатора средней дисперсии mean_variance(mu, sigma). Концептуально модель Марковица такова

class Markowitz(object):
    def __init__(self, params):
        self.price_data = ...
        self.date_list = ...

    def estimate_mu_and_sigma(self, date):
        mu = ...
        sigma = ...
        return mu, sigma

    @staticmethod
    def mean_variance_optimiser(mu, sigma):
        w = ...
        return w

    def back_test(self):
        for date in self.date_list:
            mu, sigma = self.estimate_mu_and_sigma(date)
            w = Markowitz.mean_variance_optimiser(mu, sigma)

            # do some other stuff
            pass

Единственное различие между Блэком-Литтерманом и Марковицем состоит в том, что BL использует другой метод оценки для mu и sigma, чем Марковиц, но последующая процедура оптимизации средней дисперсии идентична. Естественно, я хочу создать подкласс Markowitz, чтобы получить модель BL. Проблема в том, что в BL для оценок mu и sigma нужны дополнительные параметры. Кроме того, этот набор дополнительных параметров также динамически зависит от date, поэтому я не могу просто переопределить Markowitz.back_test, чтобы дать ему дополнительные параметры. На самом деле модель BL выглядит так:

class BlackLitterman(Markowitz):
    def __init__(self, params, more_parms):
        super().__init__(params)
        self.some_auxiliary_data = ...

    def estimate_mu_and_sigma(self, date, dynamic_params):
        mu = ...
        sigma = ...
        return mu, sigma


    def back_test(self, more_params):
        for date in self.date_list:
            dynamic_params = ...  # depends both on date and more params
            mu, sigma = self.estimate_mu_and_sigma(date, dynamic_params)
            w = Markowitz.mean_variance_optimiser(mu, sigma)

            # do some other stuff
            pass

Когда я пытаюсь это сделать, IDE уже жалуется на BlackLitterman.estimate_mu_and_sigma переопределение Markowtiz.estimate_mu_and_sigma с непоследовательной подписью. Кроме того, это фактически не использует повторно используемый код в back_test.

Может кто-нибудь сказать мне, как более элегантно наследовать от Markowitz? Спасибо!

1 Ответ

0 голосов
/ 14 января 2019

На самом деле вы не должны пытаться сделать Markowitz вашим базовым классом, но либо иметь абстрактный базовый класс Model и реализовывать обе модели как подкласс, либо - намного лучше, IMHO для вашего варианта использования - иметь один Model конкретный класс, который делает все, кроме estimate_mu_and_sigma и mean_variance_optimiser, и использует шаблон стратегии для этих частей.

Стратегическое решение:

class Estimator(object):
    def __init__(self, params, strategy):
        self.price_data = ...
        # etc
        self.strategy = strategy

    def back_test(self):
        for date in self.date_list:
            mu, sigma = self.strategy.estimate_mu_and_sigma(date)
            w = self.strategy.mean_variance_optimiser(mu, sigma)

            # do some other stuff
            pass


class MarkowitzStrategy(object):
    def __init__(self, *args, **kw):
       # ...

    def estimate_mu_and_sigma(self, date):
        mu = ...
        sigma = ...
        return mu, sigma


class BlackLittermanStrategy(object):


    def __init__(self, *args, **kw):
       # here you pass `more_params` and store them locally
       # etc so you can caculate 
       # `dynamic_params` here without polluting the Estimator class
       self.more_params = ....

    def _calc_dyn_params(self, date):
        return ...

    def estimate_mu_and_sigma(self, date):
        dynamic_params = self._calc_dyn_params(date)
        mu = ...
        sigma = ...
        return mu, sigma

Затем вы строите стратегию с соответствующими аргументами и передаете ее оценщику. Отделив вариантную часть (стратегии) ​​от инварианта (как оценить), вы избежите необходимости загрязнять Оценщик несущественными деталями.

Примечание: для решения на основе наследования вам необходимо спроектировать базовый класс, чтобы методы могли принимать все возможные аргументы для всех возможных моделей оценки, что обычно делается с использованием *args и **kwargs для всех аргументов, которые будут варьироваться от одного конкретного класса к другому. Это не очень помогает в отношении документации, отладки ...

...