Недавно, когда я учился на Self-Driving Car Nanodegree от Udacity, я наткнулся на нечто действительно удивительное, называемое фильтром Калмана. Он широко используется в беспилотных автомобилях для решения проблемы локализации.

Идея фильтра Калмана проста. Мы предполагаем, что наше текущее местоположение является некоторым гауссовым распределением со средним µ и стандартным отклонением σ. И каждый раз, когда мы проводим измерения или перемещаемся, мы обновляем это распределение. Например, если бы мы посмотрели вверх и увидели пирамиду, вероятность того, что мы оказались в Египте, увеличилась бы. И если мы двинемся к пирамиде, шанс того, что мы приблизимся к ней, также увеличится.

По сути, фильтр Калмана - это просто правило Байеса и полная вероятность. Фильтр Калмана хорош тем, что он позволяет нам легко справляться с неопределенностью. Когда мы посмотрели вверх и увидели пирамиду, есть вероятность, что эта пирамида была всего лишь изображением, и мы были слишком пьяны, чтобы это заметить. Кроме того, когда мы двигались к пирамиде, мы также могли быть слишком пьяны и полагать, что движемся к ней, в то время как на самом деле мы движемся назад. Когда мы обновляем распределение вероятностей нашего местоположения, мы должны учитывать эти «шансы». И для этого нужен фильтр Калмана.

Так что же все это общего с фондовым рынком?

Проблема с фондовым рынком заключается в том, что люди открывают длинную позицию по акции, потому что считают, что она недооценена, и продают ее, если считают, что она переоценена. Другими словами, люди верят, что существует нечто, называемое внутренней стоимостью, и, как бы это ни было нарушено, рынок в конечном итоге остановится вокруг этой стоимости.

Разве это не похоже на проблему локализации, о которой мы только что говорили?

Если мы посмотрим на внутреннюю ценность как на основную истину того, где мы находимся (наше местоположение), все данные о ценах, которые мы видим как измерения, и импульс как наш прогноз движения, то мы можем использовать фильтр Калмана, чтобы обновить нашу веру в внутреннюю ценить.

Это все разговоры, пока не запустится код. Давайте посмотрим, как эта идея противоречит наивной модели.

Чтобы проверить идею, я использую данные акций JP Morgan (JPM) с 1983–12–30 по 2016–09–26. И для реализации наивной модели я использую последнее наблюдаемое Скорректированное закрытие как следующее Скорректированное закрытие. Результат выглядит так:

Коэффициент r² действительно высокий - 99,878%, что неудивительно. Эта часть диаграммы выбрана так, чтобы была видна разница между данными и прогнозами.

Так как же модель Калмана работает против этого?

Из диаграммы ясно видно, что она соответствует данным немного лучше, чем наивная модель. Правильно ли наше внутреннее предчувствие по этому поводу?

Да! Коэффициент r² немного улучшился до 99,914%. Учитывая, насколько мы уже близки к 1, это не так уж и плохо.

Вот моя реализация фильтра Калмана на Python.

class Kalman(object):
    def __init__(self, init_price, noise=1):
        self.dt = 1 # time scale
        self.noise = noise
        
        self.x = np.array([init_price, 0]) 
        # State vector: [price, price_rate] (2x1)
        self.P = np.array([[1, 0], [0, 1]]) 
        # Uncertainty covariance matrix (2x2)
        
        self.F = np.array([[1, self.dt], [0, 1]]) 
        # Prediction matrix (2x2)
    
        self.Q = np.array([[noise, 0], [0, noise]]) 
        # Unpredictable external factor noise covariance matrix (2x2)
        
        self.H = np.array([1, 0]) 
        # Measurement mapping function (1x2)
        
        self.R_h = None # Sensor noise covariance (scalar)
        self.R_l = None # Sensor noise covariance (scalar)
        self.R_c = None # Sensor noise covariance (scalar)
        self.R_o = None # Sensor noise covariance (scalar)   
        
        self.S = None # Fusion (scalar)
        
        self.y = None # error (scalar)
        self.K = None # Kalman gain (2x1)
     
    def predict(self):
        self.x = np.matmul(self.F, self.x) 
        # Predict today's adj close
        self.P = np.matmul(np.matmul(self.F, self.P), self.F.T) + self.Q
    
    def update(self, measurement, sensor_type):
        self.y = measurement - np.matmul(self.H, self.x) 
        # Calculate loss
        
        if sensor_type == 'high':
            self.S = np.matmul(np.matmul(self.H, self.P), self.H.T) + self.R_h
        elif sensor_type == 'low':
            self.S = np.matmul(np.matmul(self.H, self.P), self.H.T) + self.R_l
        elif sensor_type == 'close':
            self.S = np.matmul(np.matmul(self.H, self.P), self.H.T) + self.R_c
        else:
            self.S = np.matmul(np.matmul(self.H, self.P), self.H.T) + self.R_o
            
        self.K = np.matmul(self.P, self.H.T) * (1/self.S) 
        # Calculate Kalman gain (3x1)
        
        # Update x and P
        self.x = self.x + self.K * self.y
        self.P = np.matmul(np.eye(2, 2) - np.matmul(self.K, self.H), self.P)

Я использую четыре «измерения» - максимум, минимум, скорректированное закрытие данного дня и открытие следующего дня, чтобы предсказать скорректированное закрытие следующего дня (и, кстати, я называю это скорректированным закрытием, потому что это то, что он называет Yahoo Финансы, но все значения были изменены мной, так что не волнуйтесь). Я также использую 6-месячные стандартные отклонения четырех переменных для шумов их измерений. А для шума движения я просто использую один доллар. Мы всегда можем вернуться и настроить эти параметры позже.

Итак, теперь мы знаем, что это работает для JP Morgan. Спрашивается, на большинстве сток работает?

Чтобы ответить на этот вопрос, я случайным образом выбираю 200 акций из S&P 500 и провожу такое же сравнение.

Вот результат.

По среднему значению видно, что улучшение примерно такое же, как и в случае с JPM, около 0,1%. И он также более последователен в плане получения хороших прогнозов со стандартным отклонением 0,002575, а не 0,003214.

И в этом есть смысл. Модель Калмана, которую мы здесь используем, можно рассматривать как модель дрейфа, модифицированную некоторыми связанными переменными (высокий, низкий, открытый) и с учетом неопределенности. И все же приятно видеть, что он с легкостью может превзойти наивную модель.

Следующий вопрос: можно ли на этом заработать? Я так не думаю, ха-ха. Но мы можем легко это проверить. И это будет тема моей следующей статьи. Будьте на связи!