Szukaj…


Analiza kwintylowa: z losowymi danymi

Analiza kwintylowa stanowi powszechne ramy oceny skuteczności czynników bezpieczeństwa.

Co jest czynnikiem

Czynnikiem jest metoda oceniania / klasyfikowania zestawów papierów wartościowych. Dla określonego momentu w czasie i dla określonego zestawu papierów wartościowych czynnik może być reprezentowany jako seria pand, gdzie indeks jest tablicą identyfikatorów zabezpieczeń, a wartościami są wyniki lub stopnie.

Jeśli weźmiemy wyniki czynnikowe w czasie, możemy w każdym momencie podzielić zestaw papierów wartościowych na 5 równych koszyków lub kwintyli, w oparciu o kolejność wyników czynnikowych. Nie ma nic szczególnie świętego w liczbie 5. Mogliśmy użyć 3 lub 10. Ale często używamy 5. Na koniec śledzimy wydajność każdego z pięciu segmentów, aby ustalić, czy istnieje znacząca różnica w zwrotach. Zwykle skupiamy się bardziej na różnicy w zyskach segmentu o najwyższej randze w stosunku do najniższej.


Zacznijmy od ustawienia niektórych parametrów i generowania losowych danych.

Aby ułatwić eksperymentowanie z mechaniką, udostępniamy prosty kod do tworzenia losowych danych, aby dać nam wyobrażenie o tym, jak to działa.

Dane losowe obejmują

  • Zwroty : generuj losowe zwroty dla określonej liczby papierów wartościowych i okresów.
  • Sygnały : generuj losowe sygnały dla określonej liczby papierów wartościowych i okresów oraz z określonym poziomem korelacji ze zwrotami . Aby czynnik był użyteczny, musi istnieć pewna informacja lub korelacja między wynikami / stopniami a późniejszymi zwrotami. Gdyby nie było korelacji, zobaczylibyśmy ją. To byłoby dobre ćwiczenie dla czytelnika, powiel tę analizę losowymi danymi wygenerowanymi z korelacją 0 .

Inicjalizacja

import pandas as pd
import numpy as np

num_securities = 1000
num_periods = 1000
period_frequency = 'W'
start_date = '2000-12-31'

np.random.seed([3,1415])

means = [0, 0]
covariance = [[  1., 5e-3],
              [5e-3,   1.]]

# generates to sets of data m[0] and m[1] with ~0.005 correlation
m = np.random.multivariate_normal(means, covariance,
                                  (num_periods, num_securities)).T

Teraz wygenerujmy indeks szeregów czasowych i indeks reprezentujący identyfikatory bezpieczeństwa. Następnie użyj ich do utworzenia ramek danych dla zwrotów i sygnałów

ids = pd.Index(['s{:05d}'.format(s) for s in range(num_securities)], 'ID')
tidx = pd.date_range(start=start_date, periods=num_periods, freq=period_frequency)

Dzielę m[0] przez 25 aby zmniejszyć do czegoś, co wygląda jak zwrot z magazynu. Dodaję także 1e-7 aby uzyskać skromny pozytywny średni zwrot.

security_returns = pd.DataFrame(m[0] / 25 + 1e-7, tidx, ids)
security_signals = pd.DataFrame(m[1], tidx, ids)

pd.qcut - Twórz pd.qcut

Użyjmy pd.qcut aby podzielić moje sygnały na pd.qcut dla każdego okresu.

def qcut(s, q=5):
    labels = ['q{}'.format(i) for i in range(1, 6)]
    return pd.qcut(s, q, labels=labels)
    
cut = security_signals.stack().groupby(level=0).apply(qcut)

Użyj tych cięć jako indeksu naszych zysków

returns_cut = security_returns.stack().rename('returns') \
    .to_frame().set_index(cut, append=True) \
    .swaplevel(2, 1).sort_index().squeeze() \
    .groupby(level=[0, 1]).mean().unstack()

Analiza

Działka powraca

import matplotlib.pyplot as plt

fig = plt.figure(figsize=(15, 5))
ax1 = plt.subplot2grid((1,3), (0,0))
ax2 = plt.subplot2grid((1,3), (0,1))
ax3 = plt.subplot2grid((1,3), (0,2))

# Cumulative Returns
returns_cut.add(1).cumprod() \
    .plot(colormap='jet', ax=ax1, title="Cumulative Returns")
leg1 = ax1.legend(loc='upper left', ncol=2, prop={'size': 10}, fancybox=True)
leg1.get_frame().set_alpha(.8)

# Rolling 50 Week Return
returns_cut.add(1).rolling(50).apply(lambda x: x.prod()) \
    .plot(colormap='jet', ax=ax2, title="Rolling 50 Week Return")
leg2 = ax2.legend(loc='upper left', ncol=2, prop={'size': 10}, fancybox=True)
leg2.get_frame().set_alpha(.8)

# Return Distribution
returns_cut.plot.box(vert=False, ax=ax3, title="Return Distribution")

fig.autofmt_xdate()

plt.show()

wprowadź opis zdjęcia tutaj

Wizualizuj korelację scatter_matrix pomocą scatter_matrix

from pandas.tools.plotting import scatter_matrix

scatter_matrix(returns_cut, alpha=0.5, figsize=(8, 8), diagonal='hist')
plt.show()

wprowadź opis zdjęcia tutaj

Oblicz i wizualizuj maksymalny spadek

def max_dd(returns):
    """returns is a series"""
    r = returns.add(1).cumprod()
    dd = r.div(r.cummax()).sub(1)
    mdd = dd.min()
    end = dd.argmin()
    start = r.loc[:end].argmax()
    return mdd, start, end

def max_dd_df(returns):
    """returns is a dataframe"""
    series = lambda x: pd.Series(x, ['Draw Down', 'Start', 'End'])
    return returns.apply(max_dd).apply(series)

Jak to wygląda?

max_dd_df(returns_cut)

wprowadź opis zdjęcia tutaj

Wymyślmy to

draw_downs = max_dd_df(returns_cut)

fig, axes = plt.subplots(5, 1, figsize=(10, 8))
for i, ax in enumerate(axes[::-1]):
    returns_cut.iloc[:, i].add(1).cumprod().plot(ax=ax)
    sd, ed = draw_downs[['Start', 'End']].iloc[i]
    ax.axvspan(sd, ed, alpha=0.1, color='r')
    ax.set_ylabel(returns_cut.columns[i])

fig.suptitle('Maximum Draw Down', fontsize=18)
fig.tight_layout()
plt.subplots_adjust(top=.95)

wprowadź opis zdjęcia tutaj

Oblicz statystyki

Istnieje wiele potencjalnych statystyk, które możemy uwzględnić. Poniżej znajduje się tylko kilka, ale pokaż, jak łatwo możemy włączyć nowe statystyki do naszego podsumowania.

def frequency_of_time_series(df):
    start, end = df.index.min(), df.index.max()
    delta = end - start
    return round((len(df) - 1.) * 365.25 / delta.days, 2)

def annualized_return(df):
    freq = frequency_of_time_series(df)
    return df.add(1).prod() ** (1 / freq) - 1
    
def annualized_volatility(df):
    freq = frequency_of_time_series(df)
    return df.std().mul(freq ** .5)

def sharpe_ratio(df):
    return annualized_return(df) / annualized_volatility(df)

def describe(df):
    r = annualized_return(df).rename('Return')
    v = annualized_volatility(df).rename('Volatility')
    s = sharpe_ratio(df).rename('Sharpe')
    skew = df.skew().rename('Skew')
    kurt = df.kurt().rename('Kurtosis')
    desc = df.describe().T
    
    return pd.concat([r, v, s, skew, kurt, desc], axis=1).T.drop('count')

Skończy się na tym, że użyjemy tylko funkcji describe ponieważ łączy ona wszystkie pozostałe.

describe(returns_cut)

wprowadź opis zdjęcia tutaj


Nie ma to być wyczerpujące. Ma on na celu zebranie wielu funkcji pand i pokazanie, w jaki sposób możesz z nich skorzystać, aby pomóc odpowiedzieć na ważne pytania. Jest to podzbiór rodzajów wskaźników, których używam do oceny skuteczności czynników ilościowych.



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow