pandas
Analyse: tout rassembler et prendre des décisions
Recherche…
Analyse de quintile: avec des données aléatoires
L'analyse de quintile est un cadre commun pour évaluer l'efficacité des facteurs de sécurité.
Qu'est-ce qu'un facteur
Un facteur est une méthode de notation / classement des ensembles de titres. Pour un moment donné et pour un ensemble particulier de titres, un facteur peut être représenté comme une série de pandas dans laquelle l’index est un tableau des identificateurs de sécurité et les valeurs sont les scores ou les rangs.
Si nous prenons des scores factoriels au fil du temps, nous pouvons, à chaque instant, diviser l'ensemble des titres en 5 compartiments ou quintiles égaux, en fonction de l'ordre des scores factoriels. Il n'y a rien de particulièrement sacré dans le nombre 5. Nous aurions pu utiliser 3 ou 10. Mais nous utilisons 5 souvent. Enfin, nous suivons la performance de chacun des cinq compartiments pour déterminer s’il ya une différence significative dans les rendements. Nous avons tendance à nous concentrer davantage sur la différence de rendement du seau avec le rang le plus élevé par rapport à celui du rang le plus bas.
Commençons par définir certains paramètres et générer des données aléatoires.
Pour faciliter l'expérimentation avec la mécanique, nous fournissons un code simple pour créer des données aléatoires afin de nous donner une idée de son fonctionnement.
Les données aléatoires incluent
- Retours : générer des retours aléatoires pour un nombre spécifié de titres et de périodes.
- Signaux : génèrent des signaux aléatoires pour un nombre spécifié de titres et de périodes et avec le niveau de corrélation prescrit avec les retours . Pour qu'un facteur soit utile, il doit y avoir des informations ou une corrélation entre les scores / rangs et les retours ultérieurs. S'il n'y avait pas de corrélation, nous le verrions. Ce serait un bon exercice pour le lecteur, dupliquer cette analyse avec des données aléatoires générées avec
0
corrélation.
Initialisation
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
Générons maintenant un index de séries temporelles et un index représentant les identifiants de sécurité. Ensuite, utilisez-les pour créer des diagrammes de données pour les retours et les signaux
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)
Je divise m[0]
par 25
pour réduire à quelque chose qui ressemble à des rendements boursiers. J'ajoute également 1e-7
pour donner un rendement moyen positif modeste.
security_returns = pd.DataFrame(m[0] / 25 + 1e-7, tidx, ids)
security_signals = pd.DataFrame(m[1], tidx, ids)
pd.qcut
- Create Quintile Buckets
Utilisons pd.qcut
pour diviser mes signaux en pd.qcut
quintiles pour chaque période.
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)
Utilisez ces coupes comme index sur nos retours
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()
Une analyse
Retours de parcelles
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()
Visualiser la corrélation de quintile avec scatter_matrix
from pandas.tools.plotting import scatter_matrix
scatter_matrix(returns_cut, alpha=0.5, figsize=(8, 8), diagonal='hist')
plt.show()
Calculer et visualiser 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)
A quoi ça ressemble
max_dd_df(returns_cut)
Plaquons-le
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)
Calculer des statistiques
Il existe de nombreuses statistiques potentielles que nous pouvons inclure. En voici quelques-uns, mais montrez comment nous pouvons simplement intégrer de nouvelles statistiques dans notre résumé.
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')
Nous finirons par utiliser uniquement la fonction de describe
car elle rassemble tous les autres.
describe(returns_cut)
Ceci n'est pas censé être complet. Il est destiné à rassembler de nombreuses fonctionnalités des pandas et à démontrer comment vous pouvez les utiliser pour répondre à des questions importantes pour vous. C'est un sous-ensemble des types de paramètres que j'utilise pour évaluer l'efficacité des facteurs quantitatifs.