Skip to content

Commit

Permalink
risk tested
Browse files Browse the repository at this point in the history
  • Loading branch information
juanbc committed Apr 21, 2023
1 parent 4b271a4 commit 0e34b40
Show file tree
Hide file tree
Showing 13 changed files with 149 additions and 108 deletions.
10 changes: 5 additions & 5 deletions garpar/core/covcorr_acc.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,28 +19,28 @@ class CovarianceAccessor(accabc.AccessorABC):

def sample_cov(self, **kwargs):
return risk_models.sample_cov(
prices=self._pf._df, returns_data=False, **kwargs
prices=self._pf._prices_df, returns_data=False, **kwargs
)

def exp_cov(self, **kwargs):
return risk_models.exp_cov(
prices=self._pf._df, returns_data=False, **kwargs
prices=self._pf._prices_df, returns_data=False, **kwargs
)

def semi_cov(self, **kwargs):
return risk_models.semicovariance(
prices=self._pf._df, returns_data=False, **kwargs
prices=self._pf._prices_df, returns_data=False, **kwargs
)

def ledoit_wolf_cov(self, shrinkage_target="constant_variance", **kwargs):
covshrink = risk_models.CovarianceShrinkage(
prices=self._pf._df, returns_data=False, **kwargs
prices=self._pf._prices_df, returns_data=False, **kwargs
)
return covshrink.ledoit_wolf(shrinkage_target=shrinkage_target)

def oracle_approximating_cov(self, **kwargs):
covshrink = risk_models.CovarianceShrinkage(
prices=self._pf._df, returns_data=False, **kwargs
prices=self._pf._prices_df, returns_data=False, **kwargs
)
return covshrink.oracle_approximating()

Expand Down
6 changes: 3 additions & 3 deletions garpar/core/ereturns_acc.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,21 @@ class ExpectedReturnsAccessor(accabc.AccessorABC):

def capm(self, **kwargs):
returns = expected_returns.capm_return(
prices=self._pf._df, returns_data=False, **kwargs
prices=self._pf._prices_df, returns_data=False, **kwargs
)
returns.name = "CAPM"
return returns

def mah(self, **kwargs):
returns = expected_returns.mean_historical_return(
prices=self._pf._df, returns_data=False, **kwargs
prices=self._pf._prices_df, returns_data=False, **kwargs
)
returns.name = "MAH"
return returns

def emah(self, **kwargs):
returns = expected_returns.ema_historical_return(
prices=self._pf._df, returns_data=False, **kwargs
prices=self._pf._prices_df, returns_data=False, **kwargs
)
returns.name = "EMAH"
return returns
2 changes: 1 addition & 1 deletion garpar/core/plot_acc.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class PortfolioPlotter(accabc.AccessorABC):
def _ddf(self, returns):
if returns:
return self._pf.as_returns(), "Returns"
return self._pf._df, "Price"
return self._pf._prices_df, "Price"

def _wdf(self):
# proxy to access the dataframe with the weights
Expand Down
108 changes: 67 additions & 41 deletions garpar/core/portfolio.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def _as_float_array(arr):
# =============================================================================
@attr.s(repr=False, cmp=False)
class Portfolio:
_df = attr.ib(validator=vldt.instance_of(pd.DataFrame))
_prices_df = attr.ib(validator=vldt.instance_of(pd.DataFrame))
_weights = attr.ib(converter=_as_float_array)
_entropy = attr.ib(converter=_as_float_array)
_window_size = attr.ib(
Expand Down Expand Up @@ -119,28 +119,41 @@ def __attrs_post_init__(self):
"be the same as number of stocks"
)

self._df.columns.name = "Stocks"
self._df.index.name = "Days"
self._prices_df.columns.name = "Stocks"
self._prices_df.index.name = "Days"

# ALTERNATIVE CONSTRUCTOR
@classmethod
def from_dfkws(
cls, df, weights=None, entropy=None, window_size=None, **metadata
cls,
prices,
weights=None,
entropy=None,
window_size=None,
stocks=None,
**metadata,
):
prices = df.copy()
prices = (
prices.copy()
if isinstance(prices, pd.DataFrame)
else pd.DataFrame(prices)
)

cols = len(prices.columns)
stocks_number = len(prices.columns)

if weights is None or not hasattr(weights, "__iter__"):
weights = 1.0 if weights is None else weights
weights = np.full(cols, weights)
weights = np.full(stocks_number, weights)

if entropy is None or not hasattr(entropy, "__iter__"):
entropy = np.nan if entropy is None else entropy
entropy = np.full(cols, entropy)
entropy = np.full(stocks_number, entropy)

if stocks is not None:
prices.columns = stocks

pf = cls(
df=prices,
prices_df=prices,
weights=weights,
entropy=entropy,
window_size=window_size,
Expand All @@ -151,12 +164,12 @@ def from_dfkws(

# INTERNALS
def __len__(self):
return len(self._df)
return len(self._prices_df)

def __eq__(self, other):
return (
isinstance(other, type(self))
and self._df.equals(other._df)
and self._prices_df.equals(other._prices_df)
and np.allclose(self._weights, other._weights, equal_nan=True)
and np.allclose(self._entropy, other._entropy, equal_nan=True)
and self._window_size == other._window_size
Expand All @@ -167,45 +180,50 @@ def __ne__(self, other):
return not self == other

def __getitem__(self, key):
df = self._df.__getitem__(key)
if isinstance(df, pd.Series):
df = df.to_frame()
prices = self._prices_df.__getitem__(key)
if isinstance(prices, pd.Series):
prices = prices.to_frame()

weights = self.weights
weights = weights[weights.index.isin(df.columns)].to_numpy()
weights = weights[weights.index.isin(prices.columns)].to_numpy()

entropy = self.entropy
entropy = entropy[entropy.index.isin(df.columns)].to_numpy()
entropy = entropy[entropy.index.isin(prices.columns)].to_numpy()

window_size = self.window_size
metadata = dict(self.metadata)

sliced = Portfolio.from_dfkws(
df=df,
cls = type(self)
sliced = cls(
prices_df=prices,
weights=weights,
entropy=entropy,
window_size=window_size,
**metadata,
metadata=metadata,
)

return sliced

# PROPERTIES ==============================================================
@property
def weights(self):
return pd.Series(self._weights, index=self._df.columns, name="Weights")
return pd.Series(
self._weights, index=self._prices_df.columns, name="Weights"
)

@property
def entropy(self):
return pd.Series(self._entropy, index=self._df.columns, name="Entropy")
return pd.Series(
self._entropy, index=self._prices_df.columns, name="Entropy"
)

@property
def stocks(self):
return self._df.columns.to_numpy()
return self._prices_df.columns.to_numpy()

@property
def stocks_number(self):
return len(self._df.columns)
return len(self._prices_df.columns)

@property
def metadata(self):
Expand All @@ -217,25 +235,28 @@ def window_size(self):

@property
def delisted(self):
dlstd = (self._df == 0.0).any(axis="rows")
dlstd = (self._prices_df == 0.0).any(axis="rows")
dlstd.name = "Delisted"
return dlstd

@property
def shape(self):
return self._df.shape
return self._prices_df.shape

# UTILS ===================================================================
def copy(
self,
df=None,
prices=None,
weights=None,
entropy=None,
window_size=None,
stocks=None,
preserve_old_metadata=True,
**metadata,
):
new_prices_df = (self._df if df is None else df).copy(deep=True)
new_prices_df = (self._prices_df if prices is None else prices).copy(
deep=True
)
new_weights = (self._weights if weights is None else weights).copy()
new_entropy = (self._entropy if entropy is None else entropy).copy()
new_window_size = (
Expand All @@ -247,12 +268,13 @@ def copy(
)
new_metadata.update(metadata)

new_pf = Portfolio(
new_pf = self.from_dfkws(
new_prices_df,
weights=new_weights,
entropy=new_entropy,
window_size=new_window_size,
metadata=new_metadata,
stocks=stocks,
**new_metadata,
)

return new_pf
Expand All @@ -263,7 +285,7 @@ def to_hdf5(self, stream_or_buff, **kwargs):
return io.to_hdf5(stream_or_buff, self, **kwargs)

def to_dataframe(self):
price_df = self._df.copy(deep=True)
price_df = self._prices_df.copy(deep=True)

# transform the weighs "series" into a compatible dataframe
weights_df = self.weights.to_frame().T
Expand All @@ -289,11 +311,11 @@ def to_dataframe(self):

def as_returns(self, **kwargs):
return pypfopt.expected_returns.returns_from_prices(
prices=self._df, **kwargs
prices=self._prices_df, **kwargs
)

def as_prices(self):
return self._df.copy()
return self._prices_df.copy()

# PRUNNING ================================================================

Expand All @@ -316,12 +338,14 @@ def weights_prune(self, threshold=1e-4):
pruned_entropy = entropy[mask].to_numpy()

# pruned pf
pruned_pf = self.from_dfkws(
pruned_prices,
cls = type(self)

pruned_pf = cls(
prices_df=pruned_prices,
weights=pruned_weights,
entropy=pruned_entropy,
window_size=window_size,
**metadata,
metadata=metadata,
)

return pruned_pf
Expand All @@ -346,12 +370,14 @@ def delisted_prune(self):
pruned_entropy = entropy[mask].to_numpy()

# pruned pf
pruned_pf = self.from_dfkws(
pruned_prices,
cls = type(self)

pruned_pf = cls(
prices_df=pruned_prices,
weights=pruned_weights,
entropy=pruned_entropy,
window_size=window_size,
**metadata,
metadata=metadata,
)

return pruned_pf
Expand Down Expand Up @@ -401,7 +427,7 @@ def _get_sw_headers(self):
headers = []
fmt_weights = self._pd_fmt_serie(self.weights)
fmt_entropy = self._pd_fmt_serie(self.entropy)
for c, w, h in zip(self._df.columns, fmt_weights, fmt_entropy):
for c, w, h in zip(self._prices_df.columns, fmt_weights, fmt_entropy):
header = f"{c}[W{w}, H{h}]"
headers.append(header)
return headers
Expand All @@ -417,7 +443,7 @@ def __repr__(self):
header = self._get_sw_headers()
dimensions = self._get_dxs_dimensions()

with df_temporal_header(self._df, header) as df:
with df_temporal_header(self._prices_df, header) as df:
with pd.option_context("display.show_dimensions", False):
original_string = repr(df)

Expand All @@ -435,7 +461,7 @@ def _repr_html_(self):
dimensions = self._get_dxs_dimensions()

# retrieve the original string
with df_temporal_header(self._df, header) as df:
with df_temporal_header(self._prices_df, header) as df:
with pd.option_context("display.show_dimensions", False):
original_html = df._repr_html_()

Expand Down
12 changes: 6 additions & 6 deletions garpar/core/prices_acc.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,23 +45,23 @@ class PricesAccessor(accabc.AccessorABC):
def __getattr__(self, a):
if a not in self._WHITELIST:
raise AttributeError(a)
target = self if a in self._GARPAR_WHITELIST else self._pf._df
target = self if a in self._GARPAR_WHITELIST else self._pf._prices_df

return getattr(target, a)

def __dir__(self):
return super().__dir__() + [
e for e in dir(self._pf._df) if e in self._WHITELIST
e for e in dir(self._pf._prices_df) if e in self._WHITELIST
]

def log(self):
return self._pf._df.apply(np.log)
return self._pf._prices_df.apply(np.log)

def log10(self):
return self._pf._df.apply(np.log10)
return self._pf._prices_df.apply(np.log10)

def log2(self):
return self._pf._df.apply(np.log2)
return self._pf._prices_df.apply(np.log2)

def mad(self, skipna=True):
"""Return the mean absolute deviation of the values over a given axis.
Expand All @@ -72,5 +72,5 @@ def mad(self, skipna=True):
Exclude NA/null values when computing the result.
"""
df = self._pf._df
df = self._pf._prices_df
return (df - df.mean(axis=0)).abs().mean(axis=0, skipna=skipna)
Loading

0 comments on commit 0e34b40

Please sign in to comment.