Skip to content

Commit

Permalink
Addressed Luca's feedback.
Browse files Browse the repository at this point in the history
  • Loading branch information
jmccorriston authored and Gerry Manoim committed Mar 10, 2020
1 parent 7eda0a4 commit d074b39
Show file tree
Hide file tree
Showing 3 changed files with 1 addition and 182 deletions.
2 changes: 1 addition & 1 deletion alphalens/performance.py
Original file line number Diff line number Diff line change
Expand Up @@ -568,7 +568,7 @@ def compute_mean_returns_spread(mean_returns,

def quantile_turnover(quantile_factor, quantile, period=1):
"""
Computes the daily proportion of names in a factor quantile that were
Computes the proportion of names in a factor quantile that were
not in that quantile in the previous period.
Parameters
Expand Down
16 changes: 0 additions & 16 deletions alphalens/tears.py
Original file line number Diff line number Diff line change
Expand Up @@ -701,21 +701,5 @@ def create_event_study_tear_sheet(factor_data,
UserWarning
)


if '1D' in factor_returns:
plotting.plot_cumulative_returns(
factor_returns['1D'],
period='1D',
freq=trading_calendar,
ax=gf.next_row(),
)

plotting.plot_cumulative_returns(
factor_returns['1D'],
period='1D',
freq=trading_calendar,
ax=gf.next_row(),
)

plt.show()
gf.close()
165 changes: 0 additions & 165 deletions alphalens/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -1016,168 +1016,3 @@ def diff_custom_calendar_timedeltas(start, end, freq):
timediff = end - start
delta_days = timediff.components.days - actual_days
return timediff - pd.Timedelta(days=delta_days)

def subportfolio_cumulative_returns(returns, period, freq=None):
"""
Builds cumulative returns from 'period' returns. This function simulates
the cumulative effect that a series of gains or losses (the 'returns')
have on an original amount of capital over a period of time.
if F is the frequency at which returns are computed (e.g. 1 day if
'returns' contains daily values) and N is the period for which the retuns
are computed (e.g. returns after 1 day, 5 hours or 3 days) then:
- if N <= F the cumulative retuns are trivially computed as Compound Return
- if N > F (e.g. F 1 day, and N is 3 days) then the returns overlap and the
cumulative returns are computed building and averaging N interleaved sub
portfolios (started at subsequent periods 1,2,..,N) each one rebalancing
every N periods. This correspond to an algorithm which trades the factor
every single time it is computed, which is statistically more robust and
with a lower volatity compared to an algorithm that trades the factor
every N periods and whose returns depend on the specific starting day of
trading.
Also note that when the factor is not computed at a specific frequency, for
exaple a factor representing a random event, it is not efficient to create
multiples sub-portfolios as it is not certain when the factor will be
traded and this would result in an underleveraged portfolio. In this case
the simulated portfolio is fully invested whenever an event happens and if
a subsequent event occur while the portfolio is still invested in a
previous event then the portfolio is rebalanced and split equally among the
active events.
Parameters
----------
returns: pd.Series
pd.Series containing factor 'period' forward returns, the index
contains timestamps at which the trades are computed and the values
correspond to returns after 'period' time
period: pandas.Timedelta or string
Length of period for which the returns are computed (1 day, 2 mins,
3 hours etc). It can be a Timedelta or a string in the format accepted
by Timedelta constructor ('1 days', '1D', '30m', '3h', '1D1h', etc)
freq : pandas DateOffset, optional
Used to specify a particular trading calendar. If not present
returns.index.freq will be used
Returns
-------
Cumulative returns series : pd.Series
Example:
2015-07-16 09:30:00 -0.012143
2015-07-16 12:30:00 0.012546
2015-07-17 09:30:00 0.045350
2015-07-17 12:30:00 0.065897
2015-07-20 09:30:00 0.030957
"""

if not isinstance(period, pd.Timedelta):
period = pd.Timedelta(period)

if freq is None:
freq = returns.index.freq

if freq is None:
freq = BDay()
warnings.warn("'freq' not set, using business day calendar",
UserWarning)

#
# returns index contains factor computation timestamps, then add returns
# timestamps too (factor timestamps + period) and save them to 'full_idx'
# Cumulative returns will use 'full_idx' index,because we want a cumulative
# returns value for each entry in 'full_idx'
#
trades_idx = returns.index.copy()
returns_idx = utils.add_custom_calendar_timedelta(trades_idx, period, freq)
full_idx = trades_idx.union(returns_idx)

#
# Build N sub_returns from the single returns Series. Each sub_retuns
# stream will contain non-overlapping returns.
# In the next step we'll compute the portfolio returns averaging the
# returns happening on those overlapping returns streams
#
sub_returns = []
print(returns.shape)
while len(trades_idx) > 0:

#
# select non-overlapping returns starting with first timestamp in index
#
sub_index = []
next = trades_idx.min()
while next <= trades_idx.max():
sub_index.append(next)
next = utils.add_custom_calendar_timedelta(next, period, freq)
# make sure to fetch the next available entry after 'period'
try:
i = trades_idx.get_loc(next, method='bfill')
next = trades_idx[i]
except KeyError:
break

sub_index = pd.DatetimeIndex(sub_index, tz=full_idx.tz)
subret = returns[sub_index]

# make the index to have all entries in 'full_idx'
subret = subret.reindex(full_idx)

#
# compute intermediate returns values for each index in subret that are
# in between the timestaps at which the factors are computed and the
# timestamps at which the 'period' returns actually happen
#
for pret_idx in reversed(sub_index):

pret = subret[pret_idx]

# get all timestamps between factor computation and period returns
pret_end_idx = \
utils.add_custom_calendar_timedelta(pret_idx, period, freq)
slice = subret[(subret.index > pret_idx) & (
subret.index <= pret_end_idx)].index

if pd.isnull(pret):
continue

def rate_of_returns(ret, period):
return ((np.nansum(ret) + 1)**(1. / period)) - 1

# compute intermediate 'period' returns values, note that this also
# moves the final 'period' returns value from trading timestamp to
# trading timestamp + 'period'
for slice_idx in slice:
sub_period = utils.diff_custom_calendar_timedeltas(
pret_idx, slice_idx, freq)
subret[slice_idx] = rate_of_returns(pret, period / sub_period)

subret[pret_idx] = np.nan

# transform returns as percentage change from previous value
subret[slice[1:]] = (subret[slice] + 1).pct_change()[slice[1:]]

sub_returns.append(subret)
trades_idx = trades_idx.difference(sub_index)

#
# Compute portfolio cumulative returns averaging the returns happening on
# overlapping returns streams.
#
sub_portfolios = pd.concat(sub_returns, axis=1)
portfolio = pd.Series(index=sub_portfolios.index)

for i, (index, row) in enumerate(sub_portfolios.iterrows()):

# check the active portfolios, count() returns non-nans elements
active_subfolios = row.count()

# fill forward portfolio value
portfolio.iloc[i] = portfolio.iloc[i - 1] if i > 0 else 1.

if active_subfolios <= 0:
continue

# current portfolio is the average of active sub_portfolios
portfolio.iloc[i] *= (row + 1).mean(skipna=True)

return portfolio

0 comments on commit d074b39

Please sign in to comment.