import numpy as np
import pandas as pd
[docs]def assets_constraints(constraints, asset_classes):
r"""
Create the linear constraints matrixes A and B of the constraint
:math:`Aw \geq B`.
Parameters
----------
constraints : DataFrame of shape (n_constraints, n_fields)
Constraints matrix, where n_constraints is the number of constraints
and n_fields is the number of fields of constraints matrix, the fields
are:
- Disabled: (bool) indicates if the constraint is enable.
- Type: (str) can be: 'Assets', 'Classes', 'All Assets' and 'Each asset in a class'.
- Set: (str) if Type is 'Classes' or 'Each asset in a class' specified the name of the asset's classes set.
- Position: (str) the name of the asset or asset class of the constraint.
- Sign: (str) can be '>=' or '<='.
- Weight: (scalar) is the maximum or minimum weight of the absolute constraint.
- Type Relative: (str) can be: 'Assets' or 'Classes'.
- Relative Set: (str) if Type Relative is 'Classes' specified the name of the set of asset classes.
- Relative: (str) the name of the asset or asset class of the relative constraint.
- Factor: (scalar) is the factor of the relative constraint.
asset_classes : DataFrame of shape (n_assets, n_cols)
Asset's classes matrix, where n_assets is the number of assets and
n_cols is the number of columns of the matrix where the first column
is the asset list and the next columns are the different asset's
classes sets.
Returns
-------
A : nd-matrix
The matrix A of :math:`Aw \geq B`.
B : nd-matrix
The matrix B of :math:`Aw \geq B`.
Raises
------
ValueError when the value cannot be calculated.
Examples
--------
::
import riskfolio.ConstraintsFunctions as cf
asset_classes = {'Assets': ['FB', 'GOOGL', 'NTFX', 'BAC', 'WFC', 'TLT', 'SHV'],
'Class 1': ['Equity', 'Equity', 'Equity', 'Equity', 'Equity',
'Fixed Income', 'Fixed Income'],
'Class 2': ['Technology', 'Technology', 'Technology',
'Financial', 'Financial', 'Treasury', 'Treasury'],}
asset_classes = pd.DataFrame(asset_classes)
constraints = {'Disabled': [False, False, False, False, False, False, False],
'Type': ['Classes', 'Classes', 'Assets', 'Assets', 'Classes',
'All Assets', 'Each asset in a class'],
'Set': ['Class 1', 'Class 1', '', '', 'Class 2', '', 'Class 1'],
'Position': ['Equity', 'Fixed Income', 'BAC', 'WFC', 'Financial',
'', 'Equity'],
'Sign': ['<=', '<=', '<=', '<=', '>=', '>=', '>='],
'Weight': [0.6, 0.5, 0.1, '', '', 0.02, ''],
'Type Relative': ['', '', '', 'Assets', 'Classes', '', 'Assets'],
'Relative Set': ['', '', '', '', 'Class 1', '', ''],
'Relative': ['', '', '', 'FB', 'Fixed Income', '', 'TLT'],
'Factor': ['', '', '', 1.2, 0.5, '', 0.4]}
constraints = pd.DataFrame(constraints)
The constraint looks like this:
.. image:: images/Constraints.png
It is easier to construct the constraints in excel and then upload to a
dataframe.
To create the matrixes A and B we use the following command:
::
A, B = cf.assets_constraints(constraints, asset_classes)
The matrixes A and B looks like this (all constraints were converted to a linear
constraint):
.. image:: images/AxB.png
"""
if not isinstance(constraints, pd.DataFrame) and not isinstance(
asset_classes, pd.DataFrame
):
raise ValueError("constraints and asset_classes must be DataFrames")
if constraints.shape[1] != 10:
raise ValueError("constraints must have ten columns")
n = len(constraints)
m = len(asset_classes)
data = constraints.fillna("")
data = data.values.tolist()
assetslist = asset_classes.iloc[:, 0].values.tolist()
A = []
B = []
for i in range(0, n):
if data[i][0] == False:
if data[i][1] == "Assets":
item = assetslist.index(data[i][3])
if data[i][4] == ">=":
d = 1
elif data[i][4] == "<=":
d = -1
if data[i][5] != "":
A1 = [0] * m
A1[item] = d
A.append(A1)
B.append([data[i][5] * d])
else:
A1 = [0] * m
A1[item] = 1
if data[i][6] == "Assets":
item2 = assetslist.index(data[i][8])
A2 = [0] * m
A2[item2] = 1
elif data[i][6] == "Classes":
A2 = np.where(
asset_classes[data[i][7]].values == data[i][8], 1, 0
)
A1 = ((np.array(A1) + np.array(A2) * data[i][9] * -1) * d).tolist()
A.append(A1)
B.append([0])
elif data[i][1] == "All Assets":
item = len(assetslist)
if data[i][4] == ">=":
d = 1
elif data[i][4] == "<=":
d = -1
if data[i][5] != "":
A1 = np.identity(item) * d
A1 = A1.tolist()
B1 = np.ones((item, 1)) * d * data[i][5]
for i in range(0, item):
A.append(A1[i])
B.append(B1.tolist()[0])
else:
A1 = np.identity(item)
if data[i][6] == "Assets":
item2 = assetslist.index(data[i][8])
A2 = np.zeros((item, item - 1))
A2 = np.insert(A2, item2 - 1, 1, axis=1)
elif data[i][6] == "Classes":
A1 = np.identity(item)
A2 = np.where(
asset_classes[data[i][7]].values == data[i][8], 1, 0
)
A2 = np.ones((item, item)) * np.array(A2)
A1 = ((np.array(A1) + np.array(A2) * data[i][9]) * d * -1).tolist()
for i in range(0, item):
A.append(A1[i])
B.append([0])
elif data[i][1] == "Classes":
if data[i][4] == ">=":
d = 1
elif data[i][4] == "<=":
d = -1
if data[i][5] != "":
A1 = np.where(asset_classes[data[i][2]].values == data[i][3], 1, 0)
A1 = np.array(A1) * d
A1 = A1.tolist()
A.append(A1)
B.append([data[i][5] * d])
else:
A1 = np.where(asset_classes[data[i][2]].values == data[i][3], 1, 0)
if data[i][6] == "Assets":
item2 = assetslist.index(data[i][8])
A2 = [0] * m
A2[item2] = 1
elif data[i][6] == "Classes":
A2 = np.where(
asset_classes[data[i][7]].values == data[i][8], 1, 0
)
A1 = ((np.array(A1) + np.array(A2) * data[i][9]) * d * -1).tolist()
A.append(A1)
B.append([0])
elif data[i][1] == "Each asset in a class":
if data[i][4] == ">=":
d = 1
elif data[i][4] == "<=":
d = -1
if data[i][5] != "":
A1 = np.where(asset_classes[data[i][2]].values == data[i][3], 1, 0)
l = 0
for k in A1:
if k == 1:
A3 = [0] * m
A3[l] = 1 * d
A.append(A3)
B.append([data[i][5] * d])
l = l + 1
else:
A1 = np.where(asset_classes[data[i][2]].values == data[i][3], 1, 0)
l = 0
for k in A1:
if k == 1:
A3 = [0] * m
A3[l] = 1
if data[i][6] == "Assets":
item2 = assetslist.index(data[i][8])
A2 = [0] * m
A2[item2] = 1
elif data[i][6] == "Classes":
A2 = np.where(
asset_classes[data[i][7]].values == data[i][8], 1, 0
)
A3 = (
(np.array(A3) + np.array(A2) * data[i][9]) * d * -1
).tolist()
A.append(A3)
B.append([0])
l = l + 1
A = np.matrix(A)
B = np.matrix(B)
return A, B
[docs]def factors_constraints(constraints, loadings):
r"""
Create the factors constraints matrixes C and D of the constraint
:math:`Cw \geq D`.
Parameters
----------
constraints : DataFrame of shape (n_constraints, n_fields)
Constraints matrix, where n_constraints is the number of constraints
and n_fields is the number of fields of constraints matrix, the fields
are:
- Disabled: (bool) indicates if the constraint is enable.
- Factor: (str) the name of the factor of the constraint.
- Sign: (str) can be '>=' or '<='.
- Value: (scalar) is the maximum or minimum value of the factor.
loadings : DataFrame of shape (n_assets, n_features)
The loadings matrix.
Returns
-------
C : nd-matrix
The matrix C of :math:`Cw \geq D`.
D : nd-matrix
The matrix D of :math:`Cw \geq D`.
Raises
------
ValueError when the value cannot be calculated.
Examples
--------
::
loadings = {'const': [0.0004, 0.0002, 0.0000, 0.0006, 0.0001, 0.0003, -0.0003],
'MTUM': [0.1916, 1.0061, 0.8695, 1.9996, 0.0000, 0.0000, 0.0000],
'QUAL': [0.0000, 2.0129, 1.4301, 0.0000, 0.0000, 0.0000, 0.0000],
'SIZE': [0.0000, 0.0000, 0.0000, 0.4717, 0.0000, -0.1857, 0.0000],
'USMV': [-0.7838, -1.6439, -1.0176, -1.4407, 0.0055, 0.5781, 0.0000],
'VLUE': [1.4772, -0.7590, -0.4090, 0.0000, -0.0054, -0.4844, 0.9435]}
loadings = pd.DataFrame(loadings)
constraints = {'Disabled': [False, False, False],
'Factor': ['MTUM', 'USMV', 'VLUE'],
'Sign': ['<=', '<=', '>='],
'Value': [0.9, -1.2, 0.3],}
constraints = pd.DataFrame(constraints)
The constraint looks like this:
.. image:: images/Constraints2.png
It is easier to construct the constraints in excel and then upload to a
dataframe.
To create the matrixes C and D we use the following command:
::
C, D = cf.factors_constraints(constraints, loadings)
The matrixes C and D looks like this (all constraints were converted to a linear
constraint):
.. image:: images/CxD.png
"""
if not isinstance(constraints, pd.DataFrame) and not isinstance(
loadings, pd.DataFrame
):
raise ValueError("constraints and loadings must be DataFrames")
if constraints.shape[1] != 4:
raise ValueError("constraints must have four columns")
n = len(constraints)
data = constraints.fillna("")
data = data.values.tolist()
C = []
D = []
for i in range(0, n):
if data[i][0] == False:
if data[i][2] == ">=":
d = 1
elif data[i][2] == "<=":
d = -1
C1 = loadings[data[i][1]].values
C.append(C1 * d)
D.append([data[i][3] * d])
C = np.matrix(C)
D = np.matrix(D)
return C, D
[docs]def assets_views(views, asset_classes):
r"""
Create the assets views matrixes P and Q of the views :math:`Pw = Q`.
Parameters
----------
views : DataFrame of shape (n_views, n_fields)
Constraints matrix, where n_views is the number of views
and n_fields is the number of fields of views matrix, the fields
are:
- Disabled: (bool) indicates if the constraint is enable.
- Type: (str) can be: 'Assets' or 'Classes'.
- Set: (str) if Type is 'Classes' specified the name of the set of asset classes.
- Position: (str) the name of the asset or asset class of the view.
- Sign: (str) can be '>=' or '<='.
- Return: (scalar) is the return of the view.
- Type Relative: (str) can be: 'Assets' or 'Classes'.
- Relative Set: (str) if Type Relative is 'Classes' specified the name of the set of asset classes.
- Relative: (str) the name of the asset or asset class of the relative view.
asset_classes : DataFrame of shape (n_assets, n_cols)
Asset's classes matrix, where n_assets is the number of assets and
n_cols is the number of columns of the matrix where the first column
is the asset list and the next columns are the different asset's
classes sets.
Returns
-------
P : nd-matrix
The matrix P that shows the relation among assets in each view.
Q : nd-matrix
The matrix Q that shows the expected return of each view.
Raises
------
ValueError when the value cannot be calculated.
Examples
--------
::
asset_classes = {'Assets': ['FB', 'GOOGL', 'NTFX', 'BAC', 'WFC', 'TLT', 'SHV'],
'Class 1': ['Equity', 'Equity', 'Equity', 'Equity', 'Equity',
'Fixed Income', 'Fixed Income'],
'Class 2': ['Technology', 'Technology', 'Technology',
'Financial', 'Financial', 'Treasury', 'Treasury'],}
asset_classes = pd.DataFrame(asset_classes)
views = {'Disabled': [False, False, False, False],
'Type': ['Assets', 'Classes', 'Classes', 'Assets'],
'Set': ['', 'Class 2','Class 1', ''],
'Position': ['WFC', 'Financial', 'Equity', 'FB'],
'Sign': ['<=', '>=', '>=', '>='],
'Return': [ 0.3, 0.1, 0.05, 0.03 ],
'Type Relative': [ 'Assets', 'Classes', 'Assets', ''],
'Relative Set': [ '', 'Class 1', '', ''],
'Relative': ['FB', 'Fixed Income', 'TLT', '']}
views = pd.DataFrame(views)
The constraint looks like this:
.. image:: images/Views.png
It is easier to construct the constraints in excel and then upload to a
dataframe.
To create the matrixes P and Q we use the following command:
::
P, Q = cf.assets_views(views, asset_classes)
The matrixes P and Q looks like this:
.. image:: images/PxQ.png
"""
if not isinstance(views, pd.DataFrame) and not isinstance(
asset_classes, pd.DataFrame
):
raise ValueError("constraints and asset_classes must be DataFrames")
if views.shape[1] != 9:
raise ValueError("constraints must have nine columns")
n = len(views)
m = len(asset_classes)
data = views.fillna("")
data = data.values.tolist()
assetslist = asset_classes.iloc[:, 0].values.tolist()
P = []
Q = []
for i in range(0, n):
valid = False
if data[i][0] == False:
if data[i][1] == "Assets":
item = assetslist.index(data[i][3])
if data[i][4] == ">=":
d = 1
elif data[i][4] == "<=":
d = -1
if data[i][5] != "":
P1 = [0] * m
P1[item] = 1
if data[i][6] == "Assets" and data[i][8] != "":
item2 = assetslist.index(data[i][8])
P2 = [0] * m
P2[item2] = 1
valid = True
elif (
data[i][6] == "Classes"
and data[i][7] != ""
and data[i][8] != ""
):
P2 = np.where(
asset_classes[data[i][7]].values == data[i][8], 1, 0
)
P2 = P2 / np.sum(P2)
valid = True
elif data[i][6] == "" and data[i][7] == "" and data[i][8] == "":
P2 = [0] * m
valid = True
if valid == True:
P1 = ((np.array(P1) - np.array(P2)) * d).tolist()
P.append(P1)
Q.append([data[i][5] * d])
elif data[i][1] == "Classes":
if data[i][4] == ">=":
d = 1
else:
d = -1
if data[i][5] != "":
P1 = np.where(asset_classes[data[i][2]].values == data[i][3], 1, 0)
P1 = P1 / np.sum(P1)
if data[i][6] == "Assets" and data[i][8] != "":
item2 = assetslist.index(data[i][8])
P2 = [0] * m
P2[item2] = 1
valid = True
elif (
data[i][6] == "Classes"
and data[i][7] != ""
and data[i][8] != ""
):
P2 = np.where(
asset_classes[data[i][7]].values == data[i][8], 1, 0
)
P2 = P2 / np.sum(P2)
valid = True
elif data[i][6] == "" and data[i][7] == "" and data[i][8] == "":
P2 = [0] * m
valid = True
if valid == True:
P1 = ((np.array(P1) - np.array(P2)) * d).tolist()
P.append(P1)
Q.append([data[i][5] * d])
P = np.matrix(P)
Q = np.matrix(Q)
for i in range(len(Q)):
if Q[i, 0] < 0:
P[i, :] = -1 * P[i, :]
Q[i, :] = -1 * Q[i, :]
return P, Q