Skip to content

Commit d074b39

Browse files
jmccorristonGerry Manoim
authored andcommitted
Addressed Luca's feedback.
1 parent 7eda0a4 commit d074b39

File tree

3 files changed

+1
-182
lines changed

3 files changed

+1
-182
lines changed

alphalens/performance.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -568,7 +568,7 @@ def compute_mean_returns_spread(mean_returns,
568568

569569
def quantile_turnover(quantile_factor, quantile, period=1):
570570
"""
571-
Computes the daily proportion of names in a factor quantile that were
571+
Computes the proportion of names in a factor quantile that were
572572
not in that quantile in the previous period.
573573
574574
Parameters

alphalens/tears.py

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -701,21 +701,5 @@ def create_event_study_tear_sheet(factor_data,
701701
UserWarning
702702
)
703703

704-
705-
if '1D' in factor_returns:
706-
plotting.plot_cumulative_returns(
707-
factor_returns['1D'],
708-
period='1D',
709-
freq=trading_calendar,
710-
ax=gf.next_row(),
711-
)
712-
713-
plotting.plot_cumulative_returns(
714-
factor_returns['1D'],
715-
period='1D',
716-
freq=trading_calendar,
717-
ax=gf.next_row(),
718-
)
719-
720704
plt.show()
721705
gf.close()

alphalens/utils.py

Lines changed: 0 additions & 165 deletions
Original file line numberDiff line numberDiff line change
@@ -1016,168 +1016,3 @@ def diff_custom_calendar_timedeltas(start, end, freq):
10161016
timediff = end - start
10171017
delta_days = timediff.components.days - actual_days
10181018
return timediff - pd.Timedelta(days=delta_days)
1019-
1020-
def subportfolio_cumulative_returns(returns, period, freq=None):
1021-
"""
1022-
Builds cumulative returns from 'period' returns. This function simulates
1023-
the cumulative effect that a series of gains or losses (the 'returns')
1024-
have on an original amount of capital over a period of time.
1025-
1026-
if F is the frequency at which returns are computed (e.g. 1 day if
1027-
'returns' contains daily values) and N is the period for which the retuns
1028-
are computed (e.g. returns after 1 day, 5 hours or 3 days) then:
1029-
- if N <= F the cumulative retuns are trivially computed as Compound Return
1030-
- if N > F (e.g. F 1 day, and N is 3 days) then the returns overlap and the
1031-
cumulative returns are computed building and averaging N interleaved sub
1032-
portfolios (started at subsequent periods 1,2,..,N) each one rebalancing
1033-
every N periods. This correspond to an algorithm which trades the factor
1034-
every single time it is computed, which is statistically more robust and
1035-
with a lower volatity compared to an algorithm that trades the factor
1036-
every N periods and whose returns depend on the specific starting day of
1037-
trading.
1038-
1039-
Also note that when the factor is not computed at a specific frequency, for
1040-
exaple a factor representing a random event, it is not efficient to create
1041-
multiples sub-portfolios as it is not certain when the factor will be
1042-
traded and this would result in an underleveraged portfolio. In this case
1043-
the simulated portfolio is fully invested whenever an event happens and if
1044-
a subsequent event occur while the portfolio is still invested in a
1045-
previous event then the portfolio is rebalanced and split equally among the
1046-
active events.
1047-
1048-
Parameters
1049-
----------
1050-
returns: pd.Series
1051-
pd.Series containing factor 'period' forward returns, the index
1052-
contains timestamps at which the trades are computed and the values
1053-
correspond to returns after 'period' time
1054-
period: pandas.Timedelta or string
1055-
Length of period for which the returns are computed (1 day, 2 mins,
1056-
3 hours etc). It can be a Timedelta or a string in the format accepted
1057-
by Timedelta constructor ('1 days', '1D', '30m', '3h', '1D1h', etc)
1058-
freq : pandas DateOffset, optional
1059-
Used to specify a particular trading calendar. If not present
1060-
returns.index.freq will be used
1061-
1062-
Returns
1063-
-------
1064-
Cumulative returns series : pd.Series
1065-
Example:
1066-
2015-07-16 09:30:00 -0.012143
1067-
2015-07-16 12:30:00 0.012546
1068-
2015-07-17 09:30:00 0.045350
1069-
2015-07-17 12:30:00 0.065897
1070-
2015-07-20 09:30:00 0.030957
1071-
"""
1072-
1073-
if not isinstance(period, pd.Timedelta):
1074-
period = pd.Timedelta(period)
1075-
1076-
if freq is None:
1077-
freq = returns.index.freq
1078-
1079-
if freq is None:
1080-
freq = BDay()
1081-
warnings.warn("'freq' not set, using business day calendar",
1082-
UserWarning)
1083-
1084-
#
1085-
# returns index contains factor computation timestamps, then add returns
1086-
# timestamps too (factor timestamps + period) and save them to 'full_idx'
1087-
# Cumulative returns will use 'full_idx' index,because we want a cumulative
1088-
# returns value for each entry in 'full_idx'
1089-
#
1090-
trades_idx = returns.index.copy()
1091-
returns_idx = utils.add_custom_calendar_timedelta(trades_idx, period, freq)
1092-
full_idx = trades_idx.union(returns_idx)
1093-
1094-
#
1095-
# Build N sub_returns from the single returns Series. Each sub_retuns
1096-
# stream will contain non-overlapping returns.
1097-
# In the next step we'll compute the portfolio returns averaging the
1098-
# returns happening on those overlapping returns streams
1099-
#
1100-
sub_returns = []
1101-
print(returns.shape)
1102-
while len(trades_idx) > 0:
1103-
1104-
#
1105-
# select non-overlapping returns starting with first timestamp in index
1106-
#
1107-
sub_index = []
1108-
next = trades_idx.min()
1109-
while next <= trades_idx.max():
1110-
sub_index.append(next)
1111-
next = utils.add_custom_calendar_timedelta(next, period, freq)
1112-
# make sure to fetch the next available entry after 'period'
1113-
try:
1114-
i = trades_idx.get_loc(next, method='bfill')
1115-
next = trades_idx[i]
1116-
except KeyError:
1117-
break
1118-
1119-
sub_index = pd.DatetimeIndex(sub_index, tz=full_idx.tz)
1120-
subret = returns[sub_index]
1121-
1122-
# make the index to have all entries in 'full_idx'
1123-
subret = subret.reindex(full_idx)
1124-
1125-
#
1126-
# compute intermediate returns values for each index in subret that are
1127-
# in between the timestaps at which the factors are computed and the
1128-
# timestamps at which the 'period' returns actually happen
1129-
#
1130-
for pret_idx in reversed(sub_index):
1131-
1132-
pret = subret[pret_idx]
1133-
1134-
# get all timestamps between factor computation and period returns
1135-
pret_end_idx = \
1136-
utils.add_custom_calendar_timedelta(pret_idx, period, freq)
1137-
slice = subret[(subret.index > pret_idx) & (
1138-
subret.index <= pret_end_idx)].index
1139-
1140-
if pd.isnull(pret):
1141-
continue
1142-
1143-
def rate_of_returns(ret, period):
1144-
return ((np.nansum(ret) + 1)**(1. / period)) - 1
1145-
1146-
# compute intermediate 'period' returns values, note that this also
1147-
# moves the final 'period' returns value from trading timestamp to
1148-
# trading timestamp + 'period'
1149-
for slice_idx in slice:
1150-
sub_period = utils.diff_custom_calendar_timedeltas(
1151-
pret_idx, slice_idx, freq)
1152-
subret[slice_idx] = rate_of_returns(pret, period / sub_period)
1153-
1154-
subret[pret_idx] = np.nan
1155-
1156-
# transform returns as percentage change from previous value
1157-
subret[slice[1:]] = (subret[slice] + 1).pct_change()[slice[1:]]
1158-
1159-
sub_returns.append(subret)
1160-
trades_idx = trades_idx.difference(sub_index)
1161-
1162-
#
1163-
# Compute portfolio cumulative returns averaging the returns happening on
1164-
# overlapping returns streams.
1165-
#
1166-
sub_portfolios = pd.concat(sub_returns, axis=1)
1167-
portfolio = pd.Series(index=sub_portfolios.index)
1168-
1169-
for i, (index, row) in enumerate(sub_portfolios.iterrows()):
1170-
1171-
# check the active portfolios, count() returns non-nans elements
1172-
active_subfolios = row.count()
1173-
1174-
# fill forward portfolio value
1175-
portfolio.iloc[i] = portfolio.iloc[i - 1] if i > 0 else 1.
1176-
1177-
if active_subfolios <= 0:
1178-
continue
1179-
1180-
# current portfolio is the average of active sub_portfolios
1181-
portfolio.iloc[i] *= (row + 1).mean(skipna=True)
1182-
1183-
return portfolio

0 commit comments

Comments
 (0)