Zoeken…


Quintile-analyse: met willekeurige gegevens

Quintile-analyse is een gemeenschappelijk raamwerk voor het evalueren van de effectiviteit van beveiligingsfactoren.

Wat is een factor

Een factor is een methode voor het scoren / rangschikken van sets effecten. Voor een bepaald tijdstip en voor een bepaalde reeks effecten kan een factor worden weergegeven als een pandas-serie waarbij de index een reeks van de beveiligings-ID's is en de waarden de scores of rangen zijn.

Als we factor scores in de tijd nemen, kunnen we op elk moment de set effecten splitsen in 5 gelijke emmers, of kwintielen, op basis van de volgorde van de factor scores. Er is niets bijzonder heiligs aan het getal 5. We hadden 3 of 10 kunnen gebruiken. Maar we gebruiken 5 vaak. Ten slotte volgen we de prestaties van elk van de vijf emmers om te bepalen of er een significant verschil in het rendement is. We hebben de neiging om meer aandacht te besteden aan het verschil in rendement van de bucket met de hoogste rang ten opzichte van die van de laagste rang.


Laten we beginnen met het instellen van enkele parameters en het genereren van willekeurige gegevens.

Om het experimenteren met de mechanica te vergemakkelijken, bieden we eenvoudige code om willekeurige gegevens te maken om ons een idee te geven hoe dit werkt.

Willekeurige gegevens bevatten

  • Retouren : genereer willekeurige rendementen voor een gespecificeerd aantal effecten en periodes.
  • Signalen : genereer willekeurige signalen voor een gespecificeerd aantal effecten en periodes en met het voorgeschreven niveau van correlatie met Returns . Om een factor nuttig te maken, moet er enige informatie of correlatie zijn tussen de scores / rangen en de daaropvolgende rendementen. Als er geen verband was, zouden we het zien. Dat zou een goede oefening zijn voor de lezer, dupliceer deze analyse met willekeurige gegevens gegenereerd met 0 correlatie.

initialisatie

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

Laten we nu een tijdreeksindex en een index genereren die beveiligings-ID's vertegenwoordigt. Gebruik ze vervolgens om dataframes te maken voor retouren en signalen

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)

Ik deel m[0] door 25 om te verkleinen naar iets dat eruit ziet als aandelenrendementen. Ik voeg ook 1e-7 toe om een bescheiden positief gemiddeld rendement te geven.

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

pd.qcut - Maak Quintile-emmers aan

Laten we pd.qcut gebruiken om mijn signalen voor elke periode in kwintielemmers te verdelen.

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)

Gebruik deze bezuinigingen als een index voor ons rendement

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()

Analyse

Plot Retourneren

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()

voer hier de afbeeldingsbeschrijving in

Visualiseer Quintile Correlatie met scatter_matrix

from pandas.tools.plotting import scatter_matrix

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

voer hier de afbeeldingsbeschrijving in

Bereken en visualiseer Maximum Draw Down

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)

Hoe ziet dit eruit?

max_dd_df(returns_cut)

voer hier de afbeeldingsbeschrijving in

Laten we het plotten

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)

voer hier de afbeeldingsbeschrijving in

Statistieken berekenen

Er zijn veel potentiële statistieken die we kunnen opnemen. Hieronder staan er enkele, maar laten zien hoe eenvoudig we nieuwe statistieken in onze samenvatting kunnen opnemen.

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')

We zullen uiteindelijk alleen de functie describe , omdat deze alle anderen bij elkaar brengt.

describe(returns_cut)

voer hier de afbeeldingsbeschrijving in


Dit is niet bedoeld om volledig te zijn. Het is bedoeld om veel functies van panda's samen te brengen en te laten zien hoe u het kunt gebruiken om vragen te beantwoorden die voor u belangrijk zijn. Dit is een subset van de soorten statistieken die ik gebruik om de effectiviteit van kwantitatieve factoren te evalueren.



Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow