From da96be4f0571ea167f6c03701ef87aca943a2433 Mon Sep 17 00:00:00 2001 From: M-BSquared Date: Mon, 5 Aug 2024 16:59:52 +0200 Subject: [PATCH 1/6] - Added epidemiological models: SI, SIS, and SIRS - All models now support selection between Mass Action and Standard Incidence --- .../gs_quant.models.epidemiology.SI.rst | 24 ++ .../gs_quant.models.epidemiology.SIRS.rst | 24 ++ .../gs_quant.models.epidemiology.SIS.rst | 24 ++ ...gs_quant.timeseries.statistics.SIModel.rst | 23 ++ ..._quant.timeseries.statistics.SIRSModel.rst | 31 ++ ...s_quant.timeseries.statistics.SISModel.rst | 25 ++ docs/models.rst | 3 + docs/module/models.rst | 3 + docs/timeseries.rst | 1 + gs_quant/models/epidemiology.py | 271 +++++++++++++++- gs_quant/test/models/test_epidemiology.py | 291 ++++++++++++++---- gs_quant/test/timeseries/test_statistics.py | 78 +++++ gs_quant/timeseries/statistics.py | 150 ++++++++- 13 files changed, 876 insertions(+), 72 deletions(-) create mode 100644 docs/classes/gs_quant.models.epidemiology.SI.rst create mode 100644 docs/classes/gs_quant.models.epidemiology.SIRS.rst create mode 100644 docs/classes/gs_quant.models.epidemiology.SIS.rst create mode 100644 docs/classes/gs_quant.timeseries.statistics.SIModel.rst create mode 100644 docs/classes/gs_quant.timeseries.statistics.SIRSModel.rst create mode 100644 docs/classes/gs_quant.timeseries.statistics.SISModel.rst diff --git a/docs/classes/gs_quant.models.epidemiology.SI.rst b/docs/classes/gs_quant.models.epidemiology.SI.rst new file mode 100644 index 00000000..1f1abc04 --- /dev/null +++ b/docs/classes/gs_quant.models.epidemiology.SI.rst @@ -0,0 +1,24 @@ +gs\_quant.models.epidemiology.SI +================================= + +.. currentmodule:: gs_quant.models.epidemiology + +.. autoclass:: SI + + + .. automethod:: __init__ + + + .. rubric:: Methods + + .. autosummary:: + + ~SI.__init__ + ~SI.calibrate + ~SI.get_parameters + + + + + + \ No newline at end of file diff --git a/docs/classes/gs_quant.models.epidemiology.SIRS.rst b/docs/classes/gs_quant.models.epidemiology.SIRS.rst new file mode 100644 index 00000000..76626221 --- /dev/null +++ b/docs/classes/gs_quant.models.epidemiology.SIRS.rst @@ -0,0 +1,24 @@ +gs\_quant.models.epidemiology.SIRS +================================= + +.. currentmodule:: gs_quant.models.epidemiology + +.. autoclass:: SIRS + + + .. automethod:: __init__ + + + .. rubric:: Methods + + .. autosummary:: + + ~SIRS.__init__ + ~SIRS.calibrate + ~SIRS.get_parameters + + + + + + \ No newline at end of file diff --git a/docs/classes/gs_quant.models.epidemiology.SIS.rst b/docs/classes/gs_quant.models.epidemiology.SIS.rst new file mode 100644 index 00000000..192d3323 --- /dev/null +++ b/docs/classes/gs_quant.models.epidemiology.SIS.rst @@ -0,0 +1,24 @@ +gs\_quant.models.epidemiology.SIS +================================= + +.. currentmodule:: gs_quant.models.epidemiology + +.. autoclass:: SIS + + + .. automethod:: __init__ + + + .. rubric:: Methods + + .. autosummary:: + + ~SIS.__init__ + ~SIS.calibrate + ~SIS.get_parameters + + + + + + \ No newline at end of file diff --git a/docs/classes/gs_quant.timeseries.statistics.SIModel.rst b/docs/classes/gs_quant.timeseries.statistics.SIModel.rst new file mode 100644 index 00000000..d3830b11 --- /dev/null +++ b/docs/classes/gs_quant.timeseries.statistics.SIModel.rst @@ -0,0 +1,23 @@ +gs\_quant.timeseries.statistics.SIModel +======================================== + +.. currentmodule:: gs_quant.timeseries.statistics + +.. autoclass:: SIModel + + + + .. rubric:: Methods + .. autosummary:: + + ~SIModel.beta + + ~SIModel.i0 + + ~SIModel.predict_i + + ~SIModel.predict_s + + ~SIModel.s0 + + \ No newline at end of file diff --git a/docs/classes/gs_quant.timeseries.statistics.SIRSModel.rst b/docs/classes/gs_quant.timeseries.statistics.SIRSModel.rst new file mode 100644 index 00000000..8a7073e7 --- /dev/null +++ b/docs/classes/gs_quant.timeseries.statistics.SIRSModel.rst @@ -0,0 +1,31 @@ +gs\_quant.timeseries.statistics.SIRSModel +======================================== + +.. currentmodule:: gs_quant.timeseries.statistics + +.. autoclass:: SIRSModel + + + + .. rubric:: Methods + .. autosummary:: + + ~SIRSModel.beta + + ~SIRSModel.gamma + + ~SIRSModel.delta + + ~SIRSModel.i0 + + ~SIRSModel.predict_i + + ~SIRSModel.predict_r + + ~SIRSModel.predict_s + + ~SIRSModel.r0 + + ~SIRSModel.s0 + + \ No newline at end of file diff --git a/docs/classes/gs_quant.timeseries.statistics.SISModel.rst b/docs/classes/gs_quant.timeseries.statistics.SISModel.rst new file mode 100644 index 00000000..df924e8b --- /dev/null +++ b/docs/classes/gs_quant.timeseries.statistics.SISModel.rst @@ -0,0 +1,25 @@ +gs\_quant.timeseries.statistics.SISModel +======================================== + +.. currentmodule:: gs_quant.timeseries.statistics + +.. autoclass:: SISModel + + + + .. rubric:: Methods + .. autosummary:: + + ~SISModel.beta + + ~SISModel.delta + + ~SISModel.i0 + + ~SISModel.predict_i + + ~SISModel.predict_s + + ~SISModel.s0 + + \ No newline at end of file diff --git a/docs/models.rst b/docs/models.rst index a5ac550e..29a3f921 100644 --- a/docs/models.rst +++ b/docs/models.rst @@ -10,7 +10,10 @@ Epidemiology .. autosummary:: :toctree: classes + SI + SIS SIR + SIRS SEIR EpidemicModel diff --git a/docs/module/models.rst b/docs/module/models.rst index 5299ee1e..3d5b5119 100644 --- a/docs/module/models.rst +++ b/docs/module/models.rst @@ -10,7 +10,10 @@ Epidemiology .. autosummary:: :toctree: classes + SI + SIS SIR + SIRS SEIR EpidemicModel diff --git a/docs/timeseries.rst b/docs/timeseries.rst index 0e9e5fb0..a09461da 100644 --- a/docs/timeseries.rst +++ b/docs/timeseries.rst @@ -149,6 +149,7 @@ Statistics LinearRegression RollingLinearRegression + SIModel SIRModel SEIRModel diff --git a/gs_quant/models/epidemiology.py b/gs_quant/models/epidemiology.py index 44da6cd5..03d29509 100644 --- a/gs_quant/models/epidemiology.py +++ b/gs_quant/models/epidemiology.py @@ -39,18 +39,150 @@ def calibrate(cls, xs, t, parameters) -> tuple: def get_parameters(cls, *args, **kwargs) -> tuple: raise NotImplementedError +class SI(CompartmentalModel): + """SI Model""" + + @classmethod + def calibrate(cls, xs: tuple, t: float, parameters: Union[Parameters, tuple], incidence_type: str = 'mass_action') -> tuple: + """ + SI model derivatives at t. + + :param xs: variables that we are solving for, i.e. [S]usceptible, [I]nfected + :param t: time parameter, inactive for this model + :param parameters: parameters of the model (not including initial conditions), i.e. beta, N + :param incidence_type: type of incidence ('standard' or 'mass_action') + :return: tuple, the derivatives dSdt, dIdt of each of the S, I variables + """ + s, i = xs + + if isinstance(parameters, Parameters): + beta = parameters['beta'].value + N = parameters['N'].value + elif isinstance(parameters, tuple): + beta, N = parameters + else: + raise ValueError("Cannot recognize parameter input") + + if incidence_type == 'mass_action': + dSdt = - beta * s * i / N + dIdt = beta * s * i / N + elif incidence_type == 'standard': + dSdt = - beta * s * i + dIdt = beta * s * i + else: + raise ValueError("Invalid incidence type. Choose 'mass_action' or 'standard'.") + + return dSdt, dIdt + + @classmethod + def get_parameters(cls, S0: float, I0: float, N: float, beta: float = 0.2, + beta_max: float = 10, S0_fixed: bool = True, S0_max: float = 1e6, + beta_fixed: bool = False,I0_fixed: bool = True, I0_max: float = 1e6) \ + -> tuple: + """ + Produce a set of parameters for the SI model. + + :param S0: initial number of susceptible in the population + :param I0: initial number of infected in the population, usually set to 1 + :param N: size of the population + :param beta: transmission rate parameter + :param beta_max: maximum value to consider for beta during parameter fitting + :param S0_fixed: whether to keep S0 fixed during fitting + :param S0_max: maximum value of S0 to consider during parameter fitting + :param I0_fixed: whether to keep I0 fixed during fitting + :param I0_max: maximum value of I0 to consider during parameter fitting + :return: tuple[Parameters, list]: (parameters, a list of the names of the variables for initial conditions) + """ + parameters = Parameters() + parameters.add('N', value=N, min=0, max=N, vary=False) + parameters.add('S0', value=S0, min=0, max=S0_max, vary=not S0_fixed) + parameters.add('I0', value=I0, min=0, max=I0_max, vary=not I0_fixed) + parameters.add('beta', value=beta, min=0, max=beta_max, vary=not beta_fixed) + initial_conditions = ['S0', 'I0'] + + return parameters, initial_conditions + +class SIS(CompartmentalModel): + """SIS Model""" + + @classmethod + def calibrate(cls, xs: tuple, t: float, parameters: Union[Parameters, tuple], incidence_type: str = 'mass_action') -> tuple: + """ + SIS model derivatives at t. + + :param xs: variables that we are solving for, i.e. [S]usceptible, [I]nfected + :param t: time parameter, inactive for this model + :param parameters: parameters of the model (not including initial conditions), i.e. beta, delta, N + :param incidence_type: type of incidence ('standard' or 'mass_action') + :return: tuple, the derivatives dSdt, dIdt of each of the S, I variables + """ + s, i = xs + + if isinstance(parameters, Parameters): + beta = parameters['beta'].value + delta = parameters['delta'].value + N = parameters['N'].value + elif isinstance(parameters, tuple): + beta, delta , N = parameters + else: + raise ValueError("Cannot recognize parameter input") + + if incidence_type == 'mass_action': + dSdt = - beta * s * i / N + delta * i + dIdt = beta * s * i / N - delta * i + elif incidence_type == 'standard': + dSdt = - beta * s * i + delta * i + dIdt = beta * s * i - delta * i + else: + raise ValueError("Invalid incidence type. Choose 'mass_action' or 'standard'.") + + return dSdt, dIdt + + @classmethod + def get_parameters(cls, S0: float, I0: float, N: float, beta: float = 0.2, delta: float = 0.1, + beta_max: float = 10, delta_max: float = 1, S0_fixed: bool = True, S0_max: float = 1e6, + beta_fixed: bool = False, delta_fixed: bool = False, + I0_fixed: bool = True, I0_max: float = 1e6) \ + -> tuple: + """ + Produce a set of parameters for the SI model. + + :param S0: initial number of susceptible in the population + :param I0: initial number of infected in the population, usually set to 1 + :param N: size of the population + :param beta: transmission rate parameter + :param beta_max: maximum value to consider for beta during parameter fitting + :param delta: loss of imunity rate parameter (recovered individuals return to the susceptible statue due to loss of immunity) + :param beta_max: maximum value to consider for beta during parameter fitting + :param S0_fixed: whether to keep S0 fixed during fitting + :param S0_max: maximum value of S0 to consider during parameter fitting + :param I0_fixed: whether to keep I0 fixed during fitting + :param I0_max: maximum value of I0 to consider during parameter fitting + :return: tuple[Parameters, list]: (parameters, a list of the names of the variables for initial conditions) + """ + parameters = Parameters() + parameters.add('N', value=N, min=0, max=N, vary=False) + parameters.add('S0', value=S0, min=0, max=S0_max, vary=not S0_fixed) + parameters.add('I0', value=I0, min=0, max=I0_max, vary=not I0_fixed) + parameters.add('beta', value=beta, min=0, max=beta_max, vary=not beta_fixed) + parameters.add('delta', value=delta, min=0, max=delta_max, vary=not delta_fixed) + initial_conditions = ['S0', 'I0'] + + return parameters, initial_conditions + class SIR(CompartmentalModel): """SIR Model""" @classmethod - def calibrate(cls, xs: tuple, t: float, parameters: Union[Parameters, tuple]) -> tuple: + def calibrate(cls, xs: tuple, t: float, parameters: Union[Parameters, tuple], incidence_type: str = 'mass_action') -> tuple: """ SIR model derivatives at t. :param xs: variables that we are solving for, i.e. [S]usceptible, [I]nfected, [R]emoved :param t: time parameter, inactive for this model :param parameters: parameters of the model (not including initial conditions), i.e. beta, gamma, N + :param incidence_type: type of incidence ('standard' or 'mass_action') :return: tuple, the derivatives dSdt, dIdt, dRdt of each of the S, I, R variables """ s, i, r = xs @@ -64,8 +196,15 @@ def calibrate(cls, xs: tuple, t: float, parameters: Union[Parameters, tuple]) -> else: raise ValueError("Cannot recognize parameter input") - dSdt = - beta * s * i / N - dIdt = beta * s * i / N - gamma * i + if incidence_type == 'mass_action': + dSdt = - beta * s * i / N + dIdt = beta * s * i / N - gamma * i + elif incidence_type == 'standard': + dSdt = - beta * s * i + dIdt = beta * s * i - gamma * i + else: + raise ValueError("Invalid incidence type. Choose 'mass_action' or 'standard'.") + dRdt = gamma * i return dSdt, dIdt, dRdt @@ -105,13 +244,92 @@ def get_parameters(cls, S0: float, I0: float, R0: float, N: float, beta: float = initial_conditions = ['S0', 'I0', 'R0'] return parameters, initial_conditions + +class SIRS(CompartmentalModel): + """SIRS Model""" + + @classmethod + def calibrate(cls, xs: tuple, t: float, parameters: Union[Parameters, tuple], incidence_type: str = 'mass_action') -> tuple: + """ + SIRS model derivatives at t. + + :param xs: variables that we are solving for, i.e. [S]usceptible, [I]nfected, [R]emoved + :param t: time parameter, inactive for this model + :param parameters: parameters of the model (not including initial conditions), i.e. beta, gamma, delta, N + :param incidence_type: type of incidence ('standard' or 'mass_action') + :return: tuple, the derivatives dSdt, dIdt, dRdt of each of the S, I, R variables + """ + s, i, r = xs + + if isinstance(parameters, Parameters): + beta = parameters['beta'].value + gamma = parameters['gamma'].value + delta = parameters['gamma'].value + N = parameters['N'].value + elif isinstance(parameters, tuple): + beta, gamma, delta, N = parameters + else: + raise ValueError("Cannot recognize parameter input") + + if incidence_type == 'mass_action': + dSdt = - beta * s * i / N + delta * r + dIdt = beta * s * i / N - gamma * i + elif incidence_type == 'standard': + dSdt = - beta * s * i + delta * r + dIdt = beta * s * i - gamma * i + else: + raise ValueError("Invalid incidence type. Choose 'mass_action' or 'standard'.") + + dRdt = gamma * i - delta * r + + return dSdt, dIdt, dRdt + + @classmethod + def get_parameters(cls, S0: float, I0: float, R0: float, N: float, beta: float = 0.2, gamma: float = 0.1, delta: float = 0.1, + beta_max: float = 10, gamma_max: float = 1, delta_max: float = 1, S0_fixed: bool = True, S0_max: float = 1e6, + beta_fixed: bool = False, gamma_fixed: bool = False, delta_fixed: bool = False, + R0_fixed: bool = True, R0_max: float = 1e6, I0_fixed: bool = True, I0_max: float = 1e6) \ + -> tuple: + """ + Produce a set of parameters for the SIR model. + + :param S0: initial number of susceptible in the population + :param I0: initial number of infected in the population, usually set to 1 + :param R0: initial number of recovered/removed in the population, usually set to 0 + :param N: size of the population + :param beta: transmission rate parameter + :param gamma: recovery rate parameter + :param delta: loss of imunity rate parameter (recovered individuals return to the susceptible statue due to loss of immunity) + :param beta_max: maximum value to consider for beta during parameter fitting + :param gamma_max: maximum value of gamma to consider during parameter fitting + :param delta_max: maximum value of delta to consider during parameter fitting + :param S0_fixed: whether to keep S0 fixed during fitting + :param S0_max: maximum value of S0 to consider during parameter fitting + :param R0_fixed: whether to keep R0 fixed during fitting + :param R0_max: maximum value of R0 to consider during parameter fitting + :param I0_fixed: whether to keep I0 fixed during fitting + :param I0_max: maximum value of I0 to consider during parameter fitting + :return: tuple[Parameters, list]: (parameters, a list of the names of the variables for initial conditions) + """ + parameters = Parameters() + parameters.add('N', value=N, min=0, max=N, vary=False) + parameters.add('S0', value=S0, min=0, max=S0_max, vary=not S0_fixed) + parameters.add('I0', value=I0, min=0, max=I0_max, vary=not I0_fixed) + parameters.add('R0', value=R0, min=0, max=R0_max, vary=not R0_fixed) + parameters.add('beta', value=beta, min=0, max=beta_max, vary=not beta_fixed) + parameters.add('gamma', value=gamma, min=0, max=gamma_max, vary=not gamma_fixed) + parameters.add('delta', value=delta, min=0, max=delta_max, vary=not delta_fixed) + initial_conditions = ['S0', 'I0', 'R0'] + + return parameters, initial_conditions + class SEIR(CompartmentalModel): """SEIR Model""" @classmethod - def calibrate(cls, xs: tuple, t: float, parameters: Union[Parameters, tuple]) -> tuple: + def calibrate(cls, xs: tuple, t: float, parameters: Union[Parameters, tuple], incidence_type: str = 'mass_action') -> tuple: """ SEIR model derivatives at t. @@ -131,9 +349,17 @@ def calibrate(cls, xs: tuple, t: float, parameters: Union[Parameters, tuple]) -> beta, gamma, sigma, N = parameters else: raise ValueError("Cannot recognize parameter input") + + + if incidence_type == 'mass_action': + dSdt = - beta * s * i / N + dEdt = beta * s * i / N - sigma * e + elif incidence_type == 'standard': + dSdt = - beta * s * i + dEdt = beta * s * i - sigma * e + else: + raise ValueError("Invalid incidence type. Choose 'mass_action' or 'standard'.") - dSdt = -beta * s * i / N - dEdt = beta * s * i / N - sigma * e dIdt = sigma * e - gamma * i dRdt = gamma * i @@ -214,7 +440,7 @@ class SEIRCM(CompartmentalModel): with cumulative cases (C) and cumulative fatalities (M) """ @classmethod - def calibrate(cls, xs: tuple, t: float, parameters: Union[Parameters, tuple]) -> tuple: + def calibrate(cls, xs: tuple, t: float, parameters: Union[Parameters, tuple], incidence_type: str = 'mass_action') -> tuple: """ SEIRCM model derivatives at t. @@ -244,9 +470,17 @@ def calibrate(cls, xs: tuple, t: float, parameters: Union[Parameters, tuple]) -> # if T_quarantine is 0, we are not considering effect of quarantine policy so scale factor is fixed at 1 quarantine_factor = switch(t, T_quarantine, eta=eta) if T_quarantine else 1 + + + if incidence_type == 'mass_action': + dSdt = -(quarantine_factor * beta) * s * i / N # susceptible -> exposed + dEdt = (quarantine_factor * beta) * s * i / N - sigma * e # exposed -> infected AND recorded cases + elif incidence_type == 'standard': + dSdt = -(quarantine_factor * beta) * s * i # susceptible -> exposed + dEdt = (quarantine_factor * beta) * s * i - sigma * e # exposed -> infected AND recorded cases + else: + raise ValueError("Invalid incidence type. Choose 'mass_action' or 'standard'.") - dSdt = -(quarantine_factor * beta) * s * i / N # susceptible -> exposed - dEdt = (quarantine_factor * beta) * s * i / N - sigma * e # exposed -> infected AND recorded cases dIdt = sigma * e - gamma * i # infected -> removed (recovered AND dead) dRdt = (1 - epsilon) * gamma * i # recovered (from removed) dCdt = sigma * e # recorded cases (from infected) @@ -332,7 +566,7 @@ class SEIRCMAgeStratified(CompartmentalModel): """ Age-structured SEIRCM Model from https://www.medrxiv.org/content/10.1101/2020.03.04.20031104v1.full.pdf """ @classmethod - def calibrate(cls, y: list, t: float, parameters: Parameters) -> list: + def calibrate(cls, y: list, t: float, parameters: Parameters, incidence_type: str = 'mass_action') -> list: """ SEIR model derivatives at t. @@ -365,8 +599,15 @@ def epsilon(k): # case fatality rate per age group quarantine_factor = switch(t, T_quarantine, eta=eta) if T_quarantine else 1 for k in range(K): - dydt[k] = -(quarantine_factor * beta) * s(k) * I_total / N # susceptible -> exposed - dydt[K + k] = (quarantine_factor * beta) * s(k) * I_total / N - sigma * e(k) # exposed -> infected + + if incidence_type == 'mass_action': + dydt[k] = -(quarantine_factor * beta) * s(k) * I_total / N # susceptible -> exposed + dydt[K + k] = (quarantine_factor * beta) * s(k) * I_total / N - sigma * e(k) # exposed -> infected + elif incidence_type == 'standard': + dydt[k] = -(quarantine_factor * beta) * s(k) * I_total # susceptible -> exposed + dydt[K + k] = (quarantine_factor * beta) * s(k) * I_total - sigma * e(k) # exposed -> infected + else: + raise ValueError("Invalid incidence type. Choose 'mass_action' or 'standard'.") dydt[2 * K + k] = sigma * e(k) - gamma * i(k) # infected -> removed dydt[3 * K + k] = (1 - epsilon(k)) * gamma * i(k) # -> cumulative recovered (from removed) dydt[4 * K + k] = sigma * e(k) # -> cumulative recorded cases (from infected) @@ -458,7 +699,7 @@ class EpidemicModel: def __init__(self, model: Type[CompartmentalModel], parameters: tuple = None, data: np.array = None, initial_conditions: list = None, fit_method: str = 'leastsq', error: callable = None, - fit_period: float = None): + fit_period: float = None, incidence_type: str = 'mass_action'): """ A class to standardize fitting and solving epidemiological models. @@ -470,6 +711,7 @@ def __init__(self, model: Type[CompartmentalModel], parameters: tuple = None, da lmfit.minimizer.minimize function. Default is Levenberg-Marquardt least squares minimization. :param error: callable, control which residuals (and in what form) to minimize for fitting. :param fit_period: float, how far back to fit the data, defaults to fitting all data + incidence_type: str, the type of incidence ('mass_action' or 'standard') """ self.model = model self.parameters = parameters @@ -480,6 +722,7 @@ def __init__(self, model: Type[CompartmentalModel], parameters: tuple = None, da self.fit_period = fit_period self.result = None self.fitted_parameters = None + self.incidence_type = incidence_type def solve(self, time_range: np.ndarray, initial_conditions: Union[list, tuple], parameters) -> np.ndarray: """ @@ -490,7 +733,7 @@ def solve(self, time_range: np.ndarray, initial_conditions: Union[list, tuple], :param parameters: the parameters for the solution :return: """ - x = odeint(self.model.calibrate, initial_conditions, time_range, args=(parameters,)) + x = odeint(self.model.calibrate, initial_conditions, time_range, args=(parameters, self.incidence_type)) x = np.array(x) return x diff --git a/gs_quant/test/models/test_epidemiology.py b/gs_quant/test/models/test_epidemiology.py index 8d1ab0ef..40037b03 100644 --- a/gs_quant/test/models/test_epidemiology.py +++ b/gs_quant/test/models/test_epidemiology.py @@ -16,69 +16,252 @@ from gs_quant.models.epidemiology import * +def test_SI(): + for incidence in ['mass_action', 'standard']: + # Initialize a model + beta_0 = 0.1 + parameters, initial_conditions = SI.get_parameters(99, 1, 100, + beta=beta_0, + beta_max=10, + S0_fixed=True, + I0_fixed=False, + S0_max=10e6, + I0_max=5e6) + si = EpidemicModel(SI, parameters=parameters, initial_conditions=initial_conditions, incidence_type=incidence) + + # Solve a problem with an independently checkable solution + N = 100 + S0 = 99 + I0 = 1 + beta = 0.5 + days_ahead = 40 # days ahead to look at + T = np.arange(days_ahead) + forecast = si.solve(T, (S0, I0), (beta, N)) + + # Get the curve data as if it were our data + S_data = forecast[:, 0] + I_data = forecast[:, 1] + data = np.array([S_data, I_data]).T + + # Slightly perturbed parameters, to check if the fitting finds the original (optimal) parameters + beta_perturbed = beta + 0.4 + parameters, initial_conditions = SI.get_parameters(S_data[0], I_data[0], N, beta=beta_perturbed, + beta_max=10, + S0_fixed=True, + I0_fixed=True, + S0_max=10e6, + I0_max=5e6) + si = EpidemicModel(SI, parameters=parameters, data=data, + initial_conditions=initial_conditions, incidence_type=incidence) + si.fit() + + # Check fitted parameters + beta_fitted = si.fitted_parameters['beta'] + + assert np.isclose(beta, beta_fitted, rtol=5e-1) + + si = EpidemicModel(SI, parameters=parameters, data=data, initial_conditions=initial_conditions, fit_period=10, incidence_type=incidence) + si.fit() + + # Check fitted parameters again + beta_fitted = si.fitted_parameters['beta'] + + assert np.isclose(beta, beta_fitted,rtol=5e-1) + +def test_SIS(): + for incidence in ['mass_action', 'standard']: + # Initialize a model + beta_0 = 0.1 + delta_0 = 0.1 + parameters, initial_conditions = SIS.get_parameters(99, 1, 100, + beta=beta_0, + beta_max=10, + delta=delta_0, + delta_max=10, + S0_fixed=True, + I0_fixed=False, + S0_max=10e6, + I0_max=5e6) + sis = EpidemicModel(SIS, parameters=parameters, initial_conditions=initial_conditions, incidence_type=incidence) + + # Solve a problem with an independently checkable solution + N = 100 + S0 = 99 + I0 = 1 + beta = 0.5 + delta = 0.4 + days_ahead = 40 # days ahead to look at + T = np.arange(days_ahead) + forecast = sis.solve(T, (S0, I0), (beta,delta, N)) + + # Get the curve data as if it were our data + S_data = forecast[:, 0] + I_data = forecast[:, 1] + data = np.array([S_data, I_data]).T + + # Slightly perturbed parameters, to check if the fitting finds the original (optimal) parameters + beta_perturbed = beta + 0.4 + delta_perturbed = delta + 0.3 + parameters, initial_conditions = SIS.get_parameters(S_data[0], I_data[0], N, beta=beta_perturbed, + beta_max=10,delta=delta_perturbed, delta_max=10, + S0_fixed=True, + I0_fixed=True, + S0_max=10e6, + I0_max=5e6) + sis = EpidemicModel(SIS, parameters=parameters, data=data, + initial_conditions=initial_conditions, incidence_type=incidence) + sis.fit() + + # Check fitted parameters + beta_fitted = sis.fitted_parameters['beta'] + delta_fitted = sis.fitted_parameters['delta'] + + assert np.isclose(beta, beta_fitted, rtol=5e-1) + assert np.isclose(delta, delta_fitted, rtol=5e-1) + + sis = EpidemicModel(SIS, parameters=parameters, data=data, initial_conditions=initial_conditions, fit_period=10, incidence_type=incidence) + sis.fit() + + # Check fitted parameters again + beta_fitted = sis.fitted_parameters['beta'] + delta_fitted = sis.fitted_parameters['delta'] + + assert np.isclose(beta, beta_fitted,rtol=5e-1) + assert np.isclose(delta, delta_fitted, rtol=5e-1) def test_SIR(): - # Initialize a model - beta_0 = 0.1 - gamma_0 = 0.2 - parameters, initial_conditions = SIR.get_parameters(99, 1, 0, 100, - beta=beta_0, - gamma=gamma_0, - S0_fixed=True, - I0_fixed=False, - R0_fixed=True, - S0_max=10e6, - I0_max=5e6, - R0_max=10e6) - sir = EpidemicModel(SIR, parameters=parameters, initial_conditions=initial_conditions) + for incidence in ['mass_action', 'standard']: + # Initialize a model + beta_0 = 0.1 + gamma_0 = 0.2 + parameters, initial_conditions = SIR.get_parameters(99, 1, 0, 100, + beta=beta_0, + gamma=gamma_0, + S0_fixed=True, + I0_fixed=False, + R0_fixed=True, + S0_max=10e6, + I0_max=5e6, + R0_max=10e6) + sir = EpidemicModel(SIR, parameters=parameters, initial_conditions=initial_conditions, incidence_type=incidence) - # Solve a problem with an independently checkable solution - N = 100 - S0 = 99 - I0 = 1 - R0 = 0 - beta = 0.5 - gamma = 0.25 - days_ahead = 40 # days ahead to look at - T = np.arange(days_ahead) - forecast = sir.solve(T, (S0, I0, R0), (beta, gamma, N)) + # Solve a problem with an independently checkable solution + N = 100 + S0 = 99 + I0 = 1 + R0 = 0 + beta = 0.5 + gamma = 0.25 + days_ahead = 40 # days ahead to look at + T = np.arange(days_ahead) + forecast = sir.solve(T, (S0, I0, R0), (beta, gamma, N)) - # get the curve data as if it were our data - S_data = forecast[:, 0] - I_data = forecast[:, 1] - R_data = forecast[:, 2] - data = np.array([S_data, I_data, R_data]).T + # get the curve data as if it were our data + S_data = forecast[:, 0] + I_data = forecast[:, 1] + R_data = forecast[:, 2] + data = np.array([S_data, I_data, R_data]).T - # slightly perturbed parameters, to check if the fitting finds the original (optimal) parameters - parameters, initial_conditions = SIR.get_parameters(S_data[0], I_data[0], R_data[0], N, beta=beta + 0.4, - gamma=gamma + 0.2, - S0_fixed=True, - I0_fixed=True, - R0_fixed=True, - S0_max=10e6, - I0_max=5e6, - R0_max=10e6) - sir = EpidemicModel(SIR, parameters=parameters, data=data, - initial_conditions=initial_conditions) - sir.fit() + # slightly perturbed parameters, to check if the fitting finds the original (optimal) parameters + parameters, initial_conditions = SIR.get_parameters(S_data[0], I_data[0], R_data[0], N, beta=beta + 0.4, + gamma=gamma + 0.2, + S0_fixed=True, + I0_fixed=True, + R0_fixed=True, + S0_max=10e6, + I0_max=5e6, + R0_max=10e6) + sir = EpidemicModel(SIR, parameters=parameters, data=data, + initial_conditions=initial_conditions, incidence_type=incidence) + sir.fit() - # check fitted parameters - beta_fitted = sir.fitted_parameters['beta'] - gamma_fitted = sir.fitted_parameters['gamma'] + # check fitted parameters + beta_fitted = sir.fitted_parameters['beta'] + gamma_fitted = sir.fitted_parameters['gamma'] - assert np.isclose(beta, beta_fitted) - assert np.isclose(gamma, gamma_fitted) + assert np.isclose(beta, beta_fitted) + assert np.isclose(gamma, gamma_fitted) - sir = EpidemicModel(SIR, parameters=parameters, data=data, initial_conditions=initial_conditions, fit_period=10) - sir.fit() + sir = EpidemicModel(SIR, parameters=parameters, data=data, initial_conditions=initial_conditions, fit_period=10, incidence_type=incidence) + sir.fit() - # check fitted parameters - beta_fitted = sir.fitted_parameters['beta'] - gamma_fitted = sir.fitted_parameters['gamma'] + # check fitted parameters + beta_fitted = sir.fitted_parameters['beta'] + gamma_fitted = sir.fitted_parameters['gamma'] - assert np.isclose(beta, beta_fitted) - assert np.isclose(gamma, gamma_fitted) + assert np.isclose(beta, beta_fitted) + assert np.isclose(gamma, gamma_fitted) + +def test_SIRS(): + for incidence in ['mass_action', 'standard']: + # Initialize a model + beta_0 = 0.1 + gamma_0 = 0.2 + delta_0 = 0.3 + parameters, initial_conditions = SIRS.get_parameters(99, 1, 0, 100, + beta=beta_0, + gamma=gamma_0, + delta=delta_0, + S0_fixed=True, + I0_fixed=False, + R0_fixed=True, + S0_max=10e6, + I0_max=5e6, + R0_max=10e6) + sirs = EpidemicModel(SIRS, parameters=parameters, initial_conditions=initial_conditions, incidence_type=incidence) + + # Solve a problem with an independently checkable solution + N = 100 + S0 = 99 + I0 = 1 + R0 = 0 + beta = 0.5 + gamma = 0.25 + delta = 0.25 + days_ahead = 40 # days ahead to look at + T = np.arange(days_ahead) + forecast = sirs.solve(T, (S0, I0, R0), (beta, gamma, delta, N)) + + # get the curve data as if it were our data + S_data = forecast[:, 0] + I_data = forecast[:, 1] + R_data = forecast[:, 2] + data = np.array([S_data, I_data, R_data]).T + + # slightly perturbed parameters, to check if the fitting finds the original (optimal) parameters + parameters, initial_conditions = SIRS.get_parameters(S_data[0], I_data[0], R_data[0], N, beta=beta + 0.4, + gamma=gamma + 0.2, + delta=delta + 0.2, + S0_fixed=True, + I0_fixed=True, + R0_fixed=True, + S0_max=10e6, + I0_max=5e6, + R0_max=10e6) + sirs = EpidemicModel(SIRS, parameters=parameters, data=data, + initial_conditions=initial_conditions, incidence_type=incidence) + sirs.fit() + # check fitted parameters + beta_fitted = sirs.fitted_parameters['beta'] + gamma_fitted = sirs.fitted_parameters['gamma'] + delta_fitted = sirs.fitted_parameters['delta'] + + assert np.isclose(beta, beta_fitted) + assert np.isclose(gamma, gamma_fitted) + assert np.isclose(delta, delta_fitted, rtol=5e-1) + + sirs = EpidemicModel(SIRS, parameters=parameters, data=data, initial_conditions=initial_conditions, fit_period=5, incidence_type=incidence) + sirs.fit() + + # check fitted parameters + beta_fitted = sirs.fitted_parameters['beta'] + gamma_fitted = sirs.fitted_parameters['gamma'] + delta_fitted = sirs.fitted_parameters['delta'] + + assert np.isclose(beta, beta_fitted, rtol=5e-1) + assert np.isclose(gamma, gamma_fitted, rtol=5e-1) + assert np.isclose(delta, delta_fitted, rtol=5e-1) def test_SEIR(): # Initialize a model @@ -142,7 +325,7 @@ def test_SEIR(): assert np.isclose(gamma, gamma_fitted) assert np.isclose(sigma, sigma_fitted) - seir = EpidemicModel(SEIR, parameters=parameters, data=data, initial_conditions=initial_conditions, fit_period=10) + seir = EpidemicModel(SEIR, parameters=parameters, data=data, initial_conditions=initial_conditions, fit_period=10,) seir.fit() # check fitted parameters @@ -152,4 +335,4 @@ def test_SEIR(): assert np.isclose(beta, beta_fitted) assert np.isclose(gamma, gamma_fitted) - assert np.isclose(sigma, sigma_fitted) + assert np.isclose(sigma, sigma_fitted) \ No newline at end of file diff --git a/gs_quant/test/timeseries/test_statistics.py b/gs_quant/test/timeseries/test_statistics.py index 9a3780fa..1ab8b767 100644 --- a/gs_quant/test/timeseries/test_statistics.py +++ b/gs_quant/test/timeseries/test_statistics.py @@ -686,6 +686,84 @@ def test_rolling_linear_regression(): expected = pd.Series([np.nan, np.nan, np.nan, 0.0, 2.236068, 4.472136], index=pd.date_range('2019-1-1', periods=6)) assert_series_equal(regression.standard_deviation_of_errors(), expected, check_names=False) +def test_si_model(): + n = 1000 + d = 100 + i0 = 100 + s0 = n - i0 + beta = 0.5 + + t = np.linspace(0, d, d) + + def deriv(y, t_loc, n_loc, beta_loc): + s, i = y + dsdt = -beta_loc * s * i / n_loc + didt = beta_loc * s * i / n_loc + + return dsdt, didt + + def get_series(beta_loc): + # Initial conditions vector + y0 = s0, i0 + # Integrate the SI equations over the time grid, t. + ret = odeint(deriv, y0, t, args=(n, beta_loc)) + s, i = ret.T + + dr = pd.date_range(dt.date.today(), dt.date.today() + dt.timedelta(days=d - 1)) + return pd.Series(s, dr), pd.Series(i, dr) + + # Test 'mass action' incidence + (s, i) = get_series(beta) + si_mass_action = SIModel(beta, s, i, n, incidence_type='mass_action') + + assert abs(si_mass_action.beta() - beta) < 0.01 + + beta = 0.4 + + (s, i) = get_series(beta) + + s_predict_mass_action = si_mass_action.predict_s() + i_predic_mass_action = si_mass_action.predict_i() + + assert s_predict_mass_action.size == d + assert i_predic_mass_action.size == d + + # Test 'standard' incidence + (s, i) = get_series(beta) + si_standard = SIModel(beta, s, i, n, incidence_type='standard') + + assert abs(si_standard.beta() - beta) < 0.01 + + beta = 0.4 + + (s, i) = get_series(beta) + + s_predict_standard = si_standard.predict_s() + i_predic_standard = si_standard.predict_i() + + assert s_predict_standard.size == d + assert i_predic_standard.size == d + + # Test for type error in fit parameter + with pytest.raises(MqTypeError): + SIModel(beta, s, i, n, fit=0) + + si_standard_no_fit = SIModel(beta, s, i, n, fit=False, incidence_type='standard') + + assert si_standard_no_fit.beta() == beta + + si1 = SIModel(beta, s, i, n, fit=False, incidence_type='standard') + + with DataContext(end=dt.date.today() + dt.timedelta(days=d - 1)): + si2 = SIModel(beta, s.iloc[0], i, n, fit=False, incidence_type='standard') + + assert si1.beta() == si1.beta() + assert (si1.predict_i() == si2.predict_i()).all() + assert (si1.predict_s() == si2.predict_s()).all() + + # Additional checks for mass action and standard + assert not (si_mass_action.predict_i() == si_standard.predict_i()).all() + assert not (si_mass_action.predict_s() == si_standard.predict_s()).all() def test_sir_model(): n = 1000 diff --git a/gs_quant/timeseries/statistics.py b/gs_quant/timeseries/statistics.py index 81df9051..01fd53ea 100644 --- a/gs_quant/timeseries/statistics.py +++ b/gs_quant/timeseries/statistics.py @@ -25,7 +25,7 @@ from .algebra import * from ..data import DataContext -from ..models.epidemiology import SIR, SEIR, EpidemicModel +from ..models.epidemiology import SI, SIR, SEIR, EpidemicModel """ Stats library is for basic arithmetic and statistical operations on timeseries. @@ -1140,6 +1140,145 @@ def standard_deviation_of_errors(self) -> pd.Series: """ return np.sqrt(self._res.mse_resid) +class SIModel: + """SIR Compartmental model for transmission of infectious disease + + :param beta: transmission rate of the infection + :param s: number of susceptible individuals in population + :param i: number of infectious individuals in population + :param n: total population size + :param end_date: end date for the evolution of the model + :param fit: whether to fit the model to the data + :param fit_period: on how many days back to fit the model + :param incidence_type: string, type of incidence ('mass action' or 'standard') + + **Usage** + + Fit `SI Model `_ based on the + population in each compartment over a given time period. + + The SI models the movement of individuals between two compartments: susceptible (S), infected (I). The model calibrates parameters : + + =========== ======================================================= + Parameter Description + =========== ======================================================= + S0 initial susceptible individuals + I0 initial infected individuals + beta Transmission rate from susceptible to infected + =========== ======================================================= + + The parameter beta model how fast people move from being susceptible to infected (beta). This model can be used to forecast the populations of each + compartment once calibrated + + """ + + def __init__(self, beta: float = None, s: Union[pd.Series, float] = None, + i: Union[pd.Series, float] = None, n: Union[pd.Series, float] = None, fit: bool = True, + fit_period: int = None, incidence_type: str = 'mass_action'): + + if not isinstance(fit, bool): + raise MqTypeError('expected a boolean value for "fit"') + + n = n.dropna()[0] if isinstance(n, pd.Series) else n + n = 100 if n is None else n + fit = False if s is None and i is None else fit + s = n if s is None else s + i = 1 if i is None else i + + data_start = [ts.index.min().date() for ts in [s, i] if isinstance(ts, pd.Series)] + data_start.append(DataContext.current.start_date) + start_date = max(data_start) + + data_end = [ts.index.max().date() for ts in [s, i] if isinstance(ts, pd.Series)] + data_end.append(DataContext.current.end_date) + end_date = max(data_end) + + self.s = s if isinstance(s, pd.Series) else pd.Series([s]) + self.i = i if isinstance(i, pd.Series) else pd.Series([i]) + self.n = n + self.beta_init = beta + self.fit = fit + self.fit_period = fit_period + self.incidence_type = incidence_type + + self.beta_fixed = not (self.fit or (self.beta_init is None)) + + lens = [len(x) for x in (self.s, self.i)] + dtype = float if max(lens) == min(lens) else object + data = np.array([self.s, self.i], dtype=dtype).T + + beta_init = self.beta_init if self.beta_init is not None else 0.9 + + parameters, initial_conditions = SI.get_parameters(self.s.iloc[0], self.i.iloc[0], n, + beta=beta_init, + beta_fixed=self.beta_fixed, + S0_fixed=True, I0_fixed=True) + self.parameters = parameters + + self._model = EpidemicModel(SI, parameters=parameters, data=data, initial_conditions=initial_conditions, + fit_period=self.fit_period, incidence_type=self.incidence_type) + if self.fit: + self._model.fit(verbose=False) + + t = np.arange((end_date - start_date).days + 1) + predict = self._model.solve(t, (self.s0(), self.i0()), (self.beta(), n)) + + predict_dates = pd.date_range(start_date, end_date) + + self._model.s_predict = pd.Series(predict[:, 0], predict_dates) + self._model.i_predict = pd.Series(predict[:, 1], predict_dates) + + @plot_method + def s0(self) -> float: + """ + Model calibration for initial susceptible individuals + + :return: initial susceptible individuals + """ + if self.fit: + return self._model.fitted_parameters['S0'] + return self.parameters['S0'].value + + @plot_method + def i0(self) -> float: + """ + Model calibration for initial infectious individuals + + :return: initial infectious individuals + """ + if self.fit: + return self._model.fitted_parameters['I0'] + return self.parameters['I0'].value + + @plot_method + def beta(self) -> float: + """ + Model calibration for transmission rate (susceptible to infected) + + :return: beta + """ + if self.fit: + return self._model.fitted_parameters['beta'] + return self.parameters['beta'].value + + @plot_method + def predict_s(self) -> pd.Series: + """ + Model calibration for susceptible individuals through time + + :return: susceptible predict + """ + return self._model.s_predict + + @plot_method + def predict_i(self) -> pd.Series: + """ + Model calibration for infected individuals through time + + :return: infected predict + """ + return self._model.i_predict + class SIRModel: """SIR Compartmental model for transmission of infectious disease @@ -1153,6 +1292,7 @@ class SIRModel: :param end_date: end date for the evolution of the model :param fit: whether to fit the model to the data :param fit_period: on how many days back to fit the model + :param incidence_type: string, type of incidence ('mass action' or 'standard') **Usage** @@ -1181,7 +1321,7 @@ class SIRModel: def __init__(self, beta: float = None, gamma: float = None, s: Union[pd.Series, float] = None, i: Union[pd.Series, float] = None, r: Union[pd.Series, float] = None, n: Union[pd.Series, float] = None, fit: bool = True, - fit_period: int = None): + fit_period: int = None, incidence_type: str = 'mass_action'): if not isinstance(fit, bool): raise MqTypeError('expected a boolean value for "fit"') @@ -1208,6 +1348,8 @@ def __init__(self, beta: float = None, gamma: float = None, s: Union[pd.Series, self.gamma_init = gamma self.fit = fit self.fit_period = fit_period + self.incidence_type = incidence_type + self.beta_fixed = not (self.fit or (self.beta_init is None)) self.gamma_fixed = not (self.fit or (self.gamma_init is None)) @@ -1225,7 +1367,7 @@ def __init__(self, beta: float = None, gamma: float = None, s: Union[pd.Series, self.parameters = parameters self._model = EpidemicModel(SIR, parameters=parameters, data=data, initial_conditions=initial_conditions, - fit_period=self.fit_period) + fit_period=self.fit_period, incidence_type=self.incidence_type) if self.fit: self._model.fit(verbose=False) @@ -1287,7 +1429,7 @@ def gamma(self) -> float: """ Model calibration for immunity (infected to resistant) - :return: beta + :return: gamma """ if self.fit: return self._model.fitted_parameters['gamma'] From 7920f9f3ddef14f51c6a8f2974bd8341d1f0b624 Mon Sep 17 00:00:00 2001 From: MBB Date: Mon, 5 Aug 2024 17:24:57 +0200 Subject: [PATCH 2/6] Create Maximilian Boeck.dco Added dco --- Maximilian Boeck.dco | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 Maximilian Boeck.dco diff --git a/Maximilian Boeck.dco b/Maximilian Boeck.dco new file mode 100644 index 00000000..0b4e9647 --- /dev/null +++ b/Maximilian Boeck.dco @@ -0,0 +1,9 @@ +1) I, Maximilian Boeck, certify that all work committed with the commit message +"covered by: Maximilian Boeck.dco" is my original work and I own the copyright +to this work. I agree to contribute this code under the Apache 2.0 license. + +2) I understand and agree all contribution including all personal +information I submit with it is maintained indefinitely and may be +redistributed consistent with the open source license(s) involved. + +This certification is effective for all code contributed from 2024-08-05 to 9999-01-01. From a9a24b7a741579c67c9a857ec7127cecc2283eb8 Mon Sep 17 00:00:00 2001 From: M-BSquared Date: Tue, 6 Aug 2024 11:38:34 +0200 Subject: [PATCH 3/6] Updated to release 1.0.106 to ensure all added models work with the new release From 9cc8dea0c1fe679d4f9706c834f92141c1e26267 Mon Sep 17 00:00:00 2001 From: M-BSquared Date: Wed, 7 Aug 2024 15:33:40 +0200 Subject: [PATCH 4/6] Fixed FutureWarning, added datetime handling in securities.py, moved DCO - Fixed FutureWarning related to positional indexing (pandas) - Added a TODO comment to update endpoint to handle datetime vals in securities.py - Moved DCO to the dco folder for better organization --- Maximilian Boeck.dco => dco/Maximilian Boeck.dco | 0 gs_quant/markets/securities.py | 6 +++--- gs_quant/test/markets/test_securities.py | 2 ++ gs_quant/test/timeseries/test_technicals.py | 2 +- gs_quant/timeseries/statistics.py | 6 ++++-- gs_quant/timeseries/technicals.py | 2 +- 6 files changed, 11 insertions(+), 7 deletions(-) rename Maximilian Boeck.dco => dco/Maximilian Boeck.dco (100%) diff --git a/Maximilian Boeck.dco b/dco/Maximilian Boeck.dco similarity index 100% rename from Maximilian Boeck.dco rename to dco/Maximilian Boeck.dco diff --git a/gs_quant/markets/securities.py b/gs_quant/markets/securities.py index d2f4678f..86fe6a75 100644 --- a/gs_quant/markets/securities.py +++ b/gs_quant/markets/securities.py @@ -1521,7 +1521,7 @@ def get_asset(cls, Get asset by bloomberg id: - >>> gs = SecurityMaster.get_asset("GS UN", AssetIdentifier.BLOOMBERG_ID) + >>> gs = SecurityMaster.get_asset("GS UN", AssetIdentifier.BLOOMBERG_ID, as_of=dt.datetime(2022, 2, 2, 22, 22)) Get asset by ticker and exchange code: @@ -1590,7 +1590,7 @@ def _get_security_master_asset(cls, type_ = id_type.value params = { type_: id_value, - 'asOfDate': as_of.strftime('%Y-%m-%d') # TODO: update endpoint to take times + 'asOfDate': as_of.strftime('%Y-%m-%dT%H:%M:%S') if isinstance(as_of, dt.datetime) else as_of.strftime('%Y-%m-%d') } if fields is not None: request_fields = { @@ -1651,7 +1651,7 @@ def get_identifiers(cls, id_values: List[str], id_type: SecurityIdentifier, as_o params = { type_: id_values, 'fields': ['id', 'identifiers'], - 'asOfDate': as_of.strftime('%Y-%m-%d') # TODO: update endpoint to take times + 'asOfDate': as_of.strftime('%Y-%m-%dT%H:%M:%S') if isinstance(as_of, dt.datetime) else as_of.strftime('%Y-%m-%d') } r = GsSession.current._get('/markets/securities', payload=params) diff --git a/gs_quant/test/markets/test_securities.py b/gs_quant/test/markets/test_securities.py index 1d854fe5..83bdb214 100644 --- a/gs_quant/test/markets/test_securities.py +++ b/gs_quant/test/markets/test_securities.py @@ -44,10 +44,12 @@ def test_get_asset(mocker): asset = SecurityMaster.get_asset(marquee_id, AssetIdentifier.MARQUEE_ID, as_of=dt.date.today()) + assert asset.name == "Test Asset" assert asset.get_type() == AssetType.STOCK asset = SecurityMaster.get_asset(marquee_id, AssetIdentifier.MARQUEE_ID, as_of=dt.datetime.utcnow()) + # asset = SecurityMaster.get_asset(marquee_id, AssetIdentifier.MARQUEE_ID, as_of=dt.datetime(2022, 2, 2, 22, 22)) assert asset.name == "Test Asset" assert asset.get_type() == AssetType.STOCK diff --git a/gs_quant/test/timeseries/test_technicals.py b/gs_quant/test/timeseries/test_technicals.py index b8c390ea..5d509333 100644 --- a/gs_quant/test/timeseries/test_technicals.py +++ b/gs_quant/test/timeseries/test_technicals.py @@ -256,7 +256,7 @@ def test_trend(): long_x = pd.Series(range(len(long_dates)), index=long_dates) res = trend(long_x) # Should not be all NaN, make sure it's correct length assert len(res) == len(long_x) - assert np.isclose(res[int(len(res) / 2)], long_x[int(len(res) / 2)], 0.1) + assert np.isclose(res.iloc[int(len(res) / 2)], long_x.iloc[int(len(res) / 2)], atol=0.1) assert res.notnull().any() with pytest.raises(ValueError): trend(long_x, SeasonalModel.MULTIPLICATIVE) # Should not be all NaN, make sure it's correct length diff --git a/gs_quant/timeseries/statistics.py b/gs_quant/timeseries/statistics.py index 01fd53ea..76f7ca66 100644 --- a/gs_quant/timeseries/statistics.py +++ b/gs_quant/timeseries/statistics.py @@ -676,7 +676,7 @@ def _zscore(x): if x.size == 1: return 0 - return stats.zscore(x, ddof=1)[-1] + return stats.zscore(x, ddof=1).iloc[-1] @plot_function @@ -911,7 +911,9 @@ def percentiles(x: pd.Series, y: Optional[pd.Series] = None, w: Union[Window, in elif not y.empty: min_periods = 0 if isinstance(w.r, pd.DateOffset) else w.r rolling_window = x[:y.index[-1]].rolling(w.w, min_periods) - percentile_on_x_index = rolling_window.apply(lambda a: percentileofscore(a, y[a.index[-1]:][0], kind="mean")) + percentile_on_x_index = rolling_window.apply( + lambda a: percentileofscore(a, y.iloc[y.index.get_loc(a.index[-1])], kind="mean") + ) joined_index = pd.concat([x, y], axis=1).index res = percentile_on_x_index.reindex(joined_index, method="ffill")[y.index] return apply_ramp(res, w) diff --git a/gs_quant/timeseries/technicals.py b/gs_quant/timeseries/technicals.py index b029542a..7b7598c5 100644 --- a/gs_quant/timeseries/technicals.py +++ b/gs_quant/timeseries/technicals.py @@ -240,7 +240,7 @@ def relative_strength_index(x: pd.Series, w: Union[Window, int, str] = 14) -> pd rsi.iloc[index] = 100 else: relative_strength = moving_avg_gains.iloc[index] / moving_avg_losses.iloc[index] - rsi[index] = 100 - (100 / (1 + relative_strength)) + rsi.iloc[index] = 100 - (100 / (1 + relative_strength)) return rsi From cc3ad34d5490ae9a3c7a4436697051dc640cb6ec Mon Sep 17 00:00:00 2001 From: M-BSquared Date: Sun, 11 Aug 2024 12:04:04 +0200 Subject: [PATCH 5/6] Updated yout contribution standards Everything is covered by: Maximilian Boeck.dco --- ...uant.models.epidemiology.EpidemicModel.rst | 3 +- .../gs_quant.models.epidemiology.SEIR.rst | 4 ++- .../gs_quant.models.epidemiology.SI.rst | 3 +- .../gs_quant.models.epidemiology.SIR.rst | 3 +- .../gs_quant.models.epidemiology.SIRS.rst | 3 +- .../gs_quant.models.epidemiology.SIS.rst | 3 +- ..._quant.timeseries.statistics.SEIRModel.rst | 3 +- ...gs_quant.timeseries.statistics.SIModel.rst | 4 ++- ...s_quant.timeseries.statistics.SIRModel.rst | 3 +- ..._quant.timeseries.statistics.SIRSModel.rst | 3 +- ...s_quant.timeseries.statistics.SISModel.rst | 3 +- docs/models.rst | 2 +- docs/module/models.rst | 2 +- docs/timeseries.rst | 2 +- .../examples/0000_query_dataset.ipynb | 4 +-- gs_quant/markets/securities.py | 3 ++ gs_quant/models/epidemiology.py | 2 ++ gs_quant/test/markets/test_securities.py | 2 ++ gs_quant/test/models/test_epidemiology.py | 2 ++ gs_quant/test/timeseries/test_measures.py | 31 +++++++++++++++---- gs_quant/test/timeseries/test_statistics.py | 6 ++-- gs_quant/test/timeseries/test_technicals.py | 2 ++ gs_quant/timeseries/statistics.py | 2 ++ gs_quant/timeseries/technicals.py | 3 ++ 24 files changed, 74 insertions(+), 24 deletions(-) diff --git a/docs/classes/gs_quant.models.epidemiology.EpidemicModel.rst b/docs/classes/gs_quant.models.epidemiology.EpidemicModel.rst index 0ff0afc5..08f57d32 100644 --- a/docs/classes/gs_quant.models.epidemiology.EpidemicModel.rst +++ b/docs/classes/gs_quant.models.epidemiology.EpidemicModel.rst @@ -1,4 +1,5 @@ -gs\_quant.models.epidemiology.EpidemicModel +.. This product contains code copyright Maximilian Boeck, licensed under Apache 2.0 license +gs\_quant.models.epidemiology.EpidemicModel =========================================== .. currentmodule:: gs_quant.models.epidemiology diff --git a/docs/classes/gs_quant.models.epidemiology.SEIR.rst b/docs/classes/gs_quant.models.epidemiology.SEIR.rst index 1bd71c82..0598a3d7 100644 --- a/docs/classes/gs_quant.models.epidemiology.SEIR.rst +++ b/docs/classes/gs_quant.models.epidemiology.SEIR.rst @@ -1,4 +1,6 @@ -gs\_quant.models.epidemiology.SEIR +.. This product contains code copyright Maximilian Boeck, licensed under Apache 2.0 license + +gs\_quant.models.epidemiology.SEIR ================================== .. currentmodule:: gs_quant.models.epidemiology diff --git a/docs/classes/gs_quant.models.epidemiology.SI.rst b/docs/classes/gs_quant.models.epidemiology.SI.rst index 1f1abc04..fdd8275d 100644 --- a/docs/classes/gs_quant.models.epidemiology.SI.rst +++ b/docs/classes/gs_quant.models.epidemiology.SI.rst @@ -1,4 +1,5 @@ -gs\_quant.models.epidemiology.SI +.. This product contains code copyright Maximilian Boeck, licensed under Apache 2.0 license +gs\_quant.models.epidemiology.SI ================================= .. currentmodule:: gs_quant.models.epidemiology diff --git a/docs/classes/gs_quant.models.epidemiology.SIR.rst b/docs/classes/gs_quant.models.epidemiology.SIR.rst index a3200b34..8a2c1e2a 100644 --- a/docs/classes/gs_quant.models.epidemiology.SIR.rst +++ b/docs/classes/gs_quant.models.epidemiology.SIR.rst @@ -1,4 +1,5 @@ -gs\_quant.models.epidemiology.SIR +.. This product contains code copyright Maximilian Boeck, licensed under Apache 2.0 license +gs\_quant.models.epidemiology.SIR ================================= .. currentmodule:: gs_quant.models.epidemiology diff --git a/docs/classes/gs_quant.models.epidemiology.SIRS.rst b/docs/classes/gs_quant.models.epidemiology.SIRS.rst index 76626221..0220ecd2 100644 --- a/docs/classes/gs_quant.models.epidemiology.SIRS.rst +++ b/docs/classes/gs_quant.models.epidemiology.SIRS.rst @@ -1,4 +1,5 @@ -gs\_quant.models.epidemiology.SIRS +.. This product contains code copyright Maximilian Boeck, licensed under Apache 2.0 license +gs\_quant.models.epidemiology.SIRS ================================= .. currentmodule:: gs_quant.models.epidemiology diff --git a/docs/classes/gs_quant.models.epidemiology.SIS.rst b/docs/classes/gs_quant.models.epidemiology.SIS.rst index 192d3323..d9be07ec 100644 --- a/docs/classes/gs_quant.models.epidemiology.SIS.rst +++ b/docs/classes/gs_quant.models.epidemiology.SIS.rst @@ -1,4 +1,5 @@ -gs\_quant.models.epidemiology.SIS +.. This product contains code copyright Maximilian Boeck, licensed under Apache 2.0 license +gs\_quant.models.epidemiology.SIS ================================= .. currentmodule:: gs_quant.models.epidemiology diff --git a/docs/classes/gs_quant.timeseries.statistics.SEIRModel.rst b/docs/classes/gs_quant.timeseries.statistics.SEIRModel.rst index 32f636ad..473e388d 100644 --- a/docs/classes/gs_quant.timeseries.statistics.SEIRModel.rst +++ b/docs/classes/gs_quant.timeseries.statistics.SEIRModel.rst @@ -1,4 +1,5 @@ -gs\_quant.timeseries.statistics.SEIRModel +.. This product contains code copyright Maximilian Boeck, licensed under Apache 2.0 license +gs\_quant.timeseries.statistics.SEIRModel ========================================= .. currentmodule:: gs_quant.timeseries.statistics diff --git a/docs/classes/gs_quant.timeseries.statistics.SIModel.rst b/docs/classes/gs_quant.timeseries.statistics.SIModel.rst index d3830b11..fa4e6d54 100644 --- a/docs/classes/gs_quant.timeseries.statistics.SIModel.rst +++ b/docs/classes/gs_quant.timeseries.statistics.SIModel.rst @@ -1,4 +1,6 @@ -gs\_quant.timeseries.statistics.SIModel +.. This product contains code copyright Maximilian Boeck, licensed under Apache 2.0 license + +gs\_quant.timeseries.statistics.SIModel ======================================== .. currentmodule:: gs_quant.timeseries.statistics diff --git a/docs/classes/gs_quant.timeseries.statistics.SIRModel.rst b/docs/classes/gs_quant.timeseries.statistics.SIRModel.rst index ef187dca..ea72c07a 100644 --- a/docs/classes/gs_quant.timeseries.statistics.SIRModel.rst +++ b/docs/classes/gs_quant.timeseries.statistics.SIRModel.rst @@ -1,4 +1,5 @@ -gs\_quant.timeseries.statistics.SIRModel +.. This product contains code copyright Maximilian Boeck, licensed under Apache 2.0 license +gs\_quant.timeseries.statistics.SIRModel ======================================== .. currentmodule:: gs_quant.timeseries.statistics diff --git a/docs/classes/gs_quant.timeseries.statistics.SIRSModel.rst b/docs/classes/gs_quant.timeseries.statistics.SIRSModel.rst index 8a7073e7..52cee857 100644 --- a/docs/classes/gs_quant.timeseries.statistics.SIRSModel.rst +++ b/docs/classes/gs_quant.timeseries.statistics.SIRSModel.rst @@ -1,4 +1,5 @@ -gs\_quant.timeseries.statistics.SIRSModel +.. This product contains code copyright Maximilian Boeck, licensed under Apache 2.0 license +gs\_quant.timeseries.statistics.SIRSModel ======================================== .. currentmodule:: gs_quant.timeseries.statistics diff --git a/docs/classes/gs_quant.timeseries.statistics.SISModel.rst b/docs/classes/gs_quant.timeseries.statistics.SISModel.rst index df924e8b..453f189f 100644 --- a/docs/classes/gs_quant.timeseries.statistics.SISModel.rst +++ b/docs/classes/gs_quant.timeseries.statistics.SISModel.rst @@ -1,4 +1,5 @@ -gs\_quant.timeseries.statistics.SISModel +.. This product contains code copyright Maximilian Boeck, licensed under Apache 2.0 license +gs\_quant.timeseries.statistics.SISModel ======================================== .. currentmodule:: gs_quant.timeseries.statistics diff --git a/docs/models.rst b/docs/models.rst index 29a3f921..eef58afc 100644 --- a/docs/models.rst +++ b/docs/models.rst @@ -1,4 +1,4 @@ - +.. This product contains code copyright Maximilian Boeck, licensed under Apache 2.0 license Models Package ============== diff --git a/docs/module/models.rst b/docs/module/models.rst index 3d5b5119..36313599 100644 --- a/docs/module/models.rst +++ b/docs/module/models.rst @@ -1,4 +1,4 @@ - +.. This product contains code copyright Maximilian Boeck, licensed under Apache 2.0 license Models Package ============== diff --git a/docs/timeseries.rst b/docs/timeseries.rst index a09461da..8a5a7d02 100644 --- a/docs/timeseries.rst +++ b/docs/timeseries.rst @@ -1,4 +1,4 @@ - +.. This product contains code copyright Maximilian Boeck, licensed under Apache 2.0 license Timeseries Package ================== diff --git a/gs_quant/documentation/00_data/00_datasets/examples/0000_query_dataset.ipynb b/gs_quant/documentation/00_data/00_datasets/examples/0000_query_dataset.ipynb index 443186a3..e449a477 100644 --- a/gs_quant/documentation/00_data/00_datasets/examples/0000_query_dataset.ipynb +++ b/gs_quant/documentation/00_data/00_datasets/examples/0000_query_dataset.ipynb @@ -528,10 +528,10 @@ "pycharm": { "stem_cell": { "cell_type": "raw", - "source": [], "metadata": { "collapsed": false - } + }, + "source": [] } } }, diff --git a/gs_quant/markets/securities.py b/gs_quant/markets/securities.py index 86fe6a75..c0c7630c 100644 --- a/gs_quant/markets/securities.py +++ b/gs_quant/markets/securities.py @@ -12,7 +12,10 @@ KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +//Portions copyright Maximilian Boeck. Licensed under Apache 2.0 license """ + import calendar import datetime import datetime as dt diff --git a/gs_quant/models/epidemiology.py b/gs_quant/models/epidemiology.py index 03d29509..a098dbef 100644 --- a/gs_quant/models/epidemiology.py +++ b/gs_quant/models/epidemiology.py @@ -12,6 +12,8 @@ KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +//Portions copyright Maximilian Boeck. Licensed under Apache 2.0 license """ from abc import ABC, abstractmethod from typing import Type, Union diff --git a/gs_quant/test/markets/test_securities.py b/gs_quant/test/markets/test_securities.py index 83bdb214..142a99fd 100644 --- a/gs_quant/test/markets/test_securities.py +++ b/gs_quant/test/markets/test_securities.py @@ -12,6 +12,8 @@ KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +//Portions copyright Maximilian Boeck. Licensed under Apache 2.0 license """ import copy import datetime diff --git a/gs_quant/test/models/test_epidemiology.py b/gs_quant/test/models/test_epidemiology.py index 40037b03..86ae916e 100644 --- a/gs_quant/test/models/test_epidemiology.py +++ b/gs_quant/test/models/test_epidemiology.py @@ -12,6 +12,8 @@ KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +//Portions copyright Maximilian Boeck. Licensed under Apache 2.0 license """ from gs_quant.models.epidemiology import * diff --git a/gs_quant/test/timeseries/test_measures.py b/gs_quant/test/timeseries/test_measures.py index b6e31351..51bf2da2 100644 --- a/gs_quant/test/timeseries/test_measures.py +++ b/gs_quant/test/timeseries/test_measures.py @@ -12,6 +12,8 @@ KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +//Portions copyright Maximilian Boeck. Licensed under Apache 2.0 license """ import datetime @@ -3507,18 +3509,23 @@ def test_skew_term(): _skew_term_typical(tm.SkewReference.DELTA, 25) _skew_term_empty() _skew_term_no_data() + with DataContext('2018-01-16', '2018-12-31'): out = _skew_term_typical(tm.SkewReference.DELTA, 25) assert out.empty assert set(out.dataset_ids) == set(_test_datasets + _test_datasets2) + with DataContext('2018-01-16', '2018-12-31'): out = _skew_term_typical_fx(tm.SkewReference.DELTA, 25) assert out.empty + with pytest.raises(NotImplementedError): tm.skew_term(..., tm.SkewReference.SPOT, 100, real_time=True) + with DataContext('2020-01-01', '2021-01-01'): with pytest.raises(MqError, match='forward looking date range'): tm.skew_term(Index('MA123', AssetClass.Equity, '123'), tm.SkewReference.SPOT, 100, source='plottool') + with DataContext(datetime.date.today(), datetime.date.today()): with pytest.raises(MqError, match='forward looking date range'): tm.skew_term(Index('MA123', AssetClass.Equity, '123'), tm.SkewReference.SPOT, 100, source='plottool') @@ -5497,11 +5504,11 @@ def test_retail_interest_agg(): replace = Replacer() positions_df = replace('gs_quant.timeseries.measures.PositionedEntity.get_positions_data', Mock()) - positions_df.return_value = { + positions_df.return_value = pd.DataFrame({ 'id': ['MA4B66MW5E27UALNBLL', 'MA4B66MW5E27UALNBLL'], 'assetClassificationsGicsSector': ['Energy', 'Health Care'], - 'positionDate': ['2012-12-20', '2012-12-20'] - } + 'positionDate': [pd.Timestamp('2012-12-20'), pd.Timestamp('2012-12-20')] + }) retail_df = pd.DataFrame( data={ @@ -5514,7 +5521,9 @@ def test_retail_interest_agg(): 'impliedRetailPctNotional': [60.0, 30.0], 'assetId': ['MA4B66MW5E27UALNBLL', 'MA4B66MW5E27UALNBLL'], }, - index=[pd.Timestamp('2021-12-20'), pd.Timestamp('2021-12-20')]) + index=[pd.Timestamp('2021-12-20'), pd.Timestamp('2021-12-20')] + ) + retail_df_energy = pd.DataFrame( data={ 'shares': [10.0], @@ -5526,35 +5535,45 @@ def test_retail_interest_agg(): 'impliedRetailPctNotional': [60.0], 'assetId': ['MA4B66MW5E27UALNBLL'], }, - index=[pd.Timestamp('2021-12-20')]) + index=[pd.Timestamp('2021-12-20')] + ) + mock_retail = replace('gs_quant.data.dataset.Dataset.get_data', Mock()) mock_retail.return_value = retail_df + expected_df = pd.DataFrame( data={'shares': [20.0], 'impliedRetailPctShares': [40.0], 'impliedRetailPctNotional': [60.0]}, - index=[pd.Timestamp('2021-12-20')]) + index=[pd.Timestamp('2021-12-20')] + ) assert_series_equal(ExtendedSeries(expected_df['shares']), tm.retail_interest_agg(mock_spx, tm.RetailMeasures.SHARES, tm.UnderlyingSourceCategory.ALL, tm.GICSSector.ALL)) + assert_series_equal(ExtendedSeries(expected_df['impliedRetailPctShares']), tm.retail_interest_agg(mock_spx, tm.RetailMeasures.RETAIL_PCT_SHARES, tm.UnderlyingSourceCategory.ALL, tm.GICSSector.ALL)) + mock_retail.return_value = retail_df_energy + assert_series_equal(ExtendedSeries(expected_df['impliedRetailPctNotional']), tm.retail_interest_agg(mock_spx, tm.RetailMeasures.RETAIL_PCT_NOTIONAL, tm.UnderlyingSourceCategory.ALL, tm.GICSSector.ENERGY)) + mock_retail.return_value = pd.DataFrame(dtype=float) + assert_series_equal(ExtendedSeries(dtype=float), tm.retail_interest_agg(mock_spx, tm.RetailMeasures.RETAIL_PCT_NOTIONAL, tm.UnderlyingSourceCategory.ALL, tm.GICSSector.ENERGY)) + replace.restore() diff --git a/gs_quant/test/timeseries/test_statistics.py b/gs_quant/test/timeseries/test_statistics.py index 1ab8b767..71bb8463 100644 --- a/gs_quant/test/timeseries/test_statistics.py +++ b/gs_quant/test/timeseries/test_statistics.py @@ -12,6 +12,8 @@ KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +//Portions copyright Maximilian Boeck. Licensed under Apache 2.0 license """ import pytest @@ -396,8 +398,8 @@ def exp_std_calc(ts, alpha=0.75): ema = sum(weights * x) / sum(weights) debias_fact = sum(weights) ** 2 / (sum(weights) ** 2 - sum(weights ** 2)) var = debias_fact * sum(weights * (x - ema) ** 2) / sum(weights) - std.iloc[i] = np.sqrt(var) - std[0] = np.NaN + std.iat[i] = np.sqrt(var) + std.iat[0] = np.NaN return std dates = [ diff --git a/gs_quant/test/timeseries/test_technicals.py b/gs_quant/test/timeseries/test_technicals.py index 5d509333..11af02a8 100644 --- a/gs_quant/test/timeseries/test_technicals.py +++ b/gs_quant/test/timeseries/test_technicals.py @@ -12,6 +12,8 @@ KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +//Portions copyright Maximilian Boeck. Licensed under Apache 2.0 license """ import pytest diff --git a/gs_quant/timeseries/statistics.py b/gs_quant/timeseries/statistics.py index 76f7ca66..62301675 100644 --- a/gs_quant/timeseries/statistics.py +++ b/gs_quant/timeseries/statistics.py @@ -15,6 +15,8 @@ # Such functions should be fully documented: docstrings should describe parameters and the return value, and provide # a 1-line description. Type annotations should be provided for parameters. +# //Portions copyright Maximilian Boeck. Licensed under Apache 2.0 license + import datetime import numpy diff --git a/gs_quant/timeseries/technicals.py b/gs_quant/timeseries/technicals.py index 7b7598c5..dc3ce032 100644 --- a/gs_quant/timeseries/technicals.py +++ b/gs_quant/timeseries/technicals.py @@ -14,6 +14,9 @@ # Marquee Plot Service will attempt to make public functions (not prefixed with _) from this module available. # Such functions should be fully documented: docstrings should describe parameters and the return value, and provide # a 1-line description. Type annotations should be provided for parameters. + +# //Portions copyright Maximilian Boeck. Licensed under Apache 2.0 license + import pandas as pd import statsmodels.tsa.seasonal From a2c94e6377eb582f2de2bd38b3381b24343b8310 Mon Sep 17 00:00:00 2001 From: M-BSquared Date: Sun, 11 Aug 2024 12:04:04 +0200 Subject: [PATCH 6/6] Updated your contribution standards Everything is covered by: Maximilian Boeck.dco --- ...uant.models.epidemiology.EpidemicModel.rst | 3 +- .../gs_quant.models.epidemiology.SEIR.rst | 4 ++- .../gs_quant.models.epidemiology.SI.rst | 3 +- .../gs_quant.models.epidemiology.SIR.rst | 3 +- .../gs_quant.models.epidemiology.SIRS.rst | 3 +- .../gs_quant.models.epidemiology.SIS.rst | 3 +- ..._quant.timeseries.statistics.SEIRModel.rst | 3 +- ...gs_quant.timeseries.statistics.SIModel.rst | 4 ++- ...s_quant.timeseries.statistics.SIRModel.rst | 3 +- ..._quant.timeseries.statistics.SIRSModel.rst | 3 +- ...s_quant.timeseries.statistics.SISModel.rst | 3 +- docs/models.rst | 2 +- docs/module/models.rst | 2 +- docs/timeseries.rst | 2 +- .../examples/0000_query_dataset.ipynb | 4 +-- gs_quant/markets/securities.py | 3 ++ gs_quant/models/epidemiology.py | 2 ++ gs_quant/test/markets/test_securities.py | 2 ++ gs_quant/test/models/test_epidemiology.py | 2 ++ gs_quant/test/timeseries/test_measures.py | 31 +++++++++++++++---- gs_quant/test/timeseries/test_statistics.py | 6 ++-- gs_quant/test/timeseries/test_technicals.py | 2 ++ gs_quant/timeseries/statistics.py | 2 ++ gs_quant/timeseries/technicals.py | 3 ++ 24 files changed, 74 insertions(+), 24 deletions(-) diff --git a/docs/classes/gs_quant.models.epidemiology.EpidemicModel.rst b/docs/classes/gs_quant.models.epidemiology.EpidemicModel.rst index 0ff0afc5..08f57d32 100644 --- a/docs/classes/gs_quant.models.epidemiology.EpidemicModel.rst +++ b/docs/classes/gs_quant.models.epidemiology.EpidemicModel.rst @@ -1,4 +1,5 @@ -gs\_quant.models.epidemiology.EpidemicModel +.. This product contains code copyright Maximilian Boeck, licensed under Apache 2.0 license +gs\_quant.models.epidemiology.EpidemicModel =========================================== .. currentmodule:: gs_quant.models.epidemiology diff --git a/docs/classes/gs_quant.models.epidemiology.SEIR.rst b/docs/classes/gs_quant.models.epidemiology.SEIR.rst index 1bd71c82..0598a3d7 100644 --- a/docs/classes/gs_quant.models.epidemiology.SEIR.rst +++ b/docs/classes/gs_quant.models.epidemiology.SEIR.rst @@ -1,4 +1,6 @@ -gs\_quant.models.epidemiology.SEIR +.. This product contains code copyright Maximilian Boeck, licensed under Apache 2.0 license + +gs\_quant.models.epidemiology.SEIR ================================== .. currentmodule:: gs_quant.models.epidemiology diff --git a/docs/classes/gs_quant.models.epidemiology.SI.rst b/docs/classes/gs_quant.models.epidemiology.SI.rst index 1f1abc04..fdd8275d 100644 --- a/docs/classes/gs_quant.models.epidemiology.SI.rst +++ b/docs/classes/gs_quant.models.epidemiology.SI.rst @@ -1,4 +1,5 @@ -gs\_quant.models.epidemiology.SI +.. This product contains code copyright Maximilian Boeck, licensed under Apache 2.0 license +gs\_quant.models.epidemiology.SI ================================= .. currentmodule:: gs_quant.models.epidemiology diff --git a/docs/classes/gs_quant.models.epidemiology.SIR.rst b/docs/classes/gs_quant.models.epidemiology.SIR.rst index a3200b34..8a2c1e2a 100644 --- a/docs/classes/gs_quant.models.epidemiology.SIR.rst +++ b/docs/classes/gs_quant.models.epidemiology.SIR.rst @@ -1,4 +1,5 @@ -gs\_quant.models.epidemiology.SIR +.. This product contains code copyright Maximilian Boeck, licensed under Apache 2.0 license +gs\_quant.models.epidemiology.SIR ================================= .. currentmodule:: gs_quant.models.epidemiology diff --git a/docs/classes/gs_quant.models.epidemiology.SIRS.rst b/docs/classes/gs_quant.models.epidemiology.SIRS.rst index 76626221..0220ecd2 100644 --- a/docs/classes/gs_quant.models.epidemiology.SIRS.rst +++ b/docs/classes/gs_quant.models.epidemiology.SIRS.rst @@ -1,4 +1,5 @@ -gs\_quant.models.epidemiology.SIRS +.. This product contains code copyright Maximilian Boeck, licensed under Apache 2.0 license +gs\_quant.models.epidemiology.SIRS ================================= .. currentmodule:: gs_quant.models.epidemiology diff --git a/docs/classes/gs_quant.models.epidemiology.SIS.rst b/docs/classes/gs_quant.models.epidemiology.SIS.rst index 192d3323..d9be07ec 100644 --- a/docs/classes/gs_quant.models.epidemiology.SIS.rst +++ b/docs/classes/gs_quant.models.epidemiology.SIS.rst @@ -1,4 +1,5 @@ -gs\_quant.models.epidemiology.SIS +.. This product contains code copyright Maximilian Boeck, licensed under Apache 2.0 license +gs\_quant.models.epidemiology.SIS ================================= .. currentmodule:: gs_quant.models.epidemiology diff --git a/docs/classes/gs_quant.timeseries.statistics.SEIRModel.rst b/docs/classes/gs_quant.timeseries.statistics.SEIRModel.rst index 32f636ad..473e388d 100644 --- a/docs/classes/gs_quant.timeseries.statistics.SEIRModel.rst +++ b/docs/classes/gs_quant.timeseries.statistics.SEIRModel.rst @@ -1,4 +1,5 @@ -gs\_quant.timeseries.statistics.SEIRModel +.. This product contains code copyright Maximilian Boeck, licensed under Apache 2.0 license +gs\_quant.timeseries.statistics.SEIRModel ========================================= .. currentmodule:: gs_quant.timeseries.statistics diff --git a/docs/classes/gs_quant.timeseries.statistics.SIModel.rst b/docs/classes/gs_quant.timeseries.statistics.SIModel.rst index d3830b11..fa4e6d54 100644 --- a/docs/classes/gs_quant.timeseries.statistics.SIModel.rst +++ b/docs/classes/gs_quant.timeseries.statistics.SIModel.rst @@ -1,4 +1,6 @@ -gs\_quant.timeseries.statistics.SIModel +.. This product contains code copyright Maximilian Boeck, licensed under Apache 2.0 license + +gs\_quant.timeseries.statistics.SIModel ======================================== .. currentmodule:: gs_quant.timeseries.statistics diff --git a/docs/classes/gs_quant.timeseries.statistics.SIRModel.rst b/docs/classes/gs_quant.timeseries.statistics.SIRModel.rst index ef187dca..ea72c07a 100644 --- a/docs/classes/gs_quant.timeseries.statistics.SIRModel.rst +++ b/docs/classes/gs_quant.timeseries.statistics.SIRModel.rst @@ -1,4 +1,5 @@ -gs\_quant.timeseries.statistics.SIRModel +.. This product contains code copyright Maximilian Boeck, licensed under Apache 2.0 license +gs\_quant.timeseries.statistics.SIRModel ======================================== .. currentmodule:: gs_quant.timeseries.statistics diff --git a/docs/classes/gs_quant.timeseries.statistics.SIRSModel.rst b/docs/classes/gs_quant.timeseries.statistics.SIRSModel.rst index 8a7073e7..52cee857 100644 --- a/docs/classes/gs_quant.timeseries.statistics.SIRSModel.rst +++ b/docs/classes/gs_quant.timeseries.statistics.SIRSModel.rst @@ -1,4 +1,5 @@ -gs\_quant.timeseries.statistics.SIRSModel +.. This product contains code copyright Maximilian Boeck, licensed under Apache 2.0 license +gs\_quant.timeseries.statistics.SIRSModel ======================================== .. currentmodule:: gs_quant.timeseries.statistics diff --git a/docs/classes/gs_quant.timeseries.statistics.SISModel.rst b/docs/classes/gs_quant.timeseries.statistics.SISModel.rst index df924e8b..453f189f 100644 --- a/docs/classes/gs_quant.timeseries.statistics.SISModel.rst +++ b/docs/classes/gs_quant.timeseries.statistics.SISModel.rst @@ -1,4 +1,5 @@ -gs\_quant.timeseries.statistics.SISModel +.. This product contains code copyright Maximilian Boeck, licensed under Apache 2.0 license +gs\_quant.timeseries.statistics.SISModel ======================================== .. currentmodule:: gs_quant.timeseries.statistics diff --git a/docs/models.rst b/docs/models.rst index 29a3f921..eef58afc 100644 --- a/docs/models.rst +++ b/docs/models.rst @@ -1,4 +1,4 @@ - +.. This product contains code copyright Maximilian Boeck, licensed under Apache 2.0 license Models Package ============== diff --git a/docs/module/models.rst b/docs/module/models.rst index 3d5b5119..36313599 100644 --- a/docs/module/models.rst +++ b/docs/module/models.rst @@ -1,4 +1,4 @@ - +.. This product contains code copyright Maximilian Boeck, licensed under Apache 2.0 license Models Package ============== diff --git a/docs/timeseries.rst b/docs/timeseries.rst index a09461da..8a5a7d02 100644 --- a/docs/timeseries.rst +++ b/docs/timeseries.rst @@ -1,4 +1,4 @@ - +.. This product contains code copyright Maximilian Boeck, licensed under Apache 2.0 license Timeseries Package ================== diff --git a/gs_quant/documentation/00_data/00_datasets/examples/0000_query_dataset.ipynb b/gs_quant/documentation/00_data/00_datasets/examples/0000_query_dataset.ipynb index 443186a3..e449a477 100644 --- a/gs_quant/documentation/00_data/00_datasets/examples/0000_query_dataset.ipynb +++ b/gs_quant/documentation/00_data/00_datasets/examples/0000_query_dataset.ipynb @@ -528,10 +528,10 @@ "pycharm": { "stem_cell": { "cell_type": "raw", - "source": [], "metadata": { "collapsed": false - } + }, + "source": [] } } }, diff --git a/gs_quant/markets/securities.py b/gs_quant/markets/securities.py index 86fe6a75..c0c7630c 100644 --- a/gs_quant/markets/securities.py +++ b/gs_quant/markets/securities.py @@ -12,7 +12,10 @@ KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +//Portions copyright Maximilian Boeck. Licensed under Apache 2.0 license """ + import calendar import datetime import datetime as dt diff --git a/gs_quant/models/epidemiology.py b/gs_quant/models/epidemiology.py index 03d29509..a098dbef 100644 --- a/gs_quant/models/epidemiology.py +++ b/gs_quant/models/epidemiology.py @@ -12,6 +12,8 @@ KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +//Portions copyright Maximilian Boeck. Licensed under Apache 2.0 license """ from abc import ABC, abstractmethod from typing import Type, Union diff --git a/gs_quant/test/markets/test_securities.py b/gs_quant/test/markets/test_securities.py index 83bdb214..142a99fd 100644 --- a/gs_quant/test/markets/test_securities.py +++ b/gs_quant/test/markets/test_securities.py @@ -12,6 +12,8 @@ KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +//Portions copyright Maximilian Boeck. Licensed under Apache 2.0 license """ import copy import datetime diff --git a/gs_quant/test/models/test_epidemiology.py b/gs_quant/test/models/test_epidemiology.py index 40037b03..86ae916e 100644 --- a/gs_quant/test/models/test_epidemiology.py +++ b/gs_quant/test/models/test_epidemiology.py @@ -12,6 +12,8 @@ KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +//Portions copyright Maximilian Boeck. Licensed under Apache 2.0 license """ from gs_quant.models.epidemiology import * diff --git a/gs_quant/test/timeseries/test_measures.py b/gs_quant/test/timeseries/test_measures.py index b6e31351..51bf2da2 100644 --- a/gs_quant/test/timeseries/test_measures.py +++ b/gs_quant/test/timeseries/test_measures.py @@ -12,6 +12,8 @@ KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +//Portions copyright Maximilian Boeck. Licensed under Apache 2.0 license """ import datetime @@ -3507,18 +3509,23 @@ def test_skew_term(): _skew_term_typical(tm.SkewReference.DELTA, 25) _skew_term_empty() _skew_term_no_data() + with DataContext('2018-01-16', '2018-12-31'): out = _skew_term_typical(tm.SkewReference.DELTA, 25) assert out.empty assert set(out.dataset_ids) == set(_test_datasets + _test_datasets2) + with DataContext('2018-01-16', '2018-12-31'): out = _skew_term_typical_fx(tm.SkewReference.DELTA, 25) assert out.empty + with pytest.raises(NotImplementedError): tm.skew_term(..., tm.SkewReference.SPOT, 100, real_time=True) + with DataContext('2020-01-01', '2021-01-01'): with pytest.raises(MqError, match='forward looking date range'): tm.skew_term(Index('MA123', AssetClass.Equity, '123'), tm.SkewReference.SPOT, 100, source='plottool') + with DataContext(datetime.date.today(), datetime.date.today()): with pytest.raises(MqError, match='forward looking date range'): tm.skew_term(Index('MA123', AssetClass.Equity, '123'), tm.SkewReference.SPOT, 100, source='plottool') @@ -5497,11 +5504,11 @@ def test_retail_interest_agg(): replace = Replacer() positions_df = replace('gs_quant.timeseries.measures.PositionedEntity.get_positions_data', Mock()) - positions_df.return_value = { + positions_df.return_value = pd.DataFrame({ 'id': ['MA4B66MW5E27UALNBLL', 'MA4B66MW5E27UALNBLL'], 'assetClassificationsGicsSector': ['Energy', 'Health Care'], - 'positionDate': ['2012-12-20', '2012-12-20'] - } + 'positionDate': [pd.Timestamp('2012-12-20'), pd.Timestamp('2012-12-20')] + }) retail_df = pd.DataFrame( data={ @@ -5514,7 +5521,9 @@ def test_retail_interest_agg(): 'impliedRetailPctNotional': [60.0, 30.0], 'assetId': ['MA4B66MW5E27UALNBLL', 'MA4B66MW5E27UALNBLL'], }, - index=[pd.Timestamp('2021-12-20'), pd.Timestamp('2021-12-20')]) + index=[pd.Timestamp('2021-12-20'), pd.Timestamp('2021-12-20')] + ) + retail_df_energy = pd.DataFrame( data={ 'shares': [10.0], @@ -5526,35 +5535,45 @@ def test_retail_interest_agg(): 'impliedRetailPctNotional': [60.0], 'assetId': ['MA4B66MW5E27UALNBLL'], }, - index=[pd.Timestamp('2021-12-20')]) + index=[pd.Timestamp('2021-12-20')] + ) + mock_retail = replace('gs_quant.data.dataset.Dataset.get_data', Mock()) mock_retail.return_value = retail_df + expected_df = pd.DataFrame( data={'shares': [20.0], 'impliedRetailPctShares': [40.0], 'impliedRetailPctNotional': [60.0]}, - index=[pd.Timestamp('2021-12-20')]) + index=[pd.Timestamp('2021-12-20')] + ) assert_series_equal(ExtendedSeries(expected_df['shares']), tm.retail_interest_agg(mock_spx, tm.RetailMeasures.SHARES, tm.UnderlyingSourceCategory.ALL, tm.GICSSector.ALL)) + assert_series_equal(ExtendedSeries(expected_df['impliedRetailPctShares']), tm.retail_interest_agg(mock_spx, tm.RetailMeasures.RETAIL_PCT_SHARES, tm.UnderlyingSourceCategory.ALL, tm.GICSSector.ALL)) + mock_retail.return_value = retail_df_energy + assert_series_equal(ExtendedSeries(expected_df['impliedRetailPctNotional']), tm.retail_interest_agg(mock_spx, tm.RetailMeasures.RETAIL_PCT_NOTIONAL, tm.UnderlyingSourceCategory.ALL, tm.GICSSector.ENERGY)) + mock_retail.return_value = pd.DataFrame(dtype=float) + assert_series_equal(ExtendedSeries(dtype=float), tm.retail_interest_agg(mock_spx, tm.RetailMeasures.RETAIL_PCT_NOTIONAL, tm.UnderlyingSourceCategory.ALL, tm.GICSSector.ENERGY)) + replace.restore() diff --git a/gs_quant/test/timeseries/test_statistics.py b/gs_quant/test/timeseries/test_statistics.py index 1ab8b767..71bb8463 100644 --- a/gs_quant/test/timeseries/test_statistics.py +++ b/gs_quant/test/timeseries/test_statistics.py @@ -12,6 +12,8 @@ KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +//Portions copyright Maximilian Boeck. Licensed under Apache 2.0 license """ import pytest @@ -396,8 +398,8 @@ def exp_std_calc(ts, alpha=0.75): ema = sum(weights * x) / sum(weights) debias_fact = sum(weights) ** 2 / (sum(weights) ** 2 - sum(weights ** 2)) var = debias_fact * sum(weights * (x - ema) ** 2) / sum(weights) - std.iloc[i] = np.sqrt(var) - std[0] = np.NaN + std.iat[i] = np.sqrt(var) + std.iat[0] = np.NaN return std dates = [ diff --git a/gs_quant/test/timeseries/test_technicals.py b/gs_quant/test/timeseries/test_technicals.py index 5d509333..11af02a8 100644 --- a/gs_quant/test/timeseries/test_technicals.py +++ b/gs_quant/test/timeseries/test_technicals.py @@ -12,6 +12,8 @@ KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +//Portions copyright Maximilian Boeck. Licensed under Apache 2.0 license """ import pytest diff --git a/gs_quant/timeseries/statistics.py b/gs_quant/timeseries/statistics.py index 76f7ca66..62301675 100644 --- a/gs_quant/timeseries/statistics.py +++ b/gs_quant/timeseries/statistics.py @@ -15,6 +15,8 @@ # Such functions should be fully documented: docstrings should describe parameters and the return value, and provide # a 1-line description. Type annotations should be provided for parameters. +# //Portions copyright Maximilian Boeck. Licensed under Apache 2.0 license + import datetime import numpy diff --git a/gs_quant/timeseries/technicals.py b/gs_quant/timeseries/technicals.py index 7b7598c5..dc3ce032 100644 --- a/gs_quant/timeseries/technicals.py +++ b/gs_quant/timeseries/technicals.py @@ -14,6 +14,9 @@ # Marquee Plot Service will attempt to make public functions (not prefixed with _) from this module available. # Such functions should be fully documented: docstrings should describe parameters and the return value, and provide # a 1-line description. Type annotations should be provided for parameters. + +# //Portions copyright Maximilian Boeck. Licensed under Apache 2.0 license + import pandas as pd import statsmodels.tsa.seasonal