Skip to content

Commit 47e15f9

Browse files
Additional historical return data:
- Return 5-year rolling returns in efficient frontier portfolios - Return 5-year rolling returns for assets in /performance route - Return full historical returns for assets in /performance route
1 parent 19b59ca commit 47e15f9

File tree

4 files changed

+55
-24
lines changed

4 files changed

+55
-24
lines changed

lib/assets.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import os
88
import json
99
import datetime
10+
from dateutil.relativedelta import relativedelta
11+
1012
import pandas as pd
1113
import pandas.io.data as web
1214
import numpy as np
@@ -32,6 +34,9 @@ def asset_json():
3234
def mean_return_json():
3335
return _mean_returns().to_json()
3436

37+
def five_year_mean_return_json():
38+
return _five_year_mean_returns().to_json()
39+
3540
def reverse_optimized_returns_json():
3641
return _reverse_optimized_returns().to_json()
3742

@@ -75,6 +80,17 @@ def _mean_returns():
7580
means = means.sort_index()
7681
return means
7782

83+
def _five_year_mean_returns():
84+
today = datetime.date.today()
85+
five_years_ago = ( today - relativedelta(years=5) )
86+
87+
five_year_returns = _monthly_returns()[five_years_ago:today]
88+
means = five_year_returns.mean()
89+
means = means.sort_index()
90+
means.index = _replacement_values_for_tickers()
91+
means = means.sort_index()
92+
return means
93+
7894
def _std_dev_returns():
7995
stds = _monthly_returns().std()
8096
stds = stds.sort_index()

lib/efficient_frontier.py

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,22 +22,22 @@
2222
# PUBLIC API #
2323
##############
2424

25-
def efficient_frontier(asset_ids, mean_returns, covariance_matrix):
25+
def efficient_frontier(asset_ids, asset_returns, historical_returns, covariance_matrix):
2626
"""
2727
Generates and formats an efficient frontier for a given set of asset ids,
28-
and their corresponding mean returns and covariances. ID's and the columns
29-
/ indexes of means/covars are expected to match.
28+
and their corresponding mean returns and covariances. ID's and the columns/indexes of means/covars are expected to match.
3029
:param asset_ids: array of asset ID's (e.g. INTL-STOCK)
31-
:param mean_returns: numpy array of the mean returns corresponding to passed in asset_ids
30+
:param asset_returns: numpy array of the relevant returns corresponding to passed in asset_ids - typically the market implied returns
31+
:param historical_returns: numpy array of the historical mean returns corresponding to passed in asset_ids
3232
:param covariance_matrix: numpy matrix of the covariances corresponding to passed in asset_ids
3333
"""
3434

3535
# Generate the frontier
36-
rfr = risk_free_rate.monthly_risk_free_rate()
37-
frontier = _solve_frontier(mean_returns, covariance_matrix, rfr)
36+
rfr = risk_free_rate.monthly_risk_free_rate()
37+
frontier = _solve_frontier(asset_returns, covariance_matrix, rfr)
3838

39-
# Format frontier
40-
formatted = _format_frontier(frontier, asset_ids)
39+
# Format the frontier, pass in the historical returns so that historical portfolio returns can be calculated
40+
formatted = _format_frontier(frontier, asset_ids, historical_returns)
4141

4242
# Sort & cull
4343
culled = _sort_and_cull_frontier(formatted)
@@ -63,7 +63,7 @@ def _sort_and_cull_frontier(frontier):
6363

6464
return culled_frontier
6565

66-
def _format_frontier(frontier, asset_ids):
66+
def _format_frontier(frontier, asset_ids, historical_returns):
6767
frontier_means = frontier[0]
6868
frontier_variances = frontier[1]
6969
frontier_weights = frontier[2]
@@ -75,10 +75,11 @@ def _format_frontier(frontier, asset_ids):
7575
formatted_frontier.append({
7676
"allocation": dict(zip(asset_ids, rounded_portfolio_allocation)),
7777
"statistics": {
78-
"mean_return": this_portfolios_mean,
79-
"std_dev": this_portfolios_std_dev,
80-
"annual_nominal_return": _annual_nominal_return(this_portfolios_mean),
81-
"annual_std_dev": _annual_std_dev(this_portfolios_std_dev),
78+
"mean_return": this_portfolios_mean,
79+
"std_dev": this_portfolios_std_dev,
80+
"annual_nominal_return": _annual_nominal_return(this_portfolios_mean),
81+
"annual_std_dev": _annual_std_dev(this_portfolios_std_dev),
82+
"annual_alternate_return": _annual_nominal_return(_port_mean(portfolio_allocation, historical_returns))
8283
}
8384
})
8485
return formatted_frontier
@@ -89,7 +90,7 @@ def _annual_nominal_return(monthly_mean_return, nominal=True):
8990
"""
9091
annualized = math.pow((1 + monthly_mean_return), 12) - 1
9192
if not nominal:
92-
annualized += 0.02 # Assume 2% inflation
93+
annualized += 0.02 # Assume 2% inflation ## This is NOT getting called by default
9394
return annualized
9495

9596
def _annual_std_dev(monthly_std_dev):

server.py

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,16 @@ def cholesky_decomposition(asset_ids):
102102

103103
return df.drop(asset_ids_to_eliminate, axis=0).drop(asset_ids_to_eliminate, axis=1)
104104

105-
def mean_returns(asset_ids, returns_source="reverse_optimized_returns"):
105+
def mean_returns(asset_ids, returns_source):
106+
"""
107+
Grabs mean return data, and returns a dataframe with only the relavant columns
108+
:param asset_ids: array of asset ID's (e.g. INTL-STOCK)
109+
:param returns_source: Which data we are interested in - one of: 'reverse_optimized_returns', 'mean_returns', 'five_year_returns'
110+
"""
111+
106112
# No need to memoize this unless jsonify is taking lots of time - data from redis
107113
# Mean returns is a *Series*
108-
json = redis_conn.get(returns_source) # other option: 'mean_returns'
114+
json = redis_conn.get(returns_source)
109115
df = pd.io.json.read_json(json, typ='series')
110116

111117
if len(asset_ids) > 0:
@@ -130,15 +136,19 @@ def std_dev_returns(asset_ids):
130136
else:
131137
return df
132138

133-
def build_efficient_frontier_for(asset_ids):
139+
def build_efficient_frontier_for(asset_ids, use_market_implied_returns=True):
134140
md5Hash = hashlib.md5("-".join(asset_ids)).hexdigest()
135-
cache_key = "efficient_frontier/" + md5Hash
141+
cache_key = "efficient_frontier/" + md5Hash + "/" + ("implied" if use_market_implied_returns else "historical")
136142
val = cache.get(cache_key)
137143
if val is None:
138144
app.logger.info("[Cache Miss] Building efficient frontier for: %s" % asset_ids)
139-
means = mean_returns(asset_ids)
140-
covars = covariance_matrix(asset_ids)
141-
frontier = efficient_frontier(asset_ids, means, covars)
145+
if use_market_implied_returns:
146+
asset_returns = mean_returns(asset_ids, returns_source="reverse_optimized_returns")
147+
else:
148+
asset_returns = mean_returns(asset_ids, returns_source="mean_returns")
149+
covars = covariance_matrix(asset_ids)
150+
historical_returns = mean_returns(asset_ids, returns_source="five_year_returns")
151+
frontier = efficient_frontier(asset_ids, asset_returns, historical_returns, covars)
142152
# FIXME: You are json dumping to store in memcache, marshalling to return
143153
# to request, then flask-jsonifying back again. Better way? For later......
144154
cache.set(cache_key, json.dumps(frontier))
@@ -191,7 +201,9 @@ def performance_route():
191201
asset_ids = get_key_in_json('asset_ids', request.json)
192202
asset_ids.sort()
193203
df = pd.DataFrame()
194-
df['mean'] = mean_returns(asset_ids)
204+
df['mean'] = mean_returns(asset_ids, returns_source="reverse_optimized_returns")
205+
df['historical_mean'] = mean_returns(asset_ids, returns_source="mean_returns")
206+
df['five_year_mean'] = mean_returns(asset_ids, returns_source="five_year_returns")
195207
df['std_dev'] = std_dev_returns(asset_ids)
196208
return Response(df.transpose().to_json(), mimetype='application/json')
197209

update.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from lib.etfs import quotes_json as quotes_json
1010
from lib.assets import asset_json as asset_json
1111
from lib.assets import mean_return_json as mean_return_json
12+
from lib.assets import five_year_mean_return_json as five_year_mean_return_json
1213
from lib.assets import reverse_optimized_returns_json as reverse_optimized_returns_json
1314
from lib.assets import std_dev_returns_json as std_dev_returns_json
1415
from lib.assets import covariance_matrix_json as covariance_matrix_json
@@ -40,8 +41,9 @@ def _get_redis_connection():
4041
pipe.set(name='inflation', value=inflation_json())
4142
pipe.set(name='real_estate', value=real_estate_json())
4243
pipe.set(name='quotes', value=quotes_json())
43-
pipe.set(name='mean_returns', value=mean_return_json())
44-
pipe.set(name='reverse_optimized_returns', value=reverse_optimized_returns_json())
44+
pipe.set(name='mean_returns', value=mean_return_json()) # Historical returns (all data)
45+
pipe.set(name='five_year_returns', value=five_year_mean_return_json()) # Rolling 5 year returns
46+
pipe.set(name='reverse_optimized_returns', value=reverse_optimized_returns_json()) # Market implied returns
4547
pipe.set(name='std_dev_returns', value=std_dev_returns_json())
4648
pipe.set(name='covariance_matrix', value=covariance_matrix_json())
4749
pipe.set(name='cholesky_decomposition', value=cholesky_decomposition_json())

0 commit comments

Comments
 (0)