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

Implement Sortino ratio for portfolio #459

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified .DS_Store
Binary file not shown.
34 changes: 34 additions & 0 deletions pypfopt/expected_returns.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import warnings
import pandas as pd
import numpy as np
import math


def returns_from_prices(prices, log_returns=False):
Expand Down Expand Up @@ -256,3 +257,36 @@ def capm_return(

# CAPM formula
return risk_free_rate + betas * (mkt_mean_ret - risk_free_rate)


def calculate_downside_diviation( historical_returns, mar):
"""
Calculate the downside diviation of all assets in a portfolio

:param historical_returns: historical returns of assets in dataframe.
:type historical_returns: np.ndarray
:param mar: Minimum Acceptable Return. Preffered practices include either US 13-week T-bill or zero.
CAUTION: mar must be in the same period as the returns. If you give daily returns then you need to convert mar to daily value.
:type mar: float
:param return_Max: an option to return the Max sortino ratio of the portfolio instead of a list of all sortino ratios for all stocks. Defaults to False
:type return_Max: Boolean
:return: Sortino ratio
:rtype: float
"""
downsideDiviations = []
linesOfData = len(historical_returns.iloc[:, [0]])
for stock in range(len(historical_returns.columns)):
noDataLines = 0 # counts NaN lines in dataset if any
negativeReturns = [] # stores the negative daily returns
for row in range(linesOfData):
if ( math.isnan(historical_returns.iloc[row, [stock]][0]) ):
noDataLines += 1
continue
if ( (historical_returns.iloc[row, [stock]][0] - mar) < 0 ):
negativeReturns.append(historical_returns.iloc[row, [stock][0]] - mar)
period = linesOfData - noDataLines # number of actual observations
squaredReturns = [r ** 2 for r in negativeReturns]
ts = sum(squaredReturns)
dd = math.sqrt(ts / period) * math.sqrt(period)
downsideDiviations.append(dd)
return downsideDiviations
31 changes: 31 additions & 0 deletions pypfopt/objective_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ def sharpe_ratio(w, expected_returns, cov_matrix, risk_free_rate=0.02, negative=
return _objective_value(w, sign * sharpe)




def L2_reg(w, gamma=1):
r"""
L2 regularisation, i.e :math:`\gamma ||w||^2`, to increase the number of nonzero weights.
Expand Down Expand Up @@ -224,3 +226,32 @@ def ex_post_tracking_error(w, historic_returns, benchmark_returns):
mean = cp.sum(x_i) / len(benchmark_returns)
tracking_error = cp.sum_squares(x_i - mean)
return _objective_value(w, tracking_error)


def sortino_ratio(w, expected_returns, downside_diviations, risk_free_rate = 0.02):
"""
Calculate the Sortino ratio of a portfolio

:param w: asset weights in the portfolio
:type w: np.ndarray OR cp.Variable
:param expected_returns: expected return of each asset
:type expected_returns: np.ndarray
:param downside_diviations: The downside diviation of each asset
:type downside_diviations: np.ndarray
:param risk_free_rate: risk-free rate of borrowing/lending, defaults to 0.02.
The period of the risk-free rate should correspond to the
frequency of expected returns.
:type risk_free_rate: float, optional
:return: Sortino ratio
:rtype: float
"""
mu = w @ expected_returns
if isinstance(downside_diviations, list):
x = np.array(downside_diviations)
dd = w * x
sortino = (mu - risk_free_rate) / dd
return sum((w * sortino))
else:
dd = w @ downside_diviations
sortino = (mu - risk_free_rate) / dd
return _objective_value(w, sortino)