Exogenous variable in the state equation in statespace MLEmodel in statsmodels I'm trying to fit the following model:
$y_t = \left[\begin{matrix} (1-w) & 1 & w \end{matrix}\right] \left[\begin{matrix} d_t  \\ \mu_t \\ m_t \end{matrix}\right] + \mathcal{N}(0,\sigma_\eta^2) $
$ \left[\begin{matrix} d_{t+1} \\ \mu_{t+1} \\ m_{t+1} \end{matrix}\right] = \left[\begin{matrix} 
(1-\alpha) & \alpha & 0 \\ 
0 & 1 & 0 \\
0& 0 & 1
\end{matrix}\right]
\left[\begin{matrix} 
d_t \\ \mu_t\\ m_t 
\end{matrix}\right] 
+ \mathcal{N}\left(0,\left[\begin{matrix} \sigma_\varepsilon^2 & 0 & 0  \\
0 & \sigma_\xi^2 & 0 \\
0 & 0 & 0\end{matrix}\right]\right) $
Where $y_t$ is a weighted average of a state variable $d_t$ and an exogenous variable $m_t$, but I have trouble putting this into the proposed custom statespace models as proposed in https://www.statsmodels.org/dev/examples/notebooks/generated/statespace_custom_models.html.
I don't understand how to implement the exogenous variable $m_t$ into the state equation. Below is the code I have got so far, where $m_t$ is not yet integrated.
class TVRegressionExtended(sm.tsa.statespace.MLEModel):
def __init__(self, y_t, m_t):
    exog = np.c_[m_t]
    
    super(TVRegressionExtended, self).__init__(
        endog=y_t, exog=exog, k_states=3,
        initialization='diffuse')
    
    #These have ok shape. Placeholders since I'm changing them
    #in the update() function
    self.ssm['design'] = np.array([1, 1, 1])
    self.ssm['selection',:2,:2] = np.eye(2)
    self.ssm['transition'] = np.array([[1, 1, 0],
                                       [0, 1, 0],
                                       [0, 0, 1]]) #{[0,0], [0,1]} = {1-alpha, alpha} 
                                                   #will be changed through update

    #Positive params
    self.positive_parameters = slice(0, 3)

@property
def param_names(self):
    return ['std.measure', 'std.return', 'std.mean',
            'alpha','weight']

@property
def start_params(self):
    params = np.r_[0.001, 0.001, 0.001, 0.8, 0.2]
    return params

def transform_params(self, unconstrained):
    constrained = unconstrained.copy()
    constrained[self.positive_parameters] = constrained[self.positive_parameters]**2
    return constrained

def untransform_params(self, constrained):
    unconstrained = constrained.copy()
    unconstrained[self.positive_parameters] = unconstrained[self.positive_parameters]**0.5
    return unconstrained

def update(self, params, **kwargs):
    params = super(TVRegressionExtended, self).update(params, **kwargs)
    self['obs_cov', 0, 0] = params[0]
    self['state_cov',:2,:2] = np.diag(params[1:3])
    self['transition', 0, 0] = params[3]
    self['transition', 0, 1] = 1-params[3]
    self['design', 0,0] = 1-params[4]
    self['design', 0,2] = params[4]

I think the solution is to time-vary the transition matrix in some way, but I can't seem to figure out how.
 A: There's no need to make the exogenous variable part of the state vector. Instead, you can write the model as:
$y_t = w m_t +  \left[\begin{matrix} (1-w) & 1 \end{matrix}\right] \left[\begin{matrix} d_t  \\ \mu_t \end{matrix}\right] + \mathcal{N}(0,\sigma_\eta^2) $
$ \left[\begin{matrix} d_{t+1} \\ \mu_{t+1} \end{matrix}\right] = \left[\begin{matrix} 
(1-\alpha) & \alpha \\ 
0 & 1
\end{matrix}\right]
\left[\begin{matrix} 
d_t \\ \mu_t
\end{matrix}\right] 
+ \mathcal{N}\left(0,\left[\begin{matrix} \sigma_\varepsilon^2 & 0  \\
0 & \sigma_\xi^2\end{matrix}\right]\right) $
and put the $w m_t$ component into the observation intercept (self['obs_intercept']). For example:
Edit: this has now been updated to set k_exog and add a clone method, so that forecasting works.
class TVRegressionExtended(sm.tsa.statespace.MLEModel):
    def __init__(self, y_t, exog):
        exog = np.c_[exog]
        self.k_exog = 1
        
        super(TVRegressionExtended, self).__init__(
            endog=y_t, exog=exog, k_states=2,
            initialization='diffuse')
        
        #These have ok shape. Placeholders since I'm changing them
        #in the update() function
        self.ssm['design'] = np.array([1, 1])
        self.ssm['selection',:2,:2] = np.eye(2)
        self.ssm['transition'] = np.array([[1, 1],
                                           [0, 1]])

        #Positive params
        self.positive_parameters = slice(0, 3)
        
    def clone(self, endog, exog=None, **kwargs):
        return self._clone_from_init_kwds(endog, exog=exog, **kwargs)

    @property
    def param_names(self):
        return ['std.measure', 'std.return', 'std.mean',
                'alpha','weight']

    @property
    def start_params(self):
        params = np.r_[0.001, 0.001, 0.001, 0.8, 0.2]
        return params

    def transform_params(self, unconstrained):
        constrained = unconstrained.copy()
        constrained[self.positive_parameters] = constrained[self.positive_parameters]**2
        return constrained

    def untransform_params(self, constrained):
        unconstrained = constrained.copy()
        unconstrained[self.positive_parameters] = unconstrained[self.positive_parameters]**0.5
        return unconstrained

    def update(self, params, **kwargs):
        params = super(TVRegressionExtended, self).update(params, **kwargs)

        self['obs_cov', 0, 0] = params[0]
        self['obs_intercept'] = np.reshape(params[4] * self.exog, (1, self.nobs))

        self['state_cov',:2,:2] = np.diag(params[1:3])
        self['transition', 0, 0] = 1 - params[3]
        self['transition', 0, 1] = params[3]
        self['design', 0,0] = 1 - params[4]

