Skip to content
Permalink
Browse files

Merge pull request #5 from SvenSerneels/master

Correct higher order co-moment analytics
  • Loading branch information...
VivekPa committed Jul 4, 2019
2 parents d86cd86 + 61b4226 commit a4549191cfd73851ab7c0ce0b695fea7cb690a2b
Showing with 115 additions and 18 deletions.
  1. +1 −0 README.md
  2. +56 −5 portfolioopt/moment_est.py
  3. +29 −5 portfolioopt/opt_allocations.py
  4. +24 −3 portfolioopt/utility_functions.py
  5. +5 −5 setup.py
@@ -62,6 +62,7 @@ Classical asset allocation is the efficient frontier allocation. This is also kn

### Higher Moment Optimisation
The core principle of optimisation with higher moments is identical to any other optimisation: given some utility function and constraints, find the weights of each of the portfolio entries such that the utility function is maximised. The only difference is that the utility function in this case would contain as arguments, higher moments. Furthermore, by adding coefficients to each moment, we are able to take into account investor risk aversion and preferences.
This version of the package includes higher moment optimization based on higher co-moments, which makes much more statistical sense than the column-wise higher order moments in the original package.

### Compared to Sharpe Ratio
When doing backtests, higher moment optimisation works better than using Sharpe ratio to optimise allocations.
@@ -27,13 +27,64 @@
from sklearn import covariance
from sklearn.covariance.shrunk_covariance_ import ledoit_wolf_shrinkage
from scipy.stats import moment
import rpy2.robjects as robjects
from rpy2.robjects.packages import importr
import warnings
from portfolioopt.exp_max import expectation_max
# from portfolioopt.exp_max import expectation_max
runfile('/home/sven/Documents/PyDox/OptimalPortfolio/portfolioopt/exp_max.py', wdir='/home/sven/Documents/PyDox/OptimalPortfolio/portfolioopt')

def sample_coM3(invariants):
"""
Calculates sample third order co-moment matrix
Taps into the R package PerformanceAnalytics through rpy2
:param invariants: sample data of market invariants
:type invariants: pd.Dataframe
:param frequency: time horizon of projection, default set ot 252 days
:type frequency: int
:return: sample skew dataframe
"""

importr('PerformanceAnalytics')
if not isinstance(invariants, pd.DataFrame):
warnings.warn("invariants not a pd.Dataframe", RuntimeWarning)
invariants = pd.DataFrame(invariants)
p = invariants.shape[1]
coskew_function = robjects.r('M3.MM')
r_inv_vec = robjects.FloatVector(np.concatenate(invariants.values))
r_invariants = robjects.r.matrix(r_inv_vec,nrow=p,ncol=p)
r_M3 = coskew_function(r_invariants)

return np.matrix(r_M3)

def sample_coM4(invariants):
"""
Calculates sample fourth order co-moment matrix
Taps into the R package PerformanceAnalytics through rpy2
:param invariants: sample data of market invariants
:type invariants: pd.Dataframe
:param frequency: time horizon of projection, default set ot 252 days
:type frequency: int
:return: sample skew dataframe
"""

importr('PerformanceAnalytics')
if not isinstance(invariants, pd.DataFrame):
warnings.warn("invariants not a pd.Dataframe", RuntimeWarning)
invariants = pd.DataFrame(invariants)
p = invariants.shape[1]
coskew_function = robjects.r('M4.MM')
r_inv_vec = robjects.FloatVector(np.concatenate(invariants.values))
r_invariants = robjects.r.matrix(r_inv_vec,nrow=p,ncol=p)
r_M4 = coskew_function(r_invariants)

return np.matrix(r_M4)


def sample_skew(invariants, frequency=252):
def sample_M3(invariants, frequency=252):
"""
Calculates sample skew
Calculates column-wise sample third moment
:param invariants: sample data of market invariants
:type invariants: pd.Dataframe
@@ -48,9 +99,9 @@ def sample_skew(invariants, frequency=252):
return daily_skew*(frequency**1.5)


def sample_kurt(invariants, frequency=252):
def sample_M4(invariants, frequency=252):
"""
Calculates sample kurtosis
Calculates column-wise sample fourth moment
:param invariants: sample data of market invariants
:type invariants: pd.Dataframe
@@ -11,8 +11,8 @@
import pandas as pd
from scipy.optimize import minimize
import warnings
import portfolioopt.utility_functions as utility_functions

# import portfolioopt.utility_functions as utility_functions
runfile('/home/sven/Documents/PyDox/OptimalPortfolio/portfolioopt/utility_functions.py', wdir='/home/sven/Documents/PyDox/OptimalPortfolio/portfolioopt')

class OptimalAllocations:
def __init__(self, n, mean, cov, tickers, weight_bounds=(0, 1)):
@@ -38,6 +38,8 @@ def __init__(self, n, mean, cov, tickers, weight_bounds=(0, 1)):
self.weights = None
self.skew = None
self.kurt = None
self.coskew = None
self.cokurt = None

def moment_optimisation(self, skew, kurt, delta1, delta2, delta3, delta4):
"""
@@ -55,7 +57,29 @@ def moment_optimisation(self, skew, kurt, delta1, delta2, delta3, delta4):
self.skew = skew
self.kurt = kurt
args = (self.mean, self.cov, skew, kurt, delta1, delta2, delta3, delta4)
result = minimize(utility_functions.moment_utility, x0=self.x0, args=args,
result = minimize(moment_utility, x0=self.x0, args=args,
method="SLSQP", bounds=self.weight_bounds, constraints=self.constraints)
self.weights = result["x"]
return dict(zip(self.tickers, self.weights))

def comoment_optimisation(self, coskew, cokurt, delta1, delta2, delta3, delta4):
"""
Calculates the optimal portfolio weights for utility functions that uses mean, covariance, coskewness and cokurtosis of
market invariants.
:param skew: coskewness matrix of market invariants
:param kurt: cokurtosis matrix of market invariants
:param delta1: relative optimization weight for mean
:param delta2: relative optimization weight for covariance
:param delta3: relative optimization weight for coskewness
:param delta4: relative optimization weight for cokurtosis
:return: dictionary of tickers and weights
"""
self.coskew = coskew
self.cokurt = cokurt
args = (self.mean, self.cov, coskew, cokurt, delta1, delta2, delta3, delta4)
result = minimize(comoment_utility, x0=self.x0, args=args,
method="SLSQP", bounds=self.weight_bounds, constraints=self.constraints)
self.weights = result["x"]
return dict(zip(self.tickers, self.weights))
@@ -71,7 +95,7 @@ def sharpe_opt(self, risk_free_rate=0.02):
:rtype: dict
"""
args = (self.mean, self.cov, risk_free_rate)
result = minimize(utility_functions.sharpe, x0=self.x0, args=args,
result = minimize(sharpe, x0=self.x0, args=args,
method="SLSQP", bounds=self.weight_bounds, constraints=self.constraints)
self.weights = result["x"]
return dict(zip(self.tickers, self.weights))
@@ -89,7 +113,7 @@ def portfolio_metrics(self, verbose=False, risk_free_rate=0.02):
self.weights, self.cov))
mu = self.weights.dot(self.mean)

sharpe = -utility_functions.sharpe(self.weights, self.mean, self.cov, risk_free_rate)
sharpe = -sharpe(self.weights, self.mean, self.cov, risk_free_rate)
print(f"Expected annual return: {100*mu}")
print(f"Annual volatility: {100*sigma}")
print(f"Sharpe Ratio: {sharpe}")
@@ -36,8 +36,8 @@ def sharpe(weights, mean, cov, risk_free_rate=0.02):
:return: negative Sharpe ratio
:rtype: float
"""
mu = weights.dot(expected_returns)
sigma = np.sqrt(np.dot(weights, np.dot(cov_matrix, weights.T)))
mu = weights.dot(mean)
sigma = np.sqrt(np.dot(weights, np.dot(cov, weights.T)))
return -(mu - risk_free_rate) / sigma


@@ -49,7 +49,7 @@ def volatility(weights, cov):
:return: portfolio variance
:rtype: float
"""
portfolio_volatility = np.dot(weights.T, np.dot(cov_matrix, weights))
portfolio_volatility = np.dot(weights.T, np.dot(cov, weights))
return portfolio_volatility


@@ -74,4 +74,25 @@ def moment_utility(weights, mean, cov, skew, kurt, delta1, delta2, delta3, delta
return -utility


def comoment_utility(weights, mean, cov, coskew, cokurt, delta1, delta2, delta3, delta4):
"""
Calculates the utility using mean, covariance, coskewness and cokurtosis of data.
:param weights: portfolio weights
:param mean: mean of market invariants
:param cov: covariance matrix of market invariants
:param skew: coskewness matrix of market invariants
:param kurt: cokurtosis matrix of market invariants
:param delta1: relative weight in optimization for mean
:param delta2: relative weight in optimization for covariance
:param delta3: relative weight in optimization for skew
:param delta4: relative weight in optimization for kurtosis
:return: portfolio utility
"""
utility = delta1 * (np.dot(np.transpose(weights), mean)) - \
delta2 * (np.dot(np.dot(np.transpose(weights), cov), weights)) + \
delta3 * (np.dot(np.dot(np.transpose(x0), coskew), np.kron(x0,x0)))[0,0] - \
delta4 * (np.dot(np.dot(np.transpose(x0), cokurt), np.kron(np.kron(x0,x0),x0)))[0,0]
return -utility



@@ -1,15 +1,15 @@
from setuptools import setup

setup(
name='PortfolioAnalytics',
version='0.0.1',
name='portfolioopt',
version='0.0.2',
packages=['portfolioopt'],
url='https://github.com/VivekPa/PortfolioAnalytics',
license='MIT',
author='Vivek Palaniappan',
author='Vivek Palaniappan and Sven Serneels',
author_email='vivekpalaniappan69@gmail.com',
description='Robust Portfolio Optimisation and Analytics',
install_requires = ["numpy", "pandas", "scikit-learn", "scipy"],
description='Portfolio Optimisation and Analytics',
install_requires = ["numpy", "pandas", "scikit-learn", "scipy","rpy2"],
python_requires = ">=3",
project_urls = {
"Documentation": "https://portfolioanalytics.readthedocs.io/en/latest/",

0 comments on commit a454919

Please sign in to comment.
You can’t perform that action at this time.