Source code for aggregate.extensions.pentagon


from collections import namedtuple
from functools import cache
from itertools import combinations
import logging
import numpy as np
import pandas as pd

logger = logging.getLogger(__name__)

mapper = dict(
      L=1 << 0,
      M=1 << 1,
      P=1 << 2,
      Q=1 << 3,
      a=1 << 4,
      lr=1 << 5,
      coc=1 << 6,
      pq=1 << 7
     )

pent_ans = namedtuple('pent_ans', 'L P M a Q LR PQ COC')

# old method and / or creating the relvant dataframes
[docs]def code(r): """ determine binary code for row """ return mapper[r.x] + mapper[r.y] + mapper[r.z]
def proc(s): if len(s.intersection(set(['L','M', 'P']))) >= 2: s = s.union(set(['L', 'M', 'P', 'lr'])) if len(s.intersection(set(['P','Q', 'a']))) >= 2: s = s.union(set(['P', 'Q', 'a', 'pq'])) if len(s.intersection(set(['a', 'coc', 'L']))) == 3 or \ len(s.intersection(set(['a', 'coc', 'lr']))) == 3 or \ len(s.intersection(set(['L', 'coc', 'pq']))) == 3: s = s.union(['P', 'M']) for two_of in [set(['L', 'M', 'P','lr']), set(['pq', 'a', 'Q', 'P']), set(['M', 'Q', 'coc'])]: if len(s.intersection(two_of)) >= 2: s = s.union(two_of) return s def proc4(s): return proc(proc(proc(proc(s))))
[docs]@cache def make_possible_pentagons(): """ enumerate possible and impossible pentagon configurations """ df = pd.DataFrame(combinations(list('LMPQa') + ['lr', 'coc', 'pq'], 3), columns=['x', 'y', 'z']) df['possible'] = True for a, r in df.iterrows(): rs = set(r.iloc[:-1]) ps = proc4(rs) if len(ps) < 8: df.loc[a, 'possible'] = False df['code'] = df.apply(code, axis=1) poss = df.query('possible').copy() # notposs = df.query('not possible').copy() return poss
class Pentagon(): # class level variables index = ['L', 'P', 'M', 'a', 'Q', 'LR', 'PQ', 'COC'] def __init__(self, obj=None): """ Create Pentagon object, optionally including an :class:`Aggregate` or :class:`Portfolio` object. """ self.obj = obj self.L = None self.P = None self.M = None self.a = None self.Q = None self.LR = None self.PQ = None self.COC = None def __str__(self): return str(self.as_series()) def as_tuple(self): return pent_ans(*self.values) def as_series(self): """ return values as a pandas Series """ return pd.Series(self.values, index=self.index) def as_frame(self): """ return values as a pandas DataFrame """ return self.as_series().to_frame().T @property def values(self): """ return values as a list """ return [getattr(self, k) for k in self.index] def ratios(self): """ Add the ratios (lr, pq, coc) to a partially solved model and / or check they have the expected value """ if self.LR is None: self.LR = self.L / self.P else: assert np.allclose(self.LR, self.L / self.P, atol=1e-14, rtol=1e-14), f'{self.LR} != {self.L / self.P}' if self.PQ is None: self.PQ = self.P / self.Q else: assert np.allclose(self.PQ, self.P / self.Q, atol=1e-14, rtol=1e-14), f'{self.PQ} != {self.P / self.Q}' if self.COC is None: self.COC = self.M / self.Q else: assert np.allclose(self.COC, self.M / self.Q, atol=1e-14, rtol=1e-14), f'{self.COC} != {self.M / self.Q}' def solve_obj(self, p, *, P=None, M=None, Q=None, lr=None, pq=None, coc=None): """ Solve using the embedded option to determine a = obj.q(p) and L = obj.density_df.loc[a, 'exa_total'] Any one of the other variables can be passed in as a keyword argument. """ assert self.obj is not None and self.obj.density_df is not None, \ 'obj must be set and recomputed before calling this method' a = self.obj.q(p) if 'exa' in self.obj.density_df.columns: # Aggregate L = self.obj.density_df.loc[a, 'exa'] elif 'exa_total' in self.obj.density_df.columns: # Portfolio L = self.obj.density_df.loc[a, 'exa_total'] else: raise ValueError('obj.density_df must contain column exa or exa_total. ' 'These are provided by Aggregate and Portfolio objects.') return self.solve(L=L, P=P, M=M, a=a, Q=Q, lr=lr, pq=pq, coc=coc) def solve(self, *, L=None, P=None, M=None, a=None, Q=None, lr=None, pq=None, coc=None): """ Figure the standard variables from a subset for obj, an :class:`Aggregate` or :class:`Portfolio` object. """ # suck out values self.L = L self.P = P self.M = M self.a = a self.Q = Q self.LR = lr self.PQ = pq self.COC = coc ser = pd.Series([L, P, M, a, Q, lr, pq, coc], index=self.index) ser_in = ser[~np.isnan(ser)] match tuple(ser_in.index): case ('L', 'M', 'Q'): self.P = self.L + self.M self.a = self.P + self.Q case ('L', 'M', 'a'): self.P = self.L + self.M self.Q = self.a - self.P case ('L', 'M', 'coc'): self.P = self.L + self.M self.Q = self.M / self.COC self.a = self.P + self.Q case ('L', 'M', 'pq'): self.P = self.L + self.M self.Q = self.P / self.PQ self.a = self.P + self.Q case ('L', 'P', 'Q'): self.M = self.P - self.L self.a = self.P + self.Q case ('L', 'P', 'a'): self.M = self.P - self.L self.Q = self.a - self.P case ('L', 'P', 'coc'): self.M = self.P - self.L self.Q = self.M / self.COC self.a = self.P + self.Q case ('L', 'P', 'pq'): self.M = self.P - self.L self.Q = self.P / self.PQ self.a = self.P + self.Q case ('L', 'a', 'Q'): self.P = self.a - self.Q self.M = self.P - self.L case ('L', 'Q', 'lr'): self.P = self.L / self.LR self.M = self.P - self.L self.a = self.P + self.Q case ('L', 'Q', 'coc'): self.M = self.Q * self.COC self.P = self.L + self.M self.a = self.P + self.Q case ('L', 'Q', 'pq'): self.P = self.Q * self.PQ self.M = self.P - self.L self.a = self.P + self.Q case ('L', 'a', 'lr'): self.P = self.L / self.LR self.M = self.P - self.L self.Q = self.a - self.P case ('L', 'a', 'coc'): self.M = self.COC / (1 + self.COC) * (self.a - self.L) self.P = self.L + self.M self.Q = self.a - self.P case ('L', 'a', 'pq'): self.P = self.a * self.PQ / (1 + self.PQ) self.M = self.P - self.L self.Q = self.a - self.P case ('L', 'lr', 'coc'): self.P = self.L / self.LR self.M = self.P - self.L self.Q = self.M / self.COC self.a = self.P + self.Q case ('L', 'lr', 'pq'): self.P = self.L / self.LR self.M = self.P - self.L self.Q = self.P / self.PQ self.a = self.P + self.Q case ('L', 'pq', 'coc'): self.P = self.PQ / (self.PQ - self.COC) * self.L self.M = self.P - self.L self.Q = self.P / self.PQ self.a = self.P + self.Q case ('P', 'M', 'Q'): self.L = self.P - self.M self.a = self.P + self.Q case ('P', 'M', 'a'): self.L = self.P - self.M self.Q = self.a - self.P case ('P', 'M', 'coc'): self.L = self.P - self.M self.Q = self.M / self.COC self.a = self.P + self.Q case ('P', 'M', 'pq'): self.L = self.P - self.M self.Q = self.P / self.PQ self.a = self.P + self.Q case ('M', 'a', 'Q'): self.P = self.a - self.Q self.L = self.P - self.M self.M = self.P - self.L case ('M', 'Q', 'lr'): self.L = self.M * self.LR / (1 - self.LR) self.P = self.L + self.M self.a = self.P + self.Q case ('M', 'Q', 'pq'): self.P = self.Q * self.PQ self.L = self.P - self.M self.a = self.P + self.Q case ('M', 'a', 'lr'): self.P = self.M / (1 - self.LR) self.L = self.P - self.M self.Q = self.a - self.P case ('M', 'a', 'coc'): self.Q = self.M / self.COC self.P = self.a - self.Q self.L = self.P - self.M case ('M', 'a', 'pq'): self.P = self.a * self.PQ / (1 + self.PQ) self.L = self.P - self.M self.Q = self.a - self.P case ('M', 'lr', 'coc'): self.P = self.M / (1 - self.LR) self.L = self.P - self.M self.Q = self.M / self.COC self.a = self.P + self.Q case ('M', 'lr', 'pq'): self.P = self.M / (1 - self.LR) self.L = self.P - self.M self.Q = self.P / self.PQ self.a = self.P + self.Q case ('M', 'pq', 'coc'): self.P = self.PQ / self.COC * self.M self.L = self.P - self.M self.Q = self.P / self.PQ self.a = self.P + self.Q case ('P', 'Q', 'lr'): self.L = self.P * self.LR self.a = self.P + self.Q self.M = self.P - self.L case ('P', 'Q', 'coc'): self.M = self.Q * self.COC self.L = self.P - self.M self.a = self.P + self.Q case ('P', 'a', 'lr'): self.L = self.P * self.LR self.M = self.P - self.L self.Q = self.a - self.P case ('P', 'a', 'coc'): self.Q = self.a - self.P self.M = self.Q * self.COC self.L = self.P - self.M case ('P', 'lr', 'coc'): self.L = self.P * self.LR self.M = self.P - self.L self.Q = self.M / self.COC self.a = self.P + self.Q case ('P', 'lr', 'pq'): self.L = self.P * self.LR self.M = self.P - self.L self.Q = self.P / self.PQ self.a = self.P + self.Q case ('P', 'pq', 'coc'): self.Q = self.P / self.PQ self.a = self.P + self.Q self.M = self.Q * self.COC self.L = self.P - self.M case ('a', 'Q', 'lr'): self.P = self.a - self.Q self.L = self.P * self.LR self.M = self.P - self.L case ('a', 'Q', 'coc'): self.P = self.a - self.Q self.M = self.Q * self.COC self.L = self.P - self.M case ('Q', 'lr', 'coc'): self.M = self.Q * self.COC self.P = self.M / (1 - self.LR) self.L = self.P - self.M self.a = self.P + self.Q case ('Q', 'lr', 'pq'): self.P = self.Q * self.PQ self.L = self.P * self.LR self.M = self.P - self.L self.a = self.P + self.Q case ('Q', 'pq', 'coc'): self.P = self.Q * self.PQ self.M = self.Q * self.COC self.L = self.P - self.M self.a = self.P + self.Q case ('a', 'lr', 'coc'): self.P = self.a * self.COC / (self.COC + 1 - self.LR) self.Q = self.a - self.P self.L = self.P * self.LR self.M = self.P - self.L case ('a', 'lr', 'pq'): self.P = self.PQ * self.a / (1 + self.PQ) self.L = self.P * self.LR self.M = self.P - self.L self.Q = self.a - self.P case ('a', 'pq', 'coc'): self.P = self.PQ * self.a / (1 + self.PQ) self.Q = self.a - self.P self.M = self.Q * self.COC self.L = self.P - self.M case _: raise ValueError(f'Insoluble case: {tuple(ser_in.index)}') # fill in the ratios self.ratios() @classmethod def test_cases(cls, L, P, a): """ Run all test cases for a given set of loss, premium, and asset inputs. """ M = P - L Q = a - P lr = L / P coc = M / Q pq = P / Q consistent_pricing = pd.Series( # TODO fragile...must be in the same order as cls.index [L, P, M, a, Q, lr, pq, coc], index=cls.index ) # run through all options p = Pentagon() df = make_possible_pentagons() # add columns for each variable for c in cls.index: df[c] = None # iterate through all possible combinations for i, r in df.iterrows(): arg_dict = {k: consistent_pricing[k] for k in r.iloc[:3]} p.solve(**arg_dict) # print(p.values) df.loc[i, cls.index] = p.values return df