Sök…


Kvintilanalys: med slumpmässiga data

Kvintilanalys är ett vanligt ramverk för att utvärdera säkerhetsfaktorernas effektivitet.

Vad är en faktor

En faktor är en metod för värdering / rangordning av värdepappersuppsättningar. För en viss tidpunkt och för en viss uppsättning värdepapper kan en faktor representeras som en pandaserie där indexet är en rad säkerhetsidentifierare och värdena är poängen eller rankningarna.

Om vi tar faktorpoäng över tid kan vi vid varje tidpunkt dela upp värdet av värdepapper i 5 lika hinkar eller kvintiler, baserat på faktorns poängs ordning. Det finns inget särskilt heligt med siffran 5. Vi kunde ha använt 3 eller 10. Men vi använder 5 ofta. Slutligen spårar vi prestandan för var och en av de fem skoporna för att avgöra om det finns en meningsfull skillnad i avkastningen. Vi tenderar att fokusera mer intensivt på skillnaden i avkastning på skopan med den högsta rankningen relativt den för den lägsta rankningen.


Låt oss börja med att ställa in några parametrar och generera slumpmässiga data.

För att underlätta experimentet med mekaniken tillhandahåller vi enkel kod för att skapa slumpmässiga data för att ge oss en uppfattning om hur detta fungerar.

Slumpmässiga data inkluderar

  • Returer : genererar slumpmässig avkastning för angivet antal värdepapper och perioder.
  • Signaler : generera slumpmässiga signaler för specificerat antal värdepapper och perioder och med föreskriven korrelationsnivå med Returer . För att en faktor ska vara användbar måste det finnas viss information eller korrelation mellan poängen / rankningarna och efterföljande avkastning. Om det inte fanns någon korrelation, skulle vi se det. Det skulle vara en bra övning för läsaren, duplicera denna analys med slumpmässiga data genererade med 0 korrelation.

initiering

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

Låt oss nu skapa ett tidsserieindex och ett index som representerar säkerhets-id. Använd dem sedan för att skapa dataframe för returer och signaler

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)

Jag delar m[0] med 25 att skala ner till något som ser ut som avkastning. Jag lägger också till 1e-7 att ge en blygsam positiv medelavkastning.

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

pd.qcut - Skapa kvintilskopor

Låt oss använda pd.qcut att dela upp mina signaler i kvintilskopor för varje period.

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)

Använd dessa nedskärningar som index på avkastningen

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

Analys

Plot Returns

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

ange bildbeskrivning här

Visualisera kvintilkorrelation med scatter_matrix

from pandas.tools.plotting import scatter_matrix

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

ange bildbeskrivning här

Beräkna och visualisera maximalt drag ned

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)

Hur ser det här ut?

max_dd_df(returns_cut)

ange bildbeskrivning här

Låt oss planera det

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)

ange bildbeskrivning här

Beräkna statistik

Det finns många potentiella statistik som vi kan inkludera. Nedan är bara några, men visa hur enkelt vi kan införliva ny statistik i vår sammanfattning.

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

Vi kommer bara att använda describe eftersom den drar ihop alla andra.

describe(returns_cut)

ange bildbeskrivning här


Detta är inte avsett att vara omfattande. Det är tänkt att föra samman många av pandas funktioner och visa hur du kan använda den för att svara på frågor som är viktiga för dig. Detta är en delmängd av de typer av mätvärden jag använder för att utvärdera effektiviteten hos kvantitativa faktorer.



Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow