Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add stochastic volatility to Bayesian tear sheet #174

Closed
wants to merge 11 commits into from
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,9 @@ docs/_build/

# PyBuilder
target/

# VIM
*.sw?

# IPython notebook checkpoints
.ipynb_checkpoints/
6 changes: 6 additions & 0 deletions WHATSNEW.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

These are new features and improvements of note in each release.

## v0.3 (TBA)

### New features

* Sector exposures: sum positions by sector given a dictionary or series of symbol to sector mappings [PR166](https://github.com/quantopian/pyfolio/pull/166)

## v0.2 (Oct 16, 2015)

This is a major release from 0.1 that includes mainly bugfixes and refactorings but also some new features. We recommend that all users upgrade to this new version.
Expand Down
84 changes: 51 additions & 33 deletions pyfolio/examples/bayesian.ipynb

Large diffs are not rendered by default.

7 changes: 4 additions & 3 deletions pyfolio/plotting.py
Original file line number Diff line number Diff line change
Expand Up @@ -938,7 +938,7 @@ def plot_sector_allocations(returns, sector_alloc, ax=None, **kwargs):

Returns
-------
ax : matplotlib.Axes, conditional
ax : matplotlib.Axes
The axes that were plotted on.
"""
if ax is None:
Expand All @@ -956,10 +956,11 @@ def plot_sector_allocations(returns, sector_alloc, ax=None, **kwargs):
loc='upper center', frameon=True, bbox_to_anchor=(
0.5, -0.14), ncol=5)

df_cum_rets = timeseries.cum_returns(returns, starting_value=1)
ax.set_xlim((df_cum_rets.index[0], df_cum_rets.index[-1]))
ax.set_xlim((sector_alloc.index[0], sector_alloc.index[-1]))
ax.set_ylabel('Exposure by sector')

return ax


def plot_return_quantiles(returns, df_weekly, df_monthly, ax=None, **kwargs):
"""Creates a box plot of daily, weekly, and monthly return
Expand Down
27 changes: 18 additions & 9 deletions pyfolio/pos.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,12 +132,9 @@ def extract_pos(positions, cash):
def get_turnover(transactions, positions, period=None):
"""
Portfolio Turnover Rate:

Average value of purchases and sales divided
by the average portfolio value for the period.

If no period is provided the period is one time step.

Parameters
----------
transactions_df : pd.DataFrame
Expand All @@ -148,7 +145,6 @@ def get_turnover(transactions, positions, period=None):
- See full explanation in tears.create_full_tear_sheet
period : str, optional
Takes the same arguments as df.resample.

Returns
-------
turnover_rate : pd.Series
Expand All @@ -170,28 +166,41 @@ def get_turnover(transactions, positions, period=None):
def get_sector_exposures(positions, symbol_sector_map):
"""
Sum position exposures by sector.

Parameters
----------
positions : pd.DataFrame
Contains position values or amounts.
- Example
index 'AAPL' 'MSFT' 'CHK' cash
2004-01-09 13939.380 -15012.993 -403.870 1477.483
2004-01-12 14492.630 -18624.870 142.630 3989.610
2004-01-13 -13853.280 13653.640 -100.980 100.000
symbol_sector_map : dict or pd.Series
Security identifier to sector mapping.
Security ids as keys/index, sectors as values.

- Example:
{'AAPL' : 'Technology'
'MSFT' : 'Technology'
'CHK' : 'Natural Resources'}
Returns
-------
positions_alloc : pd.DataFrame
sector_exp : pd.DataFrame
Sectors and their allocations.
- Example:
index 'Technology' 'Natural Resources' cash
2004-01-09 -1073.613 -403.870 1477.4830
2004-01-12 -4132.240 142.630 3989.6100
2004-01-13 -199.640 -100.980 100.0000
"""
cash = positions.pop('cash')
cash = positions['cash']
positions = positions.drop('cash', axis=1)

unmapped_pos = np.setdiff1d(positions.columns.values,
symbol_sector_map.keys())
if len(unmapped_pos) > 0:
warn_message = """Warning: Symbols {} have no sector mapping.
They will not be included in sector allocations""".format(
" ".join(map(str, unmapped_pos)))
", ".join(map(str, unmapped_pos)))
warnings.warn(warn_message, UserWarning)

sector_exp = positions.groupby(
Expand Down
66 changes: 63 additions & 3 deletions pyfolio/tears.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,16 @@
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import seaborn as sns
from time import time

def timer(msg_body, previous_time):

current_time = time()
run_time = current_time - previous_time
message = "\nFinished " + msg_body + " (required {:.2f} seconds)."
print(message.format(run_time))

return current_time


def create_full_tear_sheet(returns, positions=None, transactions=None,
Expand Down Expand Up @@ -330,12 +340,12 @@ def create_position_tear_sheet(returns, positions, gross_lev=None,
If True, returns the figure that was plotted on.
set_context : boolean, optional
If True, set default plotting style context.
sector_mapping: dict or pd.Series.
sector_mapping: dict or pd.Series, optional
Security identifier to sector mapping.
Security ids as keys, sectors as values.
"""

vertical_sections = 5 if sector_mappings else 4
vertical_sections = 5 if sector_mappings is not None else 4

fig = plt.figure(figsize=(14, vertical_sections * 6))
gs = gridspec.GridSpec(vertical_sections, 3, wspace=0.5, hspace=0.5)
Expand Down Expand Up @@ -500,7 +510,7 @@ def create_interesting_times_tear_sheet(
@plotting_context
def create_bayesian_tear_sheet(returns, benchmark_rets=None,
live_start_date=None, samples=2000,
return_fig=False):
return_fig=False, stoch_vol=False):
"""
Generate a number of Bayesian distributions and a Bayesian
cone plot of returns.
Expand All @@ -526,6 +536,8 @@ def create_bayesian_tear_sheet(returns, benchmark_rets=None,
If True, returns the figure that was plotted on.
set_context : boolean, optional
If True, set default plotting style context.
stoch_vol : boolean, optional
If True, run and plot the stochastic volatility model
"""

if live_start_date is None:
Expand All @@ -543,13 +555,21 @@ def create_bayesian_tear_sheet(returns, benchmark_rets=None,
df_test = returns.loc[returns.index >= live_start_date]

# Run T model with missing data
print("Running T model")
previous_time = time()
# track the total run time of the Bayesian tear sheet
start_time = previous_time

trace_t = bayesian.run_model('t', df_train, returns_test=df_test,
samples=samples)
previous_time = timer("T model", previous_time)

# Compute BEST model
print("\nRunning BEST model")
trace_best = bayesian.run_model('best', df_train,
returns_test=df_test,
samples=samples)
previous_time = timer("BEST model", previous_time)

# Plot results

Expand All @@ -564,6 +584,7 @@ def create_bayesian_tear_sheet(returns, benchmark_rets=None,
bayesian.plot_bayes_cone(df_train, df_test,
trace=trace_t,
ax=ax_cone)
previous_time = timer("plotting Bayesian cone", previous_time)

# Plot BEST results
row += 1
Expand All @@ -580,6 +601,7 @@ def create_bayesian_tear_sheet(returns, benchmark_rets=None,
axs.append(plt.subplot(gs[row, :]))

bayesian.plot_best(trace=trace_best, axs=axs)
previous_time = timer("plotting BEST results", previous_time)

# Compute Bayesian predictions
row += 1
Expand All @@ -597,6 +619,8 @@ def create_bayesian_tear_sheet(returns, benchmark_rets=None,
verticalalignment='bottom',
horizontalalignment='right',
transform=ax_ret_pred_day.transAxes)
previous_time = timer("computing Bayesian predictions", previous_time)

# Plot Bayesian VaRs
week_pred = (
np.cumprod(trace_t['returns_missing'][:, :5] + 1, 1) - 1)[:, -1]
Expand All @@ -611,12 +635,15 @@ def create_bayesian_tear_sheet(returns, benchmark_rets=None,
verticalalignment='bottom',
horizontalalignment='right',
transform=ax_ret_pred_week.transAxes)
previous_time = timer("plotting Bayesian VaRs estimate", previous_time)

# Run alpha beta model
print("\nRunning alpha beta model")
benchmark_rets = benchmark_rets.loc[df_train.index]
trace_alpha_beta = bayesian.run_model('alpha_beta', df_train,
bmark=benchmark_rets,
samples=samples)
previous_time = timer("running alpha beta model", previous_time)

# Plot alpha and beta
row += 1
Expand All @@ -628,6 +655,39 @@ def create_bayesian_tear_sheet(returns, benchmark_rets=None,
sns.distplot(trace_alpha_beta['beta'][100:], ax=ax_beta)
ax_beta.set_xlabel('Beta')
ax_beta.set_ylabel('Belief')
previous_time = timer("plotting alpha beta model", previous_time)

if stoch_vol:
# run stochastic volatility model
print("\nRunning stochastic volatility model on most recent 400 days of returns")
returns_cutoff = 400
if df_train.size > returns_cutoff:
df_train_truncated = df_train[-returns_cutoff:]
trace_stoch_vol = bayesian.model_stoch_vol(df_train_truncated)
previous_time = timer("running stochastic volatility model", previous_time)

# plot log(sigma) and log(nu)
print("\nPlotting stochastic volatility model")
row += 1
ax_sigma_log = plt.subplot(gs[row, 0])
ax_nu_log = plt.subplot(gs[row, 1])
sigma_log = trace_stoch_vol['sigma_log']
sns.distplot(sigma_log, ax=ax_sigma_log)
ax_sigma_log.set_xlabel('log(Sigma)')
ax_sigma_log.set_ylabel('Belief')
nu_log = trace_stoch_vol['nu_log']
sns.distplot(nu_log, ax=ax_nu_log)
ax_nu_log.set_xlabel('log(nu)')
ax_nu_log.set_ylabel('Belief')

# plot latent volatility
row += 1
ax_volatility = plt.subplot(gs[row, :])
bayesian.plot_stoch_vol(df_train_truncated, trace=trace_stoch_vol, ax=ax_volatility)
previous_time = timer("plotting stochastic volatility model", previous_time)

total_time = time() - start_time
print("\nTotal runtime was {:.2f} seconds.").format(total_time)

gs.tight_layout(fig)

Expand Down