2.10. Optimal (optimized) simple rules
An optimal simple rule (OSR) keeps the model as written – including a simple, parametric feedback rule such as a Taylor rule – and chooses the rule coefficients that minimise a policy loss function. Unlike fully optimal policy (see Optimal (Ramsey) policy), the model still has as many equations as endogenous variables: no equation is dropped, only some of its parameters are picked optimally.
2.10.1. The loss function
The loss is supplied to optimal_simple_rule at solve time, as a string in
terms of the model’s variables and parameters (it is the planner’s
objective, i.e. RISE maximises it – write a welfare measure, or minus a
quadratic loss). A discount factor can be attached; it does not affect the
ranking of unconditional losses, so it is often left at the default
0.99:
% minus a weighted quadratic loss in inflation, the output gap and the
% change in the policy rate
Loss = '-.5*(1*PAI^2 + .3*Y^2 + 0.9*DI^2)';
% with an explicit discount factor:
Loss = {0.99, '-.5*(1*PAI^2 + .3*Y^2 + 0.9*DI^2)'};
Equivalently, the objective can be declared inside the model file as
planner_objective{discount = 0.99} -.5*(1*PAI^2 + .3*Y^2 + 0.9*DI^2);.
The rule coefficients to optimise are listed the way estimated parameters are
– through a priors structure giving each one a support (start value,
lower bound, upper bound):
priors = struct();
priors.gam_lag = {0.60, 0.00, 1}; % {start, lb, ub}
priors.gam_y = {0.50, 0.00, 10};
2.10.2. (Approximate) analytical loss
If the third argument is empty, optimal_simple_rule minimises the
theoretical (unconditional) loss – computed from the model’s
(perturbation-implied) second moments, with no simulation:
m = rise('Canonical_osr');
m = set(m, 'parameters', p); % p: a name/value list
mest = optimal_simple_rule(m, Loss, [], 'priors', priors);
% inspect the optimised rule coefficients
print_estimation_results(mest)
2.10.3. Simulated loss
Passing a shocks database (or a struct / function handle producing one) as the
third argument makes optimal_simple_rule minimise the conditional
(simulated) loss along those shock paths instead – useful for nonlinear models
or when you care about a specific scenario:
shocks_db = ... ; % e.g. a ts of shock draws
mest = optimal_simple_rule(m, Loss, shocks_db, 'priors', priors);
You can evaluate the loss of any parameterisation directly with
calculate_loss(m, Loss) (unconditional) or calculate_loss(m, Loss, shocks_db)
(conditional) – handy for comparing a hand-chosen rule with the optimised one.
2.10.4. OSR and indirect inference
Because OSR is solved exactly like an estimation problem – an objective minimised over a set of parameters with bounds – it composes with RISE’s estimation machinery: the same optimisers (see Posterior maximization) are available, and OSR can be combined with indirect inference (choosing rule coefficients to match empirical moments or impulse responses) by supplying the corresponding objective.
2.10.5. OSR with a user-defined objective
optimal_simple_rule takes the loss as an expression – the string (or
{discount, string} cell) described above; it does not accept a function
handle. If the objective you want cannot be written that way – for example
matching empirical moments or impulse responses – use indirect inference directly, treating the rule coefficients
as the estimated parameters. There the objective is a function handle, with
the signature
[critmin, retcode] = myobjective(m)
where m is the solved model object, critmin is the scalar criterion to
be minimised, and retcode is 0 when the evaluation succeeded. The rule
coefficients are selected exactly as in any indirect-inference exercise (supply
them through priors); see the indirect-inference page for a worked example.
2.10.6. Policy frontiers
To trace a policy frontier – how the model’s volatilities trade off as a
preference parameter (a weight in the loss, the persistence of the rule, …)
is varied – use frontier:
% vary the weight on the output gap over a 50-point grid between 0 and 2
f = frontier(mest, 'lamb_y', [0, 2]);
% or pass an explicit grid
f = frontier(mest, 'lamb_y', linspace(0, 2, 21));
% use simulated rather than theoretical moments
f = frontier(mest, 'lamb_y', [0, 2], true);
f is a structure of the standard deviations of every model variable at each
grid point (plus a stats__ sub-structure with the grid, the number of
simulation periods, and a per-point return code); plot the columns of interest
against each other to get the frontier.