
def stspestmp_ll(datainlist):
    import numpy as np
    import statsmodels.api as sm        
    import pandas as pd
    import datetime as dt
    
    # Unpack the information in the list
    samp, refextract, y, xonr, onrvar, datesall  = datainlist
    
    # Function that chooses optimization parameters and fits the model
    def optim_details(mod):

        inititer, initfuntol = 1000, 1e-10
        finfuniter, finfuntol = 10000, 1e-10

        modinit = mod.fit(method = 'minimize',
                          min_method = 'L-BFGS-B',
                          maxiter = inititer,
                          ftol = initfuntol,
                          disp = 0) # Initialize the fit to get new starting values 

        modfit = mod.fit(method = 'minimize',
                          min_method = 'L-BFGS-B',
                          maxiter = finfuniter,
                          ftol = finfuntol,
                          start_params = modinit.params,
                          disp = 0) # Fit it using starting values   
        return modfit

    # Define the RW model with controls (for currencies with ONR variation)
    class llmod_wCntrls(sm.tsa.statespace.MLEModel):
    
        def __init__(self, y, x):
            super(llmod_wCntrls, self).__init__(endog = y,
                                                exog = x,
                                                k_states=1, 
                                                k_posdef=1,
                                                initialization = 'diffuse')
    
                
            # Specify the fixed elements of the state space matrices (things that are assumed)
            self['design', 0, 0] = 1.0 # Design matrix is 2x1 of ones
            self['transition', 0, 0] = 1.0 # Transition and selection matrices are 1x1 of ones
            self['selection', 0, 0] = 1.0         
            
            if len(np.shape(x)) == 1:
                self.k_exog = 1
            else:
                self.k_exog = np.shape(x)[-1]
    
            self['obs_intercept'] = np.zeros((1,self.nobs))        
            
            # Declare which parameters need to be positive (reference param_names to check )
            self.positive_parameters = slice(0,2) 
    
        @property
        def param_names(self):
            param_names =  [
                'state_var',
                'obs_var',
            ]
            for n in range(self.k_exog):
                param_names.extend(['betavar' + str(n) + '_1'])
            return param_names
        
        @property
        def start_params(self):
            params = [
                0.01,
                0.01,
            ]
            for n in range(self.k_exog):
                params.extend([0])
            return params
        
        # Constrain the variance parameters to be positive and no smaller than "minvar"
        def transform_params(self, unconstrained):
            minvar = 1e-7
            constrained = unconstrained.copy()
            constrained[self.positive_parameters] = constrained[self.positive_parameters] ** 2 + minvar
            return constrained
    
        def untransform_params(self, constrained):
            minvar = 1e-7
            unconstrained = constrained.copy()
            unconstrained[self.positive_parameters] = (unconstrained[self.positive_parameters] - minvar)** 0.5         
            return unconstrained   
        
        # Define how estimated parameters map to the state space matrices 
        def update(self, params, **kwargs):
            params = super(llmod_wCntrls, self).update(params, **kwargs)
            
            self['state_cov', 0, 0] = params[0] # Single state variance             
            self['obs_cov', 0, 0] = params[1] # Noise variance 
            self['obs_intercept', 0, :] = self.exog @ params[2:len(params)]

    # Define the RW model with controls (for currencies with ONR variation)
    class llmod_noCntrls(sm.tsa.statespace.MLEModel):
    
        def __init__(self, y):
            super(llmod_noCntrls, self).__init__(endog = y,
                                                  k_states=1, 
                                                  k_posdef=1,
                                                  initialization = 'diffuse')
    
                
            # Specify the fixed elements of the state space matrices (things that are assumed)
            self['design', 0, 0] = 1.0 # Design matrix is 2x1 of ones
            self['transition', 0, 0] = 1.0 # Transition and selection matrices are 1x1 of ones
            self['selection', 0, 0] = 1.0         
            
            # Declare which parameters need to be positive (reference param_names to check )
            self.positive_parameters = slice(0,2) 
    
        @property
        def param_names(self):
            param_names =  [
                'state_var',
                'obs_var',
            ]
            return param_names
        
        @property
        def start_params(self):
            params = [
                0.01,
                0.01,
            ]
            return params
        
        # Constrain the variance parameters to be positive and no smaller than "minvar"
        def transform_params(self, unconstrained):
            minvar = 1e-7
            constrained = unconstrained.copy()
            constrained[self.positive_parameters] = constrained[self.positive_parameters] ** 2 + minvar
            return constrained
    
        def untransform_params(self, constrained):
            minvar = 1e-7
            unconstrained = constrained.copy()
            unconstrained[self.positive_parameters] = (unconstrained[self.positive_parameters] - minvar)** 0.5         
            return unconstrained   
        
        # Define how estimated parameters map to the state space matrices 
        def update(self, params, **kwargs):
            params = super(llmod_noCntrls, self).update(params, **kwargs)
            
            self['state_cov', 0, 0] = params[0] # Single state variance             
            self['obs_cov', 0, 0] = params[1] # Noise variance 

    # Estimate the relevant model (StSpMod_wCntrls if there is variation in ONR, StSpMod_NoCntrls if not)
    if onrvar > 1e-10:
        regtype = llmod_wCntrls
        mod = regtype(y, np.reshape(xonr, (len(xonr),1)))
    else:
        regtype = llmod_noCntrls
        mod = regtype(y) # Model with policy variables for other ccys
        
    modfit = optim_details(mod)
    
    # Get the smoothed disturbance terms for main estimates (Table 3) and store in a dataFrame
    meas_dist_smooth = modfit.smoothed_measurement_disturbance.T
    state_dist_smooth = modfit.smoothed_state_disturbance.T
    state_smooth = modfit.smoothed_state.T    

    dfdist = pd.DataFrame({'datefmt' : datesall,
                            'rate' : refextract,
                            'sample' : samp,
                            'y' : y[:,0],
                            'xonr' : xonr,
                           'state_smooth' : state_smooth[:,0],
                           'state_dist_smooth' : state_dist_smooth[:,0],
                           'meas_dist_smooth' : meas_dist_smooth[:,0]
                           })
    
    if onrvar > 1e-10:
        dfdist['yfit'] = dfdist.xonr*modfit.params[-1] + dfdist.state_smooth
    else:
        dfdist['yfit'] = dfdist.state_smooth
        
    dfdist['meas_dist_chck'] = dfdist.y - dfdist.yfit
    
    # Store all relevant results and details for each  model     
    regrslts = pd.DataFrame({'alt' : refextract,
                             'sample' : samp,
                             'date1' : datesall.iloc[0],
                             'dateend' : datesall.iloc[-1],
                             'model' : regtype.__name__,
                             'convergence' : modfit.mle_retvals['converged'],
                             'condnmb' : str("{:e}".format(np.linalg.cond(modfit.cov_params()))),
                             'ressum' : modfit.summary(),
                             'params' : [modfit.params],
                             'tvalues' : [modfit.tvalues],
                             'paramnames' : [modfit.param_names],
                             'llf' : modfit.llf,
                             'dfsignoise' : [dfdist]
                              },
                           index = [0])

    print(refextract, samp, datesall.iloc[0], datesall.iloc[-1],
          dt.datetime.now().time().strftime('%H:%M:%S'))
    return regrslts
