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

entrer la description de l'image ici

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

entrer la description de l'image ici

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)

entrer la description de l'image ici

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)

entrer la description de l'image ici

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)

entrer la description de l'image ici


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.



Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow