diff --git a/src/DynareBison.yy b/src/DynareBison.yy
index 78a087d93405647dcf8ac599e2fb147ef1fa788f..31be0a069c2558bb2585949b38be3d689675c514 100644
--- a/src/DynareBison.yy
+++ b/src/DynareBison.yy
@@ -171,6 +171,7 @@ class ParsingDriver;
 %token NO_IDENTIFICATION_MINIMAL NO_IDENTIFICATION_SPECTRUM NORMALIZE_JACOBIANS GRID_NBR
 %token TOL_RANK TOL_DERIV TOL_SV CHECKS_VIA_SUBSETS MAX_DIM_SUBSETS_GROUPS
 %token MAX_NROWS SQUEEZE_SHOCK_DECOMPOSITION WITH_EPILOGUE
+%token MATCHED_MOMENTS
 
 %token <vector<string>> SYMBOL_VEC
 
@@ -314,6 +315,7 @@ statement : parameters
           | det_cond_forecast
           | var_expectation_model
           | compilation_setup
+          | matched_moments
           ;
 
 dsample : DSAMPLE INT_NUMBER ';'
@@ -3117,6 +3119,22 @@ irf_calibration_item : symbol COMMA symbol COMMA calibration_range ';'
                        { driver.add_irf_calibration_item($1, $3, $6, $8); }
                      ;
 
+matched_moments : MATCHED_MOMENTS ';' matched_moments_list END ';'
+                     { driver.end_matched_moments(); }
+                   ;
+
+matched_moments_list : matched_moments_item
+                        | matched_moments_list matched_moments_item
+                        ;
+
+matched_moments_item : symbol COMMA symbol ';'
+                          { driver.add_matched_moments_item($1, $3, "0"); }
+                        | symbol COMMA symbol '(' signed_integer ')' ';'
+                          { driver.add_matched_moments_item($1, $3, $5); }
+                        | symbol COMMA symbol '(' signed_integer_range ')' ';'
+                          { driver.add_matched_moments_item($1, $3, $5); }
+                        ;
+
 smoother2histval : SMOOTHER2HISTVAL ';'
                    { driver.smoother2histval(); }
                  | SMOOTHER2HISTVAL '(' smoother2histval_options_list ')' ';'
diff --git a/src/DynareFlex.ll b/src/DynareFlex.ll
index 47ccde607f2a1a309c26a223ab60d2d7100c5f59..a1397afc6ba7fe98c5bf3b71db3767a1d851a76a 100644
--- a/src/DynareFlex.ll
+++ b/src/DynareFlex.ll
@@ -224,6 +224,7 @@ DATE -?[0-9]+([ya]|m([1-9]|1[0-2])|q[1-4])
 <INITIAL>ramsey_constraints {BEGIN DYNARE_BLOCK; return token::RAMSEY_CONSTRAINTS;}
 <INITIAL>restrictions {BEGIN DYNARE_BLOCK; return token::RESTRICTIONS;}
 <INITIAL>generate_irfs {BEGIN DYNARE_BLOCK; return token::GENERATE_IRFS;}
+<INITIAL>matched_moments {BEGIN DYNARE_BLOCK; return token::MATCHED_MOMENTS;}
 
  /* For the semicolon after an "end" keyword */
 <INITIAL>; {return Dynare::parser::token_type (yytext[0]);}
diff --git a/src/ParsingDriver.cc b/src/ParsingDriver.cc
index 1165ec40277b73e0f63e0f388eacc64e5fcaa863..9f6fee8907aa199a9a67420fa9254592acbd2dbd 100644
--- a/src/ParsingDriver.cc
+++ b/src/ParsingDriver.cc
@@ -3534,3 +3534,27 @@ ParsingDriver::var_expectation_model()
   var_expectation_model_discount = nullptr;
   var_expectation_model_expression = nullptr;
 }
+
+void
+ParsingDriver::add_matched_moments_item(const string &endo1, const string &endo2, string lags)
+{
+  MatchedMoments::Constraint c;
+
+  check_symbol_is_endogenous(endo1);
+  c.endo1 = mod_file->symbol_table.getID(endo1);
+
+  check_symbol_is_endogenous(endo2);
+  c.endo2 = mod_file->symbol_table.getID(endo2);
+
+  c.lags = move(lags);
+
+  matched_moments_constraints.push_back(c);
+}
+
+void
+ParsingDriver::end_matched_moments()
+{
+  mod_file->addStatement(make_unique<MatchedMoments>(matched_moments_constraints,
+                                                        mod_file->symbol_table));
+  matched_moments_constraints.clear();
+}
diff --git a/src/ParsingDriver.hh b/src/ParsingDriver.hh
index 58818be232eacafb78465e1861a24425e8703183..6c3709a1e0e1e776c6d7c04325ae90dc2d4c954d 100644
--- a/src/ParsingDriver.hh
+++ b/src/ParsingDriver.hh
@@ -179,6 +179,8 @@ private:
   MomentCalibration::constraints_t moment_calibration_constraints;
   //! Temporary storage for irf_calibration
   IrfCalibration::constraints_t irf_calibration_constraints;
+  //! Temporary storage for matched_moments
+  MatchedMoments::constraints_t matched_moments_constraints;
   //! Temporary storage for ramsey_constraints
   RamseyConstraintsStatement::constraints_t ramsey_constraints;
   //! Temporary storage for svar_identification blocks
@@ -878,6 +880,10 @@ public:
   void add_irf_calibration_item(const string &endo, string periods, const string &exo, const pair<expr_t, expr_t> &range);
   //! End a moment_calibration statement
   void end_irf_calibration();
+  //! Add an item of a matched_moments statement
+  void add_matched_moments_item(const string &endo1, const string &endo2, string lags);
+  //! End a matched_moments statement
+  void end_matched_moments();
   //! Add a shock to a group
   void add_shock_group_element(string name);
   //! Add a set of shock groups
diff --git a/src/Shocks.cc b/src/Shocks.cc
index ec946f13ef567d4df8a00b5969ce8c2acc30dc9e..1ec6eb1b7cc63e977144b381c6cea71a41474d07 100644
--- a/src/Shocks.cc
+++ b/src/Shocks.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2003-2019 Dynare Team
+ * Copyright © 2003-2020 Dynare Team
  *
  * This file is part of Dynare.
  *
@@ -600,6 +600,44 @@ IrfCalibration::writeJsonOutput(ostream &output) const
          << "}";
 }
 
+MatchedMoments::MatchedMoments(constraints_t constraints_arg,
+                                     const SymbolTable &symbol_table_arg)
+  : constraints{move(constraints_arg)}, symbol_table{symbol_table_arg}
+{
+}
+
+void
+MatchedMoments::writeOutput(ostream &output, const string &basename, bool minimal_workspace) const
+{
+  output << "matched_moments_ = {" << endl;
+  for (const auto &c : constraints)
+    {
+      output << "'" << symbol_table.getName(c.endo1) << "', "
+             << "'" << symbol_table.getName(c.endo2) << "', "
+             << c.lags << ", "
+             << endl;
+    }
+  output << "};" << endl;
+}
+
+void
+MatchedMoments::writeJsonOutput(ostream &output) const
+{
+  output << R"({"statementName": "matched_moments")"
+         << R"(, "matched_moments_criteria": [)";
+  for (auto it = constraints.begin(); it != constraints.end(); ++it)
+    {
+      if (it != constraints.begin())
+        output << ", ";
+      output << R"({"endogenous1": ")" << symbol_table.getName(it->endo1) << R"(")"
+             << R"(, "endogenous2": ")" << symbol_table.getName(it->endo2) << R"(")"
+             << R"(, "lags": ")" << it->lags << R"(")"             
+             << "}";
+    }
+  output << "]"
+         << "}";
+}
+
 ShockGroupsStatement::ShockGroupsStatement(group_t shock_groups_arg, string name_arg)
   : shock_groups{move(shock_groups_arg)}, name{move(name_arg)}
 {
diff --git a/src/Shocks.hh b/src/Shocks.hh
index 38ffb8b84dfa36750441c727ba915eca5fb27fb5..f92aa341c36f00344a1a5c08170ed7c78b1411ef 100644
--- a/src/Shocks.hh
+++ b/src/Shocks.hh
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2003-2019 Dynare Team
+ * Copyright © 2003-2020 Dynare Team
  *
  * This file is part of Dynare.
  *
@@ -149,6 +149,26 @@ public:
   void writeJsonOutput(ostream &output) const override;
 };
 
+class MatchedMoments : public Statement
+{
+public:
+  struct Constraint
+  {
+    int endo1, endo2;
+    string lags;    
+  };
+  using constraints_t = vector<Constraint>;
+private:
+  constraints_t constraints;
+  const SymbolTable &symbol_table;
+public:
+  MatchedMoments(constraints_t constraints_arg,
+                    const SymbolTable &symbol_table_arg);
+  void writeOutput(ostream &output, const string &basename, bool minimal_workspace) const override;
+  void writeJsonOutput(ostream &output) const override;
+};
+
+
 class ShockGroupsStatement : public Statement
 {
 public: