Skip to content

Commit 623a9fa

Browse files
author
Vikram Narayan
authored
Merge pull request #76 from quantopian/add-perf-attrib
MAINT: add perf attrib to empyrical
2 parents 6c63441 + 37fbb98 commit 623a9fa

File tree

9 files changed

+3358
-0
lines changed

9 files changed

+3358
-0
lines changed

empyrical/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,9 @@
6262
MONTHLY,
6363
YEARLY
6464
)
65+
66+
67+
from .perf_attrib import (
68+
perf_attrib,
69+
compute_exposures,
70+
)

empyrical/perf_attrib.py

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
import pandas as pd
2+
3+
4+
def perf_attrib(returns,
5+
positions,
6+
factor_returns,
7+
factor_loadings):
8+
"""
9+
Attributes the performance of a returns stream to a set of risk factors.
10+
11+
Performance attribution determines how much each risk factor, e.g.,
12+
momentum, the technology sector, etc., contributed to total returns, as
13+
well as the daily exposure to each of the risk factors. The returns that
14+
can be attributed to one of the given risk factors are the
15+
`common_returns`, and the returns that _cannot_ be attributed to a risk
16+
factor are the `specific_returns`. The `common_returns` and
17+
`specific_returns` summed together will always equal the total returns.
18+
19+
Parameters
20+
----------
21+
returns : pd.Series
22+
Returns for each day in the date range.
23+
- Example:
24+
2017-01-01 -0.017098
25+
2017-01-02 0.002683
26+
2017-01-03 -0.008669
27+
28+
positions: pd.Series
29+
Daily holdings in percentages, indexed by date.
30+
- Examples:
31+
dt ticker
32+
2017-01-01 AAPL 0.417582
33+
TLT 0.010989
34+
XOM 0.571429
35+
2017-01-02 AAPL 0.202381
36+
TLT 0.535714
37+
XOM 0.261905
38+
39+
factor_returns : pd.DataFrame
40+
Returns by factor, with date as index and factors as columns
41+
- Example:
42+
momentum reversal
43+
2017-01-01 0.002779 -0.005453
44+
2017-01-02 0.001096 0.010290
45+
46+
factor_loadings : pd.DataFrame
47+
Factor loadings for all days in the date range, with date and ticker as
48+
index, and factors as columns.
49+
- Example:
50+
momentum reversal
51+
dt ticker
52+
2017-01-01 AAPL -1.592914 0.852830
53+
TLT 0.184864 0.895534
54+
XOM 0.993160 1.149353
55+
2017-01-02 AAPL -0.140009 -0.524952
56+
TLT -1.066978 0.185435
57+
XOM -1.798401 0.761549
58+
59+
Returns
60+
-------
61+
tuple of (risk_exposures_portfolio, perf_attribution)
62+
63+
risk_exposures_portfolio : pd.DataFrame
64+
df indexed by datetime, with factors as columns
65+
- Example:
66+
momentum reversal
67+
dt
68+
2017-01-01 -0.238655 0.077123
69+
2017-01-02 0.821872 1.520515
70+
71+
perf_attribution : pd.DataFrame
72+
df with factors, common returns, and specific returns as columns,
73+
and datetimes as index
74+
- Example:
75+
momentum reversal common_returns specific_returns
76+
dt
77+
2017-01-01 0.249087 0.935925 1.185012 1.185012
78+
2017-01-02 -0.003194 -0.400786 -0.403980 -0.403980
79+
80+
Note
81+
----
82+
See https://en.wikipedia.org/wiki/Performance_attribution for more details.
83+
"""
84+
risk_exposures_portfolio = compute_exposures(positions,
85+
factor_loadings)
86+
87+
perf_attrib_by_factor = risk_exposures_portfolio.multiply(factor_returns)
88+
89+
common_returns = perf_attrib_by_factor.sum(axis='columns')
90+
specific_returns = returns - common_returns
91+
92+
returns_df = pd.DataFrame({'total_returns': returns,
93+
'common_returns': common_returns,
94+
'specific_returns': specific_returns})
95+
96+
return (risk_exposures_portfolio,
97+
pd.concat([perf_attrib_by_factor, returns_df], axis='columns'))
98+
99+
100+
def compute_exposures(positions, factor_loadings):
101+
"""
102+
Compute daily risk factor exposures.
103+
104+
Parameters
105+
----------
106+
positions: pd.Series
107+
A series of holdings as percentages indexed by date and ticker.
108+
- Examples:
109+
dt ticker
110+
2017-01-01 AAPL 0.417582
111+
TLT 0.010989
112+
XOM 0.571429
113+
2017-01-02 AAPL 0.202381
114+
TLT 0.535714
115+
XOM 0.261905
116+
117+
factor_loadings : pd.DataFrame
118+
Factor loadings for all days in the date range, with date and ticker as
119+
index, and factors as columns.
120+
- Example:
121+
momentum reversal
122+
dt ticker
123+
2017-01-01 AAPL -1.592914 0.852830
124+
TLT 0.184864 0.895534
125+
XOM 0.993160 1.149353
126+
2017-01-02 AAPL -0.140009 -0.524952
127+
TLT -1.066978 0.185435
128+
XOM -1.798401 0.761549
129+
130+
Returns
131+
-------
132+
risk_exposures_portfolio : pd.DataFrame
133+
df indexed by datetime, with factors as columns
134+
- Example:
135+
momentum reversal
136+
dt
137+
2017-01-01 -0.238655 0.077123
138+
2017-01-02 0.821872 1.520515
139+
"""
140+
risk_exposures = factor_loadings.multiply(positions, axis='rows')
141+
return risk_exposures.groupby(level='dt').sum()

0 commit comments

Comments
 (0)