pandas
Analyse: alles samenbrengen en beslissingen nemen
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()
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()
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)
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)
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)
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.