Как срезать объекты DateTime более эффективно и вычислять заданную статистику c на каждой итерации? - PullRequest
1 голос
/ 14 июля 2020

Я имею дело с pandas фреймом данных, где индексом является объект DateTime, а столбцы представляют поминутную доходность по нескольким акциям из индекса SP500 вместе со столбцом доходности из индекса. Он довольно длинный (100 акций, 1510 торговых дней, поминутные данные каждый день) и выглядит следующим образом (только три акции для примера):

DateTime         SPY     AAPL    AMZN     T
2014-01-02 9:30  0.032  -0.01   0.164   0.007
2014-01-02 9:31  -0.012  0.02   0.001   -0.004
2014-01-02 9:32  -0.015  0.031  0.004   -0.001

Я пытаюсь вычислить бета-версии каждой акции для каждого дня и для каждого 30-минутного окна. Бета-версия акции в этом случае определяется как ковариация между ее доходностью и доходностью SPY, деленная на дисперсию SPY за тот же период. Мой желаемый результат - трехмерный numpy массив beta_HF, где beta_HF[s, i, j], например, означает бета-версию акции s в день i в окне j. В данный момент я вычисляю бета-версии следующим образом (пусть returns будет полным фреймом данных):

trading_days = pd.unique(returns.index.date)
window = "30min"
moments = pd.date_range(start = "9:30", end = "16:00", freq = window).time
def dispersion(trading_days, moments, df, verbose = True):
    index = 'SPY'
    beta_HF = np.zeros((df.shape[1] - 1, len(trading_days), len(moments) - 1))

    for i, day in enumerate(trading_days):
        daily_data = df[df.index.date == day]
        start_time = dt.time(9,30)
    
        for j, end_time in enumerate(moments[1:]):
            moment_data = daily_data.between_time(start_time, end_time)
            covariances = np.array([moment_data[index].cov(moment_data[symbol]) for symbol in df])
            beta_HF[:, i,j] = covariances[1:]/covariances[0]
    
        if verbose == True:
            if np.remainder(i, 100) == 0:
                print("Current Trading Day: {}".format(day))
        

    return(beta_HF)

Функция dispersion() генерирует правильный результат. Однако я понимаю, что перебираю длинные итерации, и это не очень эффективно. Я ищу более эффективный способ «разрезать» фрейм данных в каждом 30-минутном окне для каждого дня в выборке и вычислять ковариации. Фактически, для каждого среза мне нужно вычислить 101 число (100 ковариаций + 1 дисперсия). На моем локальном компьютере (Retina i5 Macbook Pro 2013 года) на все вычисления уходит около 8 минут. Я тестировал его на исследовательском сервере моего университета, и время вычислений было в основном таким же, что, вероятно, означает, что вычислительная мощность не является узким местом, но мой код в этой части имеет низкое качество. Я был бы признателен за любые идеи о том, как сделать это быстрее.

Можно отметить, что распараллеливание - это путь к go, поскольку элементы в beta_HF никогда не взаимодействуют друг с другом. Кажется, это легко распараллелить. Однако я никогда ничего не реализовывал с распараллеливанием, поэтому я новичок в этих концепциях. Есть идеи, как заставить код работать быстрее? Большое спасибо!

1 Ответ

0 голосов
/ 15 июля 2020

Вы можете использовать pandas Grouper , чтобы сгруппировать данные по частоте. Единственным недостатком является то, что у вас не может быть перекрытия windows, и он будет повторяться в течение времени, которое не существует.

Первая проблема в основном означает, что окно будет скользить от 9:30 - 9:59 до 10:00 - 10:29 вместо 9:30 - 10:00 на 10:00 - 10:30.

Вторая проблема возникает в праздничные дни и ночью, когда торговля не ведется. Следовательно, если у вас большой период без торговли, вы можете разделить DataFrame и затем объединить их.

Создать пример данных

import pandas as pd
import numpy as np

time = pd.date_range(start="2014-01-02 09:30", 
                     end="2014-01-02 16:00", freq="min")
time = time.append( pd.date_range(start="2014-01-03 09:30", 
                                  end="2014-01-03 16:00", freq="min") )
df = pd.DataFrame(data=np.random.rand(time.shape[0], 4)-0.5, 
                  index=time, columns=['SPY','AAPL','AMZN','T'])

определить диапазон, который вы хотите использовать

freq = '30min'
obs_per_day = len(pd.date_range(start = "9:30", end = "16:00", freq = "30min"))
trading_days = len(pd.unique(df.index.date))

создайте функцию для расчета бета-значений

def beta(df):
    if df.empty: # returns nan when no trading takes place
        return np.nan
    mat = df.to_numpy() # numpy is faster than pandas
    m = mat.mean(axis=0)
    mat = mat - m[np.newaxis,:] # demean

    dof = mat.shape[0] - 1 # degree of freedom
    if dof != 0: # check if you data has more than one observation
        mat = mat.T.dot(mat[:,0]) / dof  # covariance with first column
        return mat[1:] / mat[0] # beta
    else:
        return np.zeros(mat.shape[1] - 1) # return zeros for to short data e.g. 16:00

и в конце используйте pd.groupby().apply()

res = df.groupby(pd.Grouper(freq=freq)).apply(beta)
res = np.array( [k for k in res.values if ~np.isnan(k).any()] ) # remove NaN
res = res.reshape([trading_days, obs_per_day, df.shape[1]-1])

Примечание. что результат немного отличается от вашего. Результаты также немного отличаются из-за разного скольжения окон. Чтобы проверить, совпадают ли результаты, просто попробуйте что-нибудь вроде этого

trading_days = pd.unique(df.index.date)

# Your result
moments1 = pd.date_range(start = "9:30", end = "10:00", freq = "30min").time
beta(df[df.index.date == trading_days[0]].between_time(moments1[0], moments1[1]))

# mine
moments2 = pd.date_range(start = "9:30", end = "10:00", freq = "29min").time
beta(df[df.index.date == trading_days[0]].between_time(moments[0], moments2[1]))
...