-
Notifications
You must be signed in to change notification settings - Fork 0
/
delta.py
197 lines (165 loc) · 6.83 KB
/
delta.py
1
#!/usr/bin/env python3# -*- coding: utf-8 -*-"""Created on Wed Nov 4 15:13:55 2020@author: ryancrisanti"""import pandas as pdfrom .utilities import (format_name, format_money_value, month_range, save, load, check_delta_inputs)class Delta: # TODO: Add in a margin of error def __init__(self, value, dates, name, uncertainty=0, unc_fmt='+-($)'): check_delta_inputs(value, dates, name) self.value = value self.dates = dates self.name = format_name(name) self.uncertainty_pos, self.uncertainty_neg = self._uncertainty_parser( self.value, uncertainty, unc_fmt ) self._value_fmt = format_money_value(value) def __repr__(self): return f'<Delta: {self._value_fmt} {self.name}>' def __str__(self): return f'<Delta: {self._value_fmt} {self.name}>' def save(self, filepath, **kwargs): save(self, filepath, **kwargs) def _uncertainty_parser(self, value, uncertainty, fmtr='+-($)'): ''' Parameters ---------- value : number or 2-tuple/list If single number indicates uncertainty in both directions. If a 2-tuple, then indicates uncertainty in (negative, postive) direction. uncertainty : TYPE DESCRIPTION. fmtr : str, optional Indicates the uncertainty value specification meaning. There are 2 fields: 1. The first can be any of ["+-", "+", "-"] and indicates which direction the uncertainty is in. 2. The second which is in parentheses, is either "$" or "%" to indicate if the value is given in dollars or percent. The default is '+-($)'. Returns ------- None. ''' # Format the uncertainty value into a 2-tuple if isinstance(uncertainty, (int, float)): uncertainty = (uncertainty, uncertainty) elif isinstance(uncertainty, (tuple, list)) and len(uncertainty) == 2: uncertainty = tuple(uncertainty) else: raise TypeError('`uncertainty` should be either numerical or a '\ f'2-tuple/2-list, but got "{uncertainty}".') if any(u < 0 for u in uncertainty): raise ValueError('Uncertainty must be nonnegative.') # Figure out fmtr posneg, unit = fmtr.split('(') unit = unit.split(')')[0] if posneg not in ['+', '-', '+-', '-+']: raise ValueError('Expected first field to be one of [+, -, +-], '\ f'but got "{posneg}".') # Figure out units if unit == '$': pass elif unit == '%': uncertainty = tuple(abs(value) * u / 100 for u in uncertainty) else: raise ValueError('Expected second field to be one of [$, %], but'\ f' got "{unit}".') # Now figure out pos & neg unc_pos = 0 unc_neg = 0 if '+' in posneg: unc_pos = uncertainty[1] if '-' in posneg: unc_neg = uncertainty[0] return unc_neg, unc_pos class MonthlyDelta(Delta): def __init__(self, value, start_date, end_date, name, action_date=1, uncertainty=0, unc_fmt='+-($)'): self.action_date = action_date dates = self._build_dates(start_date, end_date, action_date) super().__init__( value=value, dates=dates, name=name, uncertainty=uncertainty, unc_fmt=unc_fmt ) def _build_dates(self, start_date, end_date, action_date): # Adjust Start Date if start_date.day <= action_date: start_date = start_date.replace(day=action_date) else: start_date = start_date.replace(day=action_date, month=start_date.month+1) # Adjust End Date if end_date.day >= action_date: end_date = end_date.replace(day=action_date) else: end_date = end_date.replace(day=action_date, month=end_date.month-1) # Make Range return month_range(start_date, end_date) def __repr__(self): return f'<MonthlyDelta: {self._value_fmt} {self.name}>' def __str__(self): return f'<MonthlyDelta: {self._value_fmt} {self.name}>'class WeeklyDelta(Delta): def __init__(self, value, start_date, end_date, name, action_day='Mon', uncertainty=0, unc_fmt='+-($)'): self.action_day = self._format_action_day(action_day) dates = self._build_dates(start_date, end_date, self.action_day) super().__init__( value=value, dates=dates, name=name, uncertainty=uncertainty, unc_fmt=unc_fmt ) def _build_dates(self, start_date, end_date, action_day): return pd.date_range(start=start_date, end=end_date, freq=f'W-{action_day.upper()}') def _format_action_day(self, action_day): key = { 'Mon': ['mon', 'mon.', 'monday' , 'mo', 'm'], 'Tue': ['tue', 'tue.', 'tuesday' , 'tu', 'tues', 'tues.'], 'Wed': ['wed', 'wed.', 'wednesday', 'we', 'w'], 'Thu': ['thu', 'thu.', 'thurs' , 'th', 'thurs.', 'thursday'], 'Fri': ['fri', 'fri.', 'friday' , 'fr', 'f'], 'Sat': ['sat', 'sat.', 'saturday' , 'sa'], 'Sun': ['sun', 'sun.', 'sunday' , 'su'] } for k,v in key.items(): if action_day.lower() in v: return k # If you get here, means you didn't find anything raise ValueError( f'Did not recognize `action_day` argument "{action_day}". Try '\ 'the full day name, or a common abbreviation.') def __repr__(self): return f'<WeeklyDelta: {self._value_fmt} {self.name}>' def __str__(self): return f'<WeeklyDelta: {self._value_fmt} {self.name}>' class OneTimeDelta(Delta): def __init__(self, value, date, name, uncertainty=0, unc_fmt='+-($)'): dates = self._build_dates(date) super().__init__( value=value, dates=dates, name=name, uncertainty=uncertainty, unc_fmt=unc_fmt ) def _build_dates(self, date): return pd.date_range(start=date, periods=1, freq='D') def __repr__(self): return f'<OneTimeDelta: {self._value_fmt} {self.name}>' def __str__(self): return f'<OneTimeDelta: {self._value_fmt} {self.name}>'def load_delta(filepath, **kwargs): return load(filepath, **kwargs)