diff --git a/core/qa/CMakeLists.txt b/core/qa/CMakeLists.txt
index 93567ed5908660e865073928194369398c949bb3..69c773be0852e1f8261675e9cd0aa5cf92e20d2a 100644
--- a/core/qa/CMakeLists.txt
+++ b/core/qa/CMakeLists.txt
@@ -13,6 +13,7 @@ set(SRCS
   CbmQaTask.cxx
   CbmQaIO.cxx
   CbmQaUtil.cxx
+  CbmQaCompare.cxx
   checker/CbmQaCheckerCore.cxx
   checker/CbmQaCheckerFileHandler.cxx
   checker/CbmQaCheckerHist1DHandler.cxx
@@ -49,6 +50,7 @@ set(INTERFACE_DEPENDENCIES
 generate_cbm_library()
 
 Install(FILES 
+        CbmQaCompare.h
         CbmQaManager.h
         CbmQaTask.h
         CbmQaCanvas.h
diff --git a/core/qa/CbmQaCompare.cxx b/core/qa/CbmQaCompare.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..3d809f7e7fe10cbe28f0ea4ab159b7682bc5346b
--- /dev/null
+++ b/core/qa/CbmQaCompare.cxx
@@ -0,0 +1,274 @@
+/* Copyright (C) 2023 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   CbmQaCompare.cxx
+/// \brief  A histogram comparison module for the QA task (implementation)
+/// \author S.Zharko <s.zharko@lx-pool.gsi.de>
+/// \since  23.12.2023
+
+#include "CbmQaCompare.h"
+
+#include "CbmQaCanvas.h"
+#include "Logger.h"
+#include "TAxis.h"
+#include "TCanvas.h"
+#include "TError.h"
+#include "TGraphAsymmErrors.h"
+#include "TH1.h"
+#include "TH2.h"
+#include "TLegend.h"
+#include "TMath.h"
+#include "TMultiGraph.h"
+#include "TObject.h"
+#include "TProfile.h"
+#include "TProfile2D.h"
+#include "TStyle.h"
+
+#include <sstream>
+#include <type_traits>
+
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+template<class Obj>
+CbmQaCompare<Obj>::CbmQaCompare(const Obj* pHistL, const Obj* pHistR, int verbose)
+  : fpObjL(pHistL)
+  , fpObjR(pHistR)
+  , fVerbose(verbose)
+{
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+template<class Obj>
+CbmQaCompare<Obj>::~CbmQaCompare()
+{
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+template<class Obj>
+typename CbmQaCompare<Obj>::Result CbmQaCompare<Obj>::operator()(const std::string& opt,
+                                                                 const std::string& optStat) const
+{
+  // ---- Parse options
+  bool bCmpPointByPoint = opt.find("p") != std::string::npos;
+  bool bCmpStatTest     = opt.find("s") != std::string::npos;
+  bool bCmpRatio        = opt.find("r") != std::string::npos;
+
+  Result res;
+
+
+  if constexpr (std::is_base_of_v<TH1, Obj>) {
+    auto CheckAxes = [&](const TAxis* pL, const TAxis* pR, const char* name) {
+      bool ret = true;
+      if (pL->GetNbins() != pR->GetNbins()) {
+        LOG(warn) << "histogram " << name << " has inconsistent bin number with the default histogram";
+        ret = false;
+      }
+      if (pL->GetXmin() != pR->GetXmin()) {
+        LOG(warn) << "histogram " << name << " has inconsistent min of an axis x, y or z with the default histogram";
+        ret = false;
+      }
+      if (pL->GetXmax() != pR->GetXmax()) {
+        LOG(warn) << "histogram " << name << " has inconsistent max of an axis x, y or z with the default histogram";
+        ret = false;
+      }
+      return ret;
+    };
+
+    // ---- Consistency check ------------------------------------------------------------------------------------------
+    if (!(CheckAxes(fpObjL->GetXaxis(), fpObjR->GetXaxis(), fpObjL->GetName())
+          && CheckAxes(fpObjL->GetYaxis(), fpObjR->GetYaxis(), fpObjL->GetName())
+          && CheckAxes(fpObjL->GetZaxis(), fpObjR->GetZaxis(), fpObjL->GetName()))) {
+      res.fConsistent = false;
+      return res;
+    }
+    else {
+      res.fConsistent = true;
+    }
+
+    // ---- Point-by-point comparison ----------------------------------------------------------------------------------
+    if (bCmpPointByPoint || bCmpRatio) {
+      res.fPointByPoint = true;
+      res.fRatioLo      = 1.;
+      res.fRatioUp      = 1.;
+      for (int iBinX = 0; iBinX <= fpObjL->GetNbinsX(); ++iBinX) {
+        for (int iBinY = 0; iBinY <= fpObjL->GetNbinsY(); ++iBinY) {
+          for (int iBinZ = 0; iBinZ <= fpObjL->GetNbinsZ(); ++iBinZ) {
+            auto numBinContent = fpObjL->GetBinContent(iBinX, iBinY, iBinZ);
+            auto denBinContent = fpObjR->GetBinContent(iBinX, iBinY, iBinZ);
+            double ratio       = static_cast<double>(numBinContent) / denBinContent;
+            // Check bin content
+            if (!TMath::IsNaN(numBinContent) && !TMath::IsNaN(denBinContent)) {
+              if (numBinContent != denBinContent) {
+                res.fPointByPoint = false;
+                if (bCmpRatio) {
+                  if (res.fRatioLo > ratio) {
+                    res.fRatioLo = ratio;
+                  }
+                  if (res.fRatioUp < ratio) {
+                    res.fRatioUp = ratio;
+                  }
+                  // NOTE: case numBinContent == denBinContent == 0 is accounted as true
+                }
+              }
+            }
+            else {
+              if (TMath::IsNaN(numBinContent) != TMath::IsNaN(denBinContent)) {
+                res.fPointByPoint = false;
+              }
+            }
+            auto numBinError = fpObjL->GetBinError(iBinX, iBinY, iBinZ);
+            auto denBinError = fpObjR->GetBinError(iBinX, iBinY, iBinZ);
+            // Check bin error
+            if (!TMath::IsNaN(numBinError) && !TMath::IsNaN(denBinError)) {
+              if (numBinError != denBinError) {
+                res.fPointByPoint = false;
+              }
+            }
+            else {
+              if (TMath::IsNaN(numBinError) != TMath::IsNaN(denBinError)) {
+                res.fPointByPoint = false;
+              }
+            }
+          }
+        }
+      }
+    }
+    // ---- Stat test comparison ---------------------------------------------------------------------------------------
+    if (bCmpStatTest) {
+      TString optParam = optStat + "CHI2/NDF";  // Set CHI2/NDF as an output
+      res.fChi2NDF     = fpObjL->Chi2Test(fpObjR, optParam);
+    }
+  }
+  return res;
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+template<class Obj>
+TCanvas* CbmQaCompare<Obj>::GetComparisonCanvas(const std::string& opt)
+{
+  // ---- Constant parameters
+  constexpr int kPadPX         = 500;   // Pad x-size in pixels
+  constexpr int kPadPY         = 500;   // Pad y-size in pixels
+  constexpr double kPadMarginL = 0.15;  // Left margin of a pad
+  constexpr double kPadMarginR = 0.05;  // Right margin of a pad
+  constexpr double kPadMarginB = 0.12;  // Bottom margin of a pad
+  constexpr double kPadMarginT = 0.10;  // Top margin of a pad
+  constexpr Style_t kMarkerL   = 24;
+  constexpr Style_t kMarkerR   = 25;
+  constexpr Style_t kMarkerDif = 20;
+  constexpr Style_t kMarkerRat = 20;
+
+  // ---- Parse options
+  bool bDrawCmp = opt.find("c") != std::string::npos;
+  bool bDrawRat = opt.find("r") != std::string::npos;
+  bool bDrawDif = opt.find("d") != std::string::npos;
+  int nPads     = bDrawCmp + bDrawDif + bDrawRat;
+
+  if (!nPads) {
+    return nullptr;
+  }
+
+  auto* pCanv = new TCanvas("comparison", "comparison", kPadPX * nPads, kPadPY);
+  pCanv->Divide(nPads, 1);
+
+  if constexpr (std::is_base_of_v<TH1, Obj>) {
+    if (bDrawCmp) {
+      pCanv->cd(1);
+      gPad->SetMargin(kPadMarginL, kPadMarginR, kPadMarginB, kPadMarginT);
+      auto* pMGraph = new TMultiGraph(Form("%s_cmp", fpObjL->GetName()), fpObjL->GetTitle());
+      auto* pGrL    = new TGraphAsymmErrors(fpObjL);
+      pGrL->SetLineColor(kOrange + 5);
+      pGrL->SetMarkerColor(kOrange + 5);
+      pGrL->SetMarkerStyle(kMarkerL);
+      auto* pGrR = new TGraphAsymmErrors(fpObjR);
+      pGrR->SetLineColor(kCyan - 2);
+      pGrR->SetMarkerColor(kCyan - 2);
+      pGrR->SetMarkerStyle(kMarkerR);
+      pMGraph->Add(pGrL, "P");
+      pMGraph->Add(pGrR, "P");
+      pMGraph->GetXaxis()->SetTitle(fpObjL->GetXaxis()->GetTitle());
+      pMGraph->GetYaxis()->SetTitle(fpObjL->GetYaxis()->GetTitle());
+      pMGraph->Draw("AP");
+      auto* pLegend = new TLegend(0.55, 0.80, (1. - kPadMarginR), (1. - kPadMarginT));
+      pLegend->SetTextSize(0.035);
+      //pLegend->SetBorderSize(0);
+      pLegend->SetFillStyle(0);
+      pLegend->SetMargin(0.2);
+      pLegend->AddEntry(pGrL, fsLabelL.c_str(), "P");
+      pLegend->AddEntry(pGrR, fsLabelR.c_str(), "P");
+      pLegend->Draw();
+    }
+
+    TH1* pHistL = nullptr;  // Temporary histogram
+    TH1* pHistR = nullptr;  // Temporary histogram
+    if constexpr (std::is_base_of_v<TProfile2D, Obj>) {
+      pHistL = fpObjL->ProjectionXY(Form("tmp_%s", fsLabelL.c_str()));
+      pHistR = fpObjR->ProjectionXY(Form("tmp_%s", fsLabelR.c_str()));
+    }
+    else if constexpr (std::is_base_of_v<TProfile, Obj>) {
+      pHistL = fpObjL->ProjectionX(Form("tmp_%s", fsLabelL.c_str()));
+      pHistR = fpObjR->ProjectionX(Form("tmp_%s", fsLabelR.c_str()));
+    }
+    else {
+      pHistL = static_cast<Obj*>(fpObjL->Clone(Form("tmp_%s", fsLabelL.c_str())));
+      pHistR = static_cast<Obj*>(fpObjR->Clone(Form("tmp_%s", fsLabelR.c_str())));
+    }
+
+    // Ratio plot
+    if (bDrawRat) {
+      pCanv->cd(static_cast<int>(bDrawCmp) + static_cast<int>(bDrawDif) + 1);
+      gPad->SetMargin(kPadMarginL, kPadMarginR, kPadMarginB, kPadMarginT);
+      auto currErrorLevel = gErrorIgnoreLevel;  // Suppress log error about skipping null points
+      gErrorIgnoreLevel   = kError;
+      auto* pGrRat        = new TGraphAsymmErrors(pHistL, pHistR, "pois");
+      gErrorIgnoreLevel   = currErrorLevel;
+      pGrRat->GetXaxis()->SetTitle(pHistL->GetXaxis()->GetTitle());
+      pGrRat->GetYaxis()->SetTitle("ratio");
+      pGrRat->SetTitle(Form("Ratio of \"%s\" and \"%s\"", fsLabelL.c_str(), fsLabelR.c_str()));
+      pGrRat->SetMarkerStyle(kMarkerRat);
+      pGrRat->Draw("AP");
+    }
+
+    // Difference plot
+    if (bDrawDif) {
+      pCanv->cd(static_cast<int>(bDrawCmp) + 1);
+      gPad->SetMargin(kPadMarginL, kPadMarginR, kPadMarginB, kPadMarginT);
+      auto* pDif = static_cast<Obj*>(pHistL->Clone());
+      pDif->Add(pHistR, -1.);
+      auto* pGrDif = new TGraphAsymmErrors(pDif);
+      pGrDif->GetXaxis()->SetTitle(pHistL->GetXaxis()->GetTitle());
+      pGrDif->GetYaxis()->SetTitle("difference");
+      pGrDif->SetTitle(Form("Difference of \"%s\" and \"%s\"", fsLabelL.c_str(), fsLabelR.c_str()));
+      pGrDif->SetMarkerStyle(kMarkerDif);
+      pGrDif->Draw("AP");
+      delete pDif;
+    }
+
+    if (pHistL) {
+      delete pHistL;
+    }
+    if (pHistR) {
+      delete pHistR;
+    }
+  }
+
+  return pCanv;
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+template<class Obj>
+void CbmQaCompare<Obj>::SetObjectLabels(const std::string& labelL, const std::string& labelR)
+{
+  fsLabelL = labelL;
+  fsLabelR = labelR;
+}
+
+template class CbmQaCompare<TH1>;
+template class CbmQaCompare<TH2>;
+template class CbmQaCompare<TProfile>;
diff --git a/core/qa/CbmQaCompare.h b/core/qa/CbmQaCompare.h
new file mode 100644
index 0000000000000000000000000000000000000000..75aef2d067e5da99a62e4df347d43bc9cf36127e
--- /dev/null
+++ b/core/qa/CbmQaCompare.h
@@ -0,0 +1,67 @@
+/* Copyright (C) 2023 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   CbmQaCompare.h
+/// \brief  A histogram comparison module for the QA task (declaration)
+/// \author S.Zharko <s.zharko@lx-pool.gsi.de>
+/// \since  23.12.2023
+
+#pragma once
+
+#include <cassert>
+#include <string>
+
+class TH1;
+class TCanvas;
+
+/// \class  CbmQaCompare
+/// \brief  Class to compare histograms of the QA task with default ones
+/// \tparam Obj Root object type
+template<class Obj>
+class CbmQaCompare {
+ public:
+  struct Result {
+    bool fPointByPoint = true;
+    double fRatioLo    = 1.;    ///< Lower bound of the ratio
+    double fRatioUp    = 1.;    ///< Upper bound of the ratio
+    double fChi2NDF    = 0.;    ///< Chi2/NDF value
+    bool fConsistent   = true;  ///< Consistency flag
+  };
+
+  /// \brief Constructor
+  /// \param pHistL   Left object
+  /// \param pHistR   Right object
+  /// \param verbose  Verbosity level
+  CbmQaCompare(const Obj* pHistL, const Obj* pHistR, int verbose);
+
+  /// \brief Destructor
+  ~CbmQaCompare();
+
+  /// \brief  Compares two histograms, returns a comparison status
+  /// \param  opt Comparison options
+  ///              - p: point-by-point
+  ///              - r: ratio
+  ///              - s[opt]: stat-test
+  /// \param  optStat  Option for the stat. test (see TH1 documentation)
+  /// \return Comparison results. By default the result is true
+  Result operator()(const std::string& opt, const std::string& optStat = "UU") const;
+
+  /// \brief  Creates a comparison canvas
+  /// \param  opt Canvas options
+  ///              - d: draw difference
+  ///              - r: draw ratio
+  TCanvas* GetComparisonCanvas(const std::string& opt);
+
+  /// \brief Set version lables
+  /// \param labelL  Left object label
+  /// \param labelR  Right object label
+  void SetObjectLabels(const std::string& labelL, const std::string& labelR);
+
+ private:
+  const Obj* fpObjL    = nullptr;  ///< Left (new) histogram
+  const Obj* fpObjR    = nullptr;  ///< Right (default) histogram
+  std::string fsLabelL = "this";
+  std::string fsLabelR = "default";
+  int fVerbose         = 0;  ///< Verbosity
+};
diff --git a/core/qa/CbmQaIO.cxx b/core/qa/CbmQaIO.cxx
index 8c3b84699a6d5b79cfd8f09cb96c2bdd6adcb568..e0635994de7933262d51e7d8e6babb654aa7eb1d 100644
--- a/core/qa/CbmQaIO.cxx
+++ b/core/qa/CbmQaIO.cxx
@@ -113,7 +113,6 @@ void CbmQaIO::WriteToFile(TFile* pOutFile) const
 {
   pOutFile->cd();
   for (auto& [pObject, sPath] : (*fpvObjList)) {
-    //LOG(info) << "Writing object " << pObject->GetName() << " -- " << sPath;
     if (!pOutFile->GetDirectory(sPath)) {
       pOutFile->mkdir(sPath);
     }
@@ -124,7 +123,6 @@ void CbmQaIO::WriteToFile(TFile* pOutFile) const
   }
 }
 
-
 // ---------------------------------------------------------------------------------------------------------------------
 //
 void CbmQaIO::MakeQaDirectory(TString sDirectory)
diff --git a/core/qa/CbmQaIO.h b/core/qa/CbmQaIO.h
index 7cb64f07ab5c0cae3ba406e281c6d3681b7aad09..ab6f83317d7db3025328a7c3f12656c1ebe68b8a 100644
--- a/core/qa/CbmQaIO.h
+++ b/core/qa/CbmQaIO.h
@@ -2,10 +2,10 @@
    SPDX-License-Identifier: GPL-3.0-only
    Authors: Sergei Zharko [committer] */
 
-/// @file   CbmQaIO.h
-/// @brief  Module for ROOT objects IO interface (header)
-/// @author S.Zharko <s.zharko@gsi.de>
-/// @since   29.03.2023
+/// \file   CbmQaIO.h
+/// \brief  Module for ROOT objects IO interface (header)
+/// \author S.Zharko <s.zharko@gsi.de>
+/// \since  29.03.2023
 
 #ifndef CbmQaIO_h
 #define CbmQaIO_h 1
@@ -35,7 +35,7 @@
 
 class TFile;
 
-/// @brief  ROOT object IO interface for QA
+/// \brief  ROOT object IO interface for QA
 ///
 /// The class provides interface to write ROOT object into resulted files
 class CbmQaIO {
@@ -48,37 +48,37 @@ class CbmQaIO {
     kSUBDIR    ///< Objects of different type will be stored in different subdirectories like histograms/ canvases/
   };
 
-  /// @brief Constructor
-  /// @param prefixName    Name of the unique prefix
-  /// @param pObjList      List of already created objects
+  /// \brief Constructor
+  /// \param prefixName    Name of the unique prefix
+  /// \param pObjList      List of already created objects
   CbmQaIO(TString prefixName, std::shared_ptr<ObjList_t> pObjList = nullptr);
 
-  /// @brief Destructor
+  /// \brief Destructor
   virtual ~CbmQaIO();
 
-  /// @brief Copy constructor
+  /// \brief Copy constructor
   CbmQaIO(const CbmQaIO&) = delete;
 
-  /// @brief Move constructor
+  /// \brief Move constructor
   CbmQaIO(CbmQaIO&&) = delete;
 
-  /// @brief Copy assignment operator
+  /// \brief Copy assignment operator
   CbmQaIO& operator=(const CbmQaIO&) = delete;
 
-  /// @brief Move assignment operator
+  /// \brief Move assignment operator
   CbmQaIO& operator=(CbmQaIO&&) = delete;
 
 
-  /// @brief Gets config name
+  /// \brief Gets config name
   const char* GetConfigName() const { return fsConfigName.Data(); }
 
-  /// @brief Creates a QA (ROOT) object, using properties defined with a tag in config
-  /// @tparam T       Type of the object
-  /// @param sName    Name of the object
-  /// @param sTitle   Title of the object
-  /// @param args...  The rest of the arguments, which will be passed to the histogram constructor
-  /// @note Tag is defined after the last ';' symbol in the nameBase string
-  /// @example
+  /// \brief Creates a QA (ROOT) object, using properties defined with a tag in config
+  /// \tparam T       Type of the object
+  /// \param sName    Name of the object
+  /// \param sTitle   Title of the object
+  /// \param args...  The rest of the arguments, which will be passed to the histogram constructor
+  /// \note Tag is defined after the last ';' symbol in the nameBase string
+  /// \example
   ///    nambeBase = "/stations/station0/xy_occupancy;xy_station0" will be decayed into:
   ///    1) subdirectory "stations/station0"
   ///    2) name of histogram "catrd_hit_xy_occupancy"
@@ -87,53 +87,56 @@ class CbmQaIO {
   template<typename T, typename... Args>
   T* MakeQaObject(TString sName, TString sTitle, Args... args);
 
-  /// @brief Creates a directory in the output file.
+  /// \brief Creates a directory in the output file.
   ///        Only needed to place directories in a preffered order when doing Write()
   ///        Call it prior to MakeQaObject() calls.
-  /// @param dirName    Name of the directory
-  /// @note Tag is defined after the last ';' symbol in the nameBase string
-  /// @example
+  /// \param dirName    Name of the directory
+  /// \note Tag is defined after the last ';' symbol in the nameBase string
+  /// \example
   ///    dirName = "/stations/station0"
   void MakeQaDirectory(TString sName);
 
-  /// @brief Creates a ROOT object
-  /// @brief Sets config name
-  /// @param name  A path to the config
+  /// \brief Creates a ROOT object
+  /// \brief Sets config name
+  /// \param name  A path to the config
   void SetConfigName(const char* path);
 
-  /// @brief Sets a common root path to the objects in the output file
-  /// @param path  A path to the object
+  /// \brief Sets a common root path to the objects in the output file
+  /// \param path  A path to the object
   void SetRootFolderName(const TString& path) { fsRootFolderName = path.Strip(TString::kBoth, '/'); }
 
-  /// @brief Set storing mode
+  /// \brief Set storing mode
   void SetStoringMode(EStoringMode mode) { fStoringMode = mode; }
 
  protected:
-  /// @brief Function to check, if a property is defined
-  /// @param property  A property to be tested
-  /// @param name      A name of property (used for logging)
-  /// @note  Throws an exception, if property is undefined
+  /// \brief Function to check, if a property is defined
+  /// \param property  A property to be tested
+  /// \param name      A name of property (used for logging)
+  /// \note  Throws an exception, if property is undefined
   template<typename T>
   void CheckProperty(T&& property, const char* name) const;
 
-  /// @brief Applies properties on the histogram created with the MakeQaObject function
-  /// @param pHist  Pointer to the histogram
+  /// \brief Compares the selected objects with default, if the default file is provided
+
+
+  /// \brief Applies properties on the histogram created with the MakeQaObject function
+  /// \param pHist  Pointer to the histogram
   virtual void SetTH1Properties(TH1* pHist);
 
-  /// @brief Applies properties on the histogram created with the MakeQaObject function
-  /// @param pHist  Pointer to the histogram
+  /// \brief Applies properties on the histogram created with the MakeQaObject function
+  /// \param pHist  Pointer to the histogram
   virtual void SetTH2Properties(TH2* pHist);
 
-  /// @brief Applies properties on the profile 2D  created with the MakeQaObject function
-  /// @param pHist  Pointer to the profile
+  /// \brief Applies properties on the profile 2D  created with the MakeQaObject function
+  /// \param pHist  Pointer to the profile
   void SetTProfile2DProperties(TProfile2D* pHist);
 
-  /// @brief Applies properties on the canvas created with the MakeQaObject funciton
-  /// @param pCanv  Pointer to the canvas
+  /// \brief Applies properties on the canvas created with the MakeQaObject funciton
+  /// \param pCanv  Pointer to the canvas
   virtual void SetCanvasProperties(TCanvas* pCanv);
 
-  /// @brief Writes objects into file
-  /// @param pOutFile Pointer to output ROOT file
+  /// \brief Writes objects into file
+  /// \param pOutFile Pointer to output ROOT file
   void WriteToFile(TFile* pOutFile) const;
 
   TString fsRootFolderName = "";  ///< Name of root folder
@@ -146,9 +149,9 @@ class CbmQaIO {
   YAML::Node fConfigNode{};  ///< Configuration node
 
  private:
-  /// @brief Creates and registers a ROOT object
-  /// @param name  A name of the ROOT object, which can contain a sub-directory
-  /// @param args  Other arguments, passed to the ROOT object constructor
+  /// \brief Creates and registers a ROOT object
+  /// \param name  A name of the ROOT object, which can contain a sub-directory
+  /// \param args  Other arguments, passed to the ROOT object constructor
   template<typename T, typename... Args>
   T* ConstructAndRegisterQaObject(TString name, Args... args);
 };
@@ -223,7 +226,12 @@ T* CbmQaIO::ConstructAndRegisterQaObject(TString sName, Args... args)
 
   // Add parent directory
   if (fsRootFolderName.Length() != 0) {
-    sDirectory = fsRootFolderName + "/" + sDirectory;
+    if (sDirectory.Length() != 0) {
+      sDirectory = fsRootFolderName + "/" + sDirectory;
+    }
+    else {
+      sDirectory = fsRootFolderName;
+    }
   }
 
   // Register the object in the list
diff --git a/core/qa/CbmQaManager.cxx b/core/qa/CbmQaManager.cxx
index aac24f8084e3b4d0cffa1e1e41881063605e03bc..ad7231030129b3a2cf23f17b29569198c03d9531 100644
--- a/core/qa/CbmQaManager.cxx
+++ b/core/qa/CbmQaManager.cxx
@@ -27,6 +27,7 @@ void CbmQaManager::Finish()
   using std::right;
   using std::setw;
 
+  // Check QA-tasks
   for (auto* task : *(this->GetListOfTasks())) {
     auto* pQaTask = dynamic_cast<CbmQaTask*>(task);
     if (pQaTask) {
@@ -36,7 +37,8 @@ void CbmQaManager::Finish()
     }
   }
 
-  LOG(info) << fName << ": monitorable status:";
+  // Process check flags
+  LOG(info) << fName << " check-list:";
   fStatus = true;
   for (auto* task : *(this->GetListOfTasks())) {
     auto* pQaTask = dynamic_cast<CbmQaTask*>(task);
@@ -53,6 +55,20 @@ void CbmQaManager::Finish()
       }
     }
   }
+
+  // Process histogram checking
+  if (fpCrossCheckFile.get()) {
+    for (auto* task : *(this->GetListOfTasks())) {
+      auto* pQaTask = dynamic_cast<CbmQaTask*>(task);
+      if (pQaTask) {
+        LOG(info) << "Histograms check for the task " << pQaTask->GetName();
+        pQaTask->CompareQaObjects();
+      }
+    }
+    if (fpCompareOutput.get()) {
+      fpCompareOutput->Close();
+    }
+  }
 }
 
 // ---------------------------------------------------------------------------------------------------------------------
@@ -66,8 +82,48 @@ InitStatus CbmQaManager::Init()
       auto* pQaTask = dynamic_cast<CbmQaTask*>(task);
       if (pQaTask) {
         pQaTask->SetConfigName(fsConfigName.Data());
+        pQaTask->SetVersionTag(fsVersionTag);
+        pQaTask->SetDefaultTag(fsDefaultTag);
+        if (fpCrossCheckFile.get() != nullptr) {
+          pQaTask->SetCheckFile(fpCrossCheckFile);
+        }
+        if (fpCompareOutput.get() != nullptr) {
+          pQaTask->SetCompareOutput(fpCompareOutput);
+        }
       }
     }
   }
   return kSUCCESS;
 }
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void CbmQaManager::OpenCrossCheckFile(const TString& path)
+{
+  if (path.Length()) {
+    auto pFile = std::make_shared<TFile>(path, "READONLY");
+    if (pFile->IsOpen()) {
+      fpCrossCheckFile = std::move(pFile);
+      LOG(info) << fName << ": opening cross-check file " << fpCrossCheckFile->GetName();
+    }
+    else {
+      LOG(error) << fName << ": cross-check file " << path << " was not open";
+    }
+  }
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void CbmQaManager::OpenCompareOutput(const TString& path)
+{
+  if (path.Length()) {
+    auto pFile = std::make_shared<TFile>(path, "RECREATE");
+    if (pFile->IsOpen()) {
+      fpCompareOutput = std::move(pFile);
+      LOG(info) << fName << ": opening comparison output file " << fpCompareOutput->GetName();
+    }
+    else {
+      LOG(error) << fName << ": comparison output file " << path << " was not open";
+    }
+  }
+}
diff --git a/core/qa/CbmQaManager.h b/core/qa/CbmQaManager.h
index 7919ad76db08233a8bfb69c863f7b15b946b1420..06f2f141a2e245d8dacc6421550b7dc7231d22ec 100644
--- a/core/qa/CbmQaManager.h
+++ b/core/qa/CbmQaManager.h
@@ -47,6 +47,18 @@ class CbmQaManager : public FairTask {
   /// \brief Action of the task in the end of the run
   void Finish();
 
+  /// \brief Gets status flag
+  bool GetStatus() const { return fStatus; }
+
+  /// \brief Gets YAML config name
+  const TString& GetConfigName() const { return fsConfigName; }
+
+  /// \brief Gets default tag
+  const TString& GetDefaultTag() const { return fsDefaultTag; }
+
+  /// \brief Gets version tag
+  const TString& GetVersionTag() const { return fsVersionTag; }
+
   /// \brief Task initialization
   InitStatus Init();
 
@@ -55,24 +67,31 @@ class CbmQaManager : public FairTask {
 
   ClassDef(CbmQaManager, 0);
 
-  /// \brief Gets YAML config name
-  const TString& GetConfigName() const { return fsConfigName; }
+  /// \brief Open cross-check file
+  /// \param path  Path to the cross-check file
+  ///
+  /// Opens the cross-check ROOT-file with the QA-chain output, obtained under the default code base.
+  void OpenCrossCheckFile(const TString& path);
 
-  /// \brief Gets status flag
-  bool GetStatus() const { return fStatus; }
+  /// \brief Open comparison output file
+  /// \param path  Path to the comparison output
+  void OpenCompareOutput(const TString& path);
 
   /// \brief Sets YAML config name
   void SetConfigName(const TString& name) { fsConfigName = name; }
 
-  /// \brief Open cross-check file
-  /// \param path  Path to the cross-check file
-  ///
-  /// Opens the cross-check ROOT-file with the QA-chain output, obtained under the default code base.
-  //void OpenCrossCheckFile(const TString& path);
+  /// \brief Sets version tag
+  void SetVersionTag(const TString& tag) { fsVersionTag = tag; }
+
+  /// \brief Sets default tag
+  void SetDefaultTag(const TString& tag) { fsDefaultTag = tag; }
 
  private:
   bool fStatus         = true;  ///< Status of QA: true - all tasks passed, false - at least one of the task failed
   TString fsConfigName = "";    ///< Name of the configuration YAML file (passed to underlying QA tasks)
+  TString fsVersionTag = "";    ///< Version tag (git SHA etc.)
+  TString fsDefaultTag = "";    ///< Default tag (git SHA etc.)
 
-  //std::shared_ptr<TFile> fpCrossCheckFile = nullptr;  ///< A file with default histograms to carry out a cross-check
+  std::shared_ptr<TFile> fpCrossCheckFile = nullptr;  ///< A file with default ROOT objects used for the cross-check
+  std::shared_ptr<TFile> fpCompareOutput  = nullptr;  ///< An output file for histograms cross-check
 };
diff --git a/core/qa/CbmQaTask.cxx b/core/qa/CbmQaTask.cxx
index df38af48b2fea93a5c2cac62f2e32423720d1c9c..ab431fafd4fba28266b802b82a13f1e29e36e2da 100644
--- a/core/qa/CbmQaTask.cxx
+++ b/core/qa/CbmQaTask.cxx
@@ -22,6 +22,7 @@
 #include "TString.h"
 
 #include <array>
+#include <limits>
 #include <regex>
 
 ClassImp(CbmQaTask);
@@ -162,6 +163,92 @@ bool CbmQaTask::CheckRange(TH1* h, double meanMax, double rmsMin, double rmsMax)
   return res;
 }
 
+// ---------------------------------------------------------------------------------------------------------------------
+//
+bool CbmQaTask::CompareQaObjects()
+{
+  YAML::Node tagNode = fConfigNode["check_histograms"];
+
+
+  struct HashTString {
+    std::size_t operator()(const TString& str) const { return std::hash<std::string>()(str.Data()); };
+  };
+
+  // Fill map of ROOT objects, which are going to be checked
+  std::unordered_map<TString, ObjectComparisonConfig, HashTString> mCompareList;
+  // TODO: 28.12.2023 SZh: Add a possibility to define a pack of histograms (in a similar way to check flags)
+  for (const auto& histNode : tagNode) {
+    try {
+      auto histName = fsRootFolderName + "/" + histNode["name"].as<std::string>().c_str();
+      ObjectComparisonConfig entry;
+      if (const auto& subNode = histNode["point_to_point"]) {
+        entry.fPoint = subNode["use"].as<uint8_t>();
+      }
+      if (const auto& subNode = histNode["ratio"]) {
+        entry.fRatio    = subNode["use"].as<uint8_t>();
+        entry.fRatioMin = subNode["min"].as<double>();
+        entry.fRatioMax = subNode["max"].as<double>();
+      }
+      if (const auto& subNode = histNode["chi2Test"]) {
+        entry.fStat       = subNode["use"].as<uint8_t>();
+        entry.fChi2NdfMax = subNode["chi2_ndf_max"].as<double>();
+      }
+      if (const auto& subNode = histNode["canvas"]) {
+        entry.fCanvOpt = "";
+        if (subNode["comp"].as<bool>(false)) {
+          entry.fCanvOpt += "c";
+        }
+        if (subNode["ratio"].as<bool>(false)) {
+          entry.fCanvOpt += "r";
+        }
+        if (subNode["diff"].as<bool>(false)) {
+          entry.fCanvOpt += "d";
+        }
+      }
+      mCompareList[histName] = entry;
+    }
+    catch (const YAML::InvalidNode& exc) {
+      LOG(warn) << fName << "::CompareQaObjects(): attempt to access key which does not exist in the configuration"
+                << " file (message from YAML exception: " << exc.what() << ')';
+    }
+  }
+
+  // Process comparison
+  // Look up the objects in the object list and search for the name in the map
+
+  bool bCheckResult = true;
+  for (auto& [pObject, sPath] : (*fpvObjList)) {
+    if (pObject) {
+
+      TString objName    = sPath + "/" + pObject->GetName();
+      auto configEntryIt = mCompareList.find(objName);
+      if (configEntryIt == mCompareList.end()) {
+        continue;
+      }
+      const auto& configEntry = configEntryIt->second;
+
+      // Get default object
+      auto* pObjCheck = fpCheckFile->Get(objName);
+      if (!pObjCheck) {
+        LOG(warn) << fName << "::CompareQaObjects(): ROOT object " << objName << " is not found in the check file";
+        continue;
+      }
+
+      // Call the comparison function depending on the object type
+      if (dynamic_cast<TProfile*>(pObject)) {
+        bCheckResult &= CompareTwoObjects<TProfile>(pObject, pObjCheck, objName, configEntry);
+      }
+      else if (dynamic_cast<TH2*>(pObject)) {
+        bCheckResult &= CompareTwoObjects<TH2>(pObject, pObjCheck, objName, configEntry);
+      }
+      else if (dynamic_cast<TH1*>(pObject)) {
+        bCheckResult &= CompareTwoObjects<TH1>(pObject, pObjCheck, objName, configEntry);
+      }
+    }
+  }
+  return bCheckResult;
+}
+
 // ---------------------------------------------------------------------------------------------------------------------
 //
 void CbmQaTask::PutSetupNameOnPad(double xMin, double yMin, double xMax, double yMax)
@@ -217,7 +304,7 @@ void CbmQaTask::ReadCheckListFromConfig()
           checkListIt->second.fStatus = it->second.as<bool>();
         }
         else {
-          LOG(warn) << fName << "::ReadCheckListFromConfig: config contains unknown entry " << configEntry;
+          LOG(warn) << fName << "::ReadCheckListFromConfig(): config contains unknown entry " << configEntry;
         }
       }
       ++iConfigEntry;
diff --git a/core/qa/CbmQaTask.h b/core/qa/CbmQaTask.h
index 27d023aa5e7a1f13541b6c047f9e533c7b7a8d1b..23d4d885f98680d798980dde9c2520108a5824c9 100644
--- a/core/qa/CbmQaTask.h
+++ b/core/qa/CbmQaTask.h
@@ -5,18 +5,19 @@
 /// \file   CbmQaTask.h
 /// \brief  A base class for CBM QA task logic
 /// \author S.Zharko <s.zharko@gsi.de>
-/// \data   12.01.2023
-
+/// \since  12.01.2023
 
 #pragma once
 
 #include "CbmEvent.h"
+#include "CbmQaCompare.h"
 #include "CbmQaIO.h"
 #include "CbmQaTable.h"
 #include "FairTask.h"
 #include "Logger.h"
 #include "TCanvas.h"
 #include "TEfficiency.h"
+#include "TFile.h"
 #include "TFolder.h"
 #include "TH1.h"
 #include "TH2.h"
@@ -26,6 +27,7 @@
 #include "TProfile2D.h"
 #include "TProfile3D.h"
 #include "TROOT.h"
+#include "TString.h"
 
 #include <algorithm>
 #include <map>
@@ -51,6 +53,19 @@ class CbmQaTask : public FairTask, public CbmQaIO {
     bool fStatus = false;  ///< Status of the check
   };
 
+  /// \struct ObjectComparisonConfig
+  /// \brief  Contains configuration to compare two root objects (histograms)
+  struct ObjectComparisonConfig {
+    uint8_t fPoint       = 0;
+    uint8_t fRatio       = 0;
+    uint8_t fStat        = 0;
+    double fRatioMin     = 0;
+    double fRatioMax     = std::numeric_limits<double>::max();
+    double fChi2NdfMax   = std::numeric_limits<double>::max();
+    std::string fStatOpt = "UU";
+    std::string fCanvOpt = "";
+  };
+
  public:
   /// \brief  Constructor from parameters
   /// \param  name     Name of the task
@@ -76,48 +91,70 @@ class CbmQaTask : public FairTask, public CbmQaIO {
   /// \brief Move assignment operator
   CbmQaTask& operator=(CbmQaTask&&) = delete;
 
+  /// \brief  Function to check, if the QA results are acceptable
+  ///
+  /// In the function one should provide the check flags with the function StoreCheckResult.
+  virtual void Check() = 0;
+
+  /// \brief Process ROOT objects comparison
+  /// \return true  All the object are consistent
+  /// \return false Some of the objects are inconsistent
+  bool CompareQaObjects();
+
+  /// \brief FairTask: Defines action of the task in the event/TS
+  void Exec(Option_t* /*option*/) override;
+
+  /// \brief FairTask: Defines action of the task in the end of run
+  void Finish() override;
+
+  /// \brief Gets check-list
+  const std::map<std::string, CheckFlags>& GetCheckList() const { return fmCheckList; }
+
+  /// \brief Gets default tag
+  const TString& GetDefaultTag() const { return fsDefaultTag; }
+
   /// \brief Gets name of the setup
   const std::string& GetSetupName() const { return fsSetupName; }
 
-  /// \brief Returns flag, whether MC information is used or not in the task
-  bool IsMCUsed() const { return fbUseMC; }
-
-  /// \brief Sets events suppression flag
-  ///
-  /// By default the QA task runs on the reconstructed events (CbmEvent objects), if the tree of the objects is
-  /// presented in the input. This flag disables the events-based routine, so the QA is executed over the whole
-  /// Time-slice.
-  void SetProcessFullTs(bool bProcessFullTs) { fbProcessFullTs = bProcessFullTs; }
+  /// \brief Gets version tag
+  const TString& GetVersionTag() const { return fsVersionTag; }
 
   /// \brief FairTask: Task initialization in the beginning of the run
   InitStatus Init() override;
 
+  /// \brief Returns flag, whether MC information is used or not in the task
+  bool IsMCUsed() const { return fbUseMC; }
+
+  /// \brief Reads check-list from the configuration file
+  void ReadCheckListFromConfig();
+
   /// \brief FairTask: Task reinitialization
   InitStatus ReInit() override;
 
-  /// \brief FairTask: Defines action of the task in the event/TS
-  void Exec(Option_t* /*option*/) override;
+  /// \brief Sets check-file
+  /// \param pCheckFile  Shared pointer to the cross-check file
+  void SetCheckFile(const std::shared_ptr<TFile>& pCheckFile) { fpCheckFile = pCheckFile; }
 
-  /// \brief FairTask: Defines action of the task in the end of run
-  void Finish() override;
+  /// \brief Sets compare output file
+  /// \param pCompareOutput  Shared pointer to the comparison output file
+  void SetCompareOutput(const std::shared_ptr<TFile>& pCompareOutput) { fpCompareOutput = pCompareOutput; }
 
-  /// \brief Sets name of the setup
-  void SetSetupName(const char* setup) { fsSetupName = setup; }
+  /// \brief Sets version tag
+  void SetVersionTag(const TString& tag) { fsVersionTag = tag; }
 
-  // *****************************************************
-  // ** Functions accessible inside the derived classes **
-  // *****************************************************
+  /// \brief Sets default tag
+  void SetDefaultTag(const TString& tag) { fsDefaultTag = tag; }
 
-  /// \brief  Function to check, if the QA results are acceptable
+  /// \brief Sets events suppression flag
   ///
-  /// In the function one should provide the check flags with the function StoreCheckResult.
-  virtual void Check() = 0;
+  /// By default the QA task runs on the reconstructed events (CbmEvent objects), if the tree of the objects is
+  /// presented in the input. This flag disables the events-based routine, so the QA is executed over the whole
+  /// Time-slice.
+  void SetProcessFullTs(bool bProcessFullTs) { fbProcessFullTs = bProcessFullTs; }
 
-  /// \brief Reads check-list from the configuration file
-  void ReadCheckListFromConfig();
+  /// \brief Sets name of the setup
+  void SetSetupName(const char* setup) { fsSetupName = setup; }
 
-  /// \brief Gets check-list
-  const std::map<std::string, CheckFlags>& GetCheckList() const { return fmCheckList; }
 
  protected:
   /// \brief De-initialize the task
@@ -183,6 +220,18 @@ class CbmQaTask : public FairTask, public CbmQaIO {
   /// \brief De-initializes this task
   void DeInitBase();
 
+  /// \brief Process object comparison
+  /// \tparam Obj Class of the ROOT object
+  /// \param  pObjL   First object (this)
+  /// \param  pObjR   Second object (default)
+  /// \param  objName Name of the object (contains full path to the object)
+  /// \param  cfg     Comparison configuration entry
+  /// \return true  Histograms are equal within selected method
+  /// \return false Histograms differ within one of the comparison methods
+  template<class Obj>
+  bool CompareTwoObjects(const TObject* pObjL, const TObject* pObjR, const TString& objName,
+                         const ObjectComparisonConfig& cfg);
+
   /// \brief A QA check-list map
   ///
   /// The check list is updated with new entries with the StoreCheckResult(tag, result) function, which is to be called
@@ -199,6 +248,12 @@ class CbmQaTask : public FairTask, public CbmQaIO {
   bool fbUseMC         = false;              ///< Flag, if MC is used
   bool fbProcessFullTs = false;              ///< If true, routine runs on the full TS even if the CbmEvent branch is in
 
+  TString fsVersionTag = "";  ///< Version tag (git SHA etc.)
+  TString fsDefaultTag = "";  ///< Default tag (git SHA etc.)
+
+  std::shared_ptr<TFile> fpCheckFile     = nullptr;  ///< A file with default ROOT objects used for the cross-check
+  std::shared_ptr<TFile> fpCompareOutput = nullptr;  ///< An output file for histograms cross-check
+
   ClassDefOverride(CbmQaTask, 0);
 };
 
@@ -233,3 +288,84 @@ bool CbmQaTask::CheckRange(std::string_view name, T var, T varErr, T lo, T hi) c
   }
   return ret;
 }
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+template<class Obj>
+bool CbmQaTask::CompareTwoObjects(const TObject* pObjL, const TObject* pObjR, const TString& objName,
+                                  const ObjectComparisonConfig& cfg)
+{
+  const auto* pHistL = static_cast<const Obj*>(pObjL);
+  const auto* pHistR = dynamic_cast<const Obj*>(pObjR);
+  if (!pHistR) {
+    LOG(error) << fName << "::CompareObjects(): the default " << pObjR->GetName()
+               << " has different type to the new object";
+    return false;
+  }
+  std::string opt = "";
+  if (cfg.fPoint > 0) {
+    opt += "p";
+  }
+  if (cfg.fRatio > 0) {
+    opt += "r";
+  }
+  if (cfg.fStat > 0) {
+    opt += "s";
+  }
+
+  auto comparator = CbmQaCompare<Obj>(pHistL, pHistR, 0);
+  auto out        = comparator(opt, cfg.fStatOpt);
+
+  if (!out.fConsistent) {
+    LOG(info) << fName << ": This and default versions of the object " << pObjL->GetName() << " are incompatible";
+    return false;
+  }
+
+  // Collect status and print log
+  bool res = true;
+  {
+    std::stringstream msg;
+    using std::setw;
+    constexpr const char* kEqual = "\e[1;32mequal\e[0m";
+    constexpr const char* kDiff  = "\e[1;32mdifferent\e[0m";
+    msg << "\tobject " << setw(12) << pObjL->ClassName() << ' ' << setw(50) << objName;
+    if (cfg.fPoint) {
+      msg << ": point-by-point -> " << (out.fPointByPoint ? kEqual : kDiff);
+      if (cfg.fPoint == 2) {
+        res &= out.fPointByPoint;
+      }
+    }
+    if (cfg.fRatio) {
+      bool bOk = (out.fRatioLo >= cfg.fRatioMin && out.fRatioUp <= cfg.fRatioMax);
+      msg << ", ratio -> " << (bOk ? kEqual : kDiff) << "(lo: " << out.fRatioLo << ", up: " << out.fRatioUp << ')';
+      if (cfg.fRatio == 2) {
+        res &= bOk;
+      }
+    }
+    if (cfg.fStat) {
+      bool bOk = out.fChi2NDF <= cfg.fChi2NdfMax;
+      msg << ", stat. test -> " << (bOk ? kEqual : kDiff) << "(chi2/NDF: " << out.fChi2NDF << ')';
+      if (cfg.fStat == 2) {
+        res &= bOk;
+      }
+    }
+    LOG(info) << msg.str();
+  }
+
+  // Write comparison result
+  if (fpCompareOutput.get()) {
+    fpCompareOutput->mkdir(objName);
+    auto* pDir = fpCompareOutput->GetDirectory(objName);
+    pDir->cd();
+    pObjL->Write(Form("%s_%s", pObjL->GetName(), fsVersionTag.Data()));
+    pObjR->Write(Form("%s_%s", pObjL->GetName(), fsDefaultTag.Data()));
+    if (true || !res) {  // Save canvas only if the histograms are different
+      auto* pCanv = comparator.GetComparisonCanvas(cfg.fCanvOpt);
+      if (pCanv) {
+        pCanv->Write(Form("%s_cmp_canvas", pObjL->GetName()));
+        delete pCanv;
+      }
+    }
+  }
+  return res;
+}
diff --git a/macro/mcbm/mcbm_qa.C b/macro/mcbm/mcbm_qa.C
index 1a4723b9f03163274c47368b36d76c1e6b56c066..db7b44a8a16b17237258f0ea5652456fdd2823c4 100644
--- a/macro/mcbm/mcbm_qa.C
+++ b/macro/mcbm/mcbm_qa.C
@@ -18,11 +18,22 @@
 
 // Includes needed for IDE
 #if !defined(__CLING__)
+#include "CbmCaInputQaMuch.h"
+#include "CbmCaInputQaSetup.h"
+#include "CbmCaInputQaSts.h"
+#include "CbmCaInputQaTof.h"
+#include "CbmCaInputQaTrd.h"
+#include "CbmCaOutputQa.h"
 #include "CbmDefs.h"
+#include "CbmKF.h"
 #include "CbmMCDataManager.h"
+#include "CbmMatchRecoToMC.h"
+#include "CbmMuchGeoScheme.h"
+#include "CbmMuchHitFinderQa.h"
 #include "CbmMuchTransportQa.h"
 #include "CbmQaManager.h"
 #include "CbmSetup.h"
+#include "CbmTrackingDetectorInterfaceInit.h"
 
 #include <FairFileSource.h>
 #include <FairMonitor.h>
@@ -33,11 +44,25 @@
 #include <FairRuntimeDb.h>
 #include <FairSystemInfo.h>
 
+#include <TObjString.h>
 #include <TStopwatch.h>
 #endif
 
-void mcbm_qa(Int_t nEvents = 0, TString dataset = "data/mcbm_beam_2020_03_test",
-             TString setupName = "mcbm_beam_2020_03", Bool_t bUseMC = kTRUE, const TString& config = "")
+/* clang-format off */
+/// \brief QA macro execution function
+/// \param nEvents     Number of events to proceed
+/// \param dataset     Prefix of the output files files upstream the simulation/reconstruction chain
+/// \param setupName   Name of the setup
+/// \param bUseMC      Flag for MC (simulation) usage
+/// \param config      QA YAML configuraiton file
+/// \param defaultPath Path to a default QA output, obtained for a given setup and given number of events
+void mcbm_qa(Int_t nEvents = 0, 
+             TString dataset = "data/mcbm_beam_2020_03_test",
+             TString setupName = "mcbm_beam_2020_03",
+             Bool_t bUseMC = kTRUE,
+             TString config = "",
+             TString crossCheckFile = "")
+/* clang-format on */
 {
 
   // ========================================================================
@@ -166,9 +191,17 @@ void mcbm_qa(Int_t nEvents = 0, TString dataset = "data/mcbm_beam_2020_03_test",
   FairMonitor::GetMonitor()->EnableMonitor(kTRUE, monitorFile);
   // ------------------------------------------------------------------------
 
+  // -----   QA manager   ---------------------------------------------------
   auto* qaManager = new CbmQaManager(3);
   qaManager->SetConfigName(qaConfig);
+  if (crossCheckFile.Length()) {
+    qaManager->OpenCrossCheckFile(crossCheckFile);
+    qaManager->OpenCompareOutput("compareResult.root");
+    qaManager->SetDefaultTag("default");
+    qaManager->SetVersionTag("this");  // TODO: read git SHA
+  }
   run->AddTask(qaManager);
+  // ------------------------------------------------------------------------
 
   // -----   MCDataManager (legacy mode)  -----------------------------------
   if (bUseMC) {
diff --git a/macro/qa/configs/qa_tasks_config_mcbm.yaml b/macro/qa/configs/qa_tasks_config_mcbm.yaml
index 351fbcb6e7e39e0c5cd4ef99b084537c788471da..01cac78248d8cd16d8a1c536f377471f2bc1286e 100644
--- a/macro/qa/configs/qa_tasks_config_mcbm.yaml
+++ b/macro/qa/configs/qa_tasks_config_mcbm.yaml
@@ -27,6 +27,15 @@ qa:
       pull_t_station_%d: true
       pull_u_%d_digis: true
       pull_v_%d_digis: true
+    check_histograms:
+      - name: "Station 0/pull_x_st0"
+        point_to_point: { use: 1 }
+        ratio: { use: 1, min: 0.5, max: 1.5 }
+        chi2Test: {use: 1, max: 10. }
+      - name: "Station 0/pull_y_st0"
+        point_to_point: { use: 1 }
+        ratio: { use: 1, min: 0.5, max: 1.5 }
+        chi2Test: {use: 1, max: 10., stat_opt: "UU" }
   CbmCaInputQaMuch:
     check_list:
       station_position_ordering: true
diff --git a/macro/qa/configs/qa_tasks_config_mcbm_beam_2022_05_23_nickel.yaml b/macro/qa/configs/qa_tasks_config_mcbm_beam_2022_05_23_nickel.yaml
index 50f9dc7093e7313b1fbf8aecbbc973eeadfb5d1a..dbe5a6099515592007831cb65694409c7b73f55d 100644
--- a/macro/qa/configs/qa_tasks_config_mcbm_beam_2022_05_23_nickel.yaml
+++ b/macro/qa/configs/qa_tasks_config_mcbm_beam_2022_05_23_nickel.yaml
@@ -2,7 +2,7 @@
 #  SPDX-License-Identifier: GPL-3.0-only
 #  Authors: Sergei Zharko [committer] 
 # 
-## @file   qa_task_config_mcbm.yaml
+## @file   qa_task_config_mcbm_beam_2022_05_23_nickel.yaml
 ## @brief  Configuration file for QA-tasks in mCBM
 ## @since  27.07.2023
 ## @author Sergei Zharko <s.zharko@gsi.de>
@@ -27,6 +27,17 @@ qa:
       pull_t_station_%d: true
       pull_u_%d_digis: true
       pull_v_%d_digis: true
+    check_histograms:
+      - name: "Station 0/pull/pull_x_st0"
+        point_to_point: { use: 1 }
+        ratio: { use: 1, min: 0.5, max: 1.5 }
+        chi2Test: {use: 1, chi2_ndf_max: 10., stat_opt: "UU" }
+        canvas: {comp: true, ratio: true, diff: true}
+      - name: "Station 0/pull/pull_y_st0"
+        point_to_point: { use: 1 }
+        ratio: { use: 0, min: 0.5, max: 1.5 }
+        chi2Test: {use: 1, chi2_ndf_max: 10., stat_opt: "UU" }
+        canvas: {comp: true, ratio: false, diff: true}
   CbmCaInputQaMuch:
     check_list:
       station_position_ordering: true
@@ -68,5 +79,22 @@ qa:
       pull_y_station_%d: true
       pull_t_station_%d: false
       pull_t_station_3: false
+  CbmCaOutputQa:
+    check_histograms:
+      - name: "all/eff_pMC"
+        point_to_point: { use: 1 }
+        ratio: { use: 0, min: 0.5, max: 1.5 }
+        chi2Test: {use: 1, chi2_ndf_max: 10., stat_opt: "WW" }
+        canvas: {comp: true, ratio: true, diff: true}
+      - name: "all/reco_p"
+        point_to_point: { use: 1 }
+        ratio: { use: 0, min: 0.5, max: 1.5 }
+        chi2Test: {use: 1, chi2_ndf_max: 10., stat_opt: "UU" }
+        canvas: {comp: true, ratio: true, diff: true}
+      - name: "ghost/reco_p"
+        point_to_point: { use: 1 }
+        ratio: { use: 0, min: 0.5, max: 1.5 }
+        chi2Test: {use: 1, chi2_ndf_max: 10., stat_opt: "UU" }
+        canvas: {comp: true, ratio: true, diff: true}
 
 ...