diff --git a/Tracking/Acts/ActsConfig/python/ActsAnalysisConfig.py b/Tracking/Acts/ActsConfig/python/ActsAnalysisConfig.py
index bf81afdfab92224d1b80d2af506e3a893dee37bc..90aa6e1b3430d962e63bb56c437fd71822bbb8f6 100644
--- a/Tracking/Acts/ActsConfig/python/ActsAnalysisConfig.py
+++ b/Tracking/Acts/ActsConfig/python/ActsAnalysisConfig.py
@@ -38,7 +38,67 @@ def ActsTrackAnalysisAlgCfg(flags,
 
     acc.merge(helper.result())
     return acc
-    
+
+def ActsTrackParticleAnalysisAlgCfg(flags,
+                                    name: str = 'ActsTrackParticleAnalysisAlg',
+                                    **kwargs) -> ComponentAccumulator:
+    acc = ComponentAccumulator()
+    kwargs.setdefault('MonitorTrackStateCounts',True)
+    kwargs.setdefault('TrackParticleLocation', 'ActsTrackParticles')
+
+    kwargs.setdefault('MonGroupName', kwargs['TrackParticleLocation'])
+
+    from AthenaMonitoring import AthMonitorCfgHelper
+    helper = AthMonitorCfgHelper(flags, 'TrackParticleAnalysisAlgCfg')
+    monitoringAlgorithm = helper.addAlgorithm(CompFactory.ActsTrk.TrackParticleAnalysisAlg, name, **kwargs)
+    monitoringGroup = helper.addGroup(monitoringAlgorithm, kwargs['MonGroupName'], '/'+name+'/')
+
+    monitoringGroup.defineHistogram('pt', title='TrackParticle pt;pt (MeV);Entries', type='TH1F', path=kwargs['MonGroupName'],
+                                    xbins=100, xmin=0, xmax=10e3)
+    monitoringGroup.defineHistogram('eta', title='TrackParticle eta;eta;Entries', type='TH1F', path=kwargs['MonGroupName'],
+                                    xbins=50, xmin=-4, xmax=4)
+
+    # hit counts
+    monitoringGroup.defineHistogram('pixelHits', title='Number of pixel hits;N;Entries', type='TH1I', path=kwargs['MonGroupName'],
+                                    xbins=50, xmin=-1, xmax=49)
+    monitoringGroup.defineHistogram('innermostHits', title='Number of innermost pixel hits;N;Entries', type='TH1I', path=kwargs['MonGroupName'],
+                                    xbins=6, xmin=-1, xmax=5)
+    monitoringGroup.defineHistogram('nextToInnermostHits', title='Number of next-to-innermost pixel hits;N;Entries', type='TH1I',
+                                    path=kwargs['MonGroupName'], xbins=6, xmin=-1, xmax=5)
+    monitoringGroup.defineHistogram('expectInnermostHit', title='Innermost pixel hit expected;N;Entries', type='TH1I', path=kwargs['MonGroupName'],
+                                    xbins=3, xmin=-1, xmax=2)
+    monitoringGroup.defineHistogram('expectNextToInnermostHit', title='Next-to-innermost pixel hit expected;N;Entries', type='TH1I',
+                                    path=kwargs['MonGroupName'],xbins=3, xmin=-1, xmax=2)
+
+    if kwargs['MonitorTrackStateCounts'] :
+        # have to add artifical dependency, because the ActsTracks are created by
+        # a special reader algorithm from the various component branches, but the
+        # element links pointing to the ActsTracks would not add this dependency
+        # by themselves.
+        if 'ExtraInputs' not in kwargs :
+            tracks_name = kwargs['TrackParticleLocation']
+            if tracks_name[-len('TrackParticles'):] == 'TrackParticles' :
+                kwargs.setdefault('ExtraInputs',{('ActsTrk::TrackContainer',tracks_name[:-len('TrackParticles')]+'Tracks')})
+
+        monitoringGroup.defineHistogram('States', title='Number of states on track;N;Entries', type='TH1I', path=kwargs['MonGroupName'],
+                                        xbins=50, xmin=0, xmax=50)
+        monitoringGroup.defineHistogram('Measurements', title='Number of measurements on track;N;Entries', type='TH1I', path=kwargs['MonGroupName'],
+                                        xbins=50, xmin=0, xmax=50)
+        monitoringGroup.defineHistogram('Parameters', title='Number of parameters on track;N;Entries', type='TH1I', path=kwargs['MonGroupName'],
+                                        xbins=50, xmin=0, xmax=50)
+        monitoringGroup.defineHistogram('Outliers', title='Number of outliers on track;N;Entries', type='TH1I', path=kwargs['MonGroupName'],
+                                        xbins=50, xmin=0, xmax=50)
+        monitoringGroup.defineHistogram('Holes', title='Number of holes on track;N;Entries', type='TH1I', path=kwargs['MonGroupName'],
+                                        xbins=50, xmin=0, xmax=50)
+        monitoringGroup.defineHistogram('SharedHits', title='Number of shared hits on track;N;Entries', type='TH1I', path=kwargs['MonGroupName'],
+                                        xbins=50, xmin=0, xmax=50)
+        monitoringGroup.defineHistogram('MaterialStates', title='Number of material states on track;N;Entries', type='TH1I', path=kwargs['MonGroupName'],
+                                        xbins=50, xmin=0, xmax=50)
+    acc.merge(helper.result())
+
+    acc.addEventAlgo( CompFactory.ActsTrk.TrackParticleAnalysisAlg(name, **kwargs) )
+    return acc
+
 def ActsHgtdClusterAnalysisAlgCfg(flags,
                                   name: str = "ActsHgtdClusterAnalysisAlg",
                                   **kwargs) -> ComponentAccumulator:
diff --git a/Tracking/Acts/ActsConfig/test/ActsPersistifyEDM.sh b/Tracking/Acts/ActsConfig/test/ActsPersistifyEDM.sh
index 33490ece148469781b8dc948f6b50f9bb151bf97..616565011ef05defcddcf5c881c06b6d2f659ffa 100755
--- a/Tracking/Acts/ActsConfig/test/ActsPersistifyEDM.sh
+++ b/Tracking/Acts/ActsConfig/test/ActsPersistifyEDM.sh
@@ -29,7 +29,9 @@ ActsReadEDM.py \
    readClusters=True \
    readSpacePoints=True \
    readTracks=True \
-   tracks="ActsTracks"
+   tracks="ActsTracks" \
+   readTrackParticles=True \
+   trackParticles="ActsCombinedTracksParticlesAlt"
 
 rc=$?
 if [ $rc != 0 ]; then
diff --git a/Tracking/Acts/ActsMonitoring/src/TrackParticleAnalysisAlg.cxx b/Tracking/Acts/ActsMonitoring/src/TrackParticleAnalysisAlg.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..22d8c1f1c3b136021c7da2a5f2aa912fa01b3800
--- /dev/null
+++ b/Tracking/Acts/ActsMonitoring/src/TrackParticleAnalysisAlg.cxx
@@ -0,0 +1,130 @@
+/*
+  Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
+*/
+
+#include "TrackParticleAnalysisAlg.h"
+#include "ActsEvent/TrackContainer.h"
+#include "SGTools/CurrentEventStore.h"
+#include <iostream>
+#include <sstream>
+#include <iomanip>
+
+namespace {
+   int getSummaryValue(const xAOD::TrackParticle &track_particle, xAOD::SummaryType summary_type) {
+      uint8_t tmp;
+      return track_particle.summaryValue(tmp, summary_type) ? static_cast<int>(tmp) : -1;
+   }
+}
+
+
+namespace ActsTrk {
+
+  TrackParticleAnalysisAlg::TrackParticleAnalysisAlg(const std::string& name, ISvcLocator* pSvcLocator)
+    : AthMonitorAlgorithm(name, pSvcLocator)
+  {}
+
+  StatusCode TrackParticleAnalysisAlg::initialize() {
+    ATH_MSG_DEBUG("Initializing " << name() << " ...");
+
+    ATH_MSG_VERBOSE("Properties:");
+    ATH_MSG_VERBOSE(m_tracksKey);
+
+    ATH_CHECK(m_tracksKey.initialize());
+
+    ATH_MSG_VERBOSE("Monitoring settings ...");
+    ATH_MSG_VERBOSE(m_monGroupName);
+
+    return AthMonitorAlgorithm::initialize();
+  }
+
+  StatusCode TrackParticleAnalysisAlg::fillHistograms(const EventContext& ctx) const {
+    ATH_MSG_DEBUG( "Filling Histograms for " << name() << " ... " );
+
+    // Retrieve the tracks
+    SG::ReadHandle<xAOD::TrackParticleContainer> trackParticleskHandle = SG::makeHandle(m_tracksKey, ctx);
+    ATH_CHECK(trackParticleskHandle.isValid());
+    const xAOD::TrackParticleContainer *track_particles = trackParticleskHandle.cptr();
+
+    if (m_monitorTrackStateCounts) {
+       for (const xAOD::TrackParticle *track_particle : *track_particles) {
+          monitorTrackStateCounts(*track_particle);
+       }
+    }
+    auto monitor_pt = Monitored::Collection("pt", *track_particles,
+                                            [](const xAOD::TrackParticle *tp){return static_cast<double>(tp->pt()); } );
+    auto monitor_eta = Monitored::Collection("eta", *track_particles,
+                                             [](const xAOD::TrackParticle *tp){return static_cast<double>(tp->eta()); } );
+
+    auto monitor_pixelHits = Monitored::Collection("pixelHits", *track_particles,
+                                                   [](const xAOD::TrackParticle *tp){
+                                                      return getSummaryValue(*tp,xAOD::numberOfPixelHits); });
+
+    auto monitor_innermostHits = Monitored::Collection("innermostHits", *track_particles,
+                                                       [](const xAOD::TrackParticle *tp){
+                                                          return getSummaryValue(*tp,xAOD::numberOfInnermostPixelLayerHits); });
+
+    auto monitor_nextToInnermostHits = Monitored::Collection("nextToInnermostHits", *track_particles,
+                                                       [](const xAOD::TrackParticle *tp){
+                                                          return getSummaryValue(*tp,xAOD::numberOfNextToInnermostPixelLayerHits);});
+
+    auto monitor_expectInnermost = Monitored::Collection("expectInnermostHit", *track_particles,
+                                             [](const xAOD::TrackParticle *tp){
+                                                return getSummaryValue(*tp,xAOD::expectInnermostPixelLayerHit); });
+
+    auto monitor_expectNextToInnermost = Monitored::Collection("expectNextToInnermostHit", *track_particles,
+                                             [](const xAOD::TrackParticle *tp){
+                                                return getSummaryValue(*tp,xAOD::expectNextToInnermostPixelLayerHit); });
+
+    fill(m_monGroupName.value(),monitor_pt,monitor_eta, monitor_pixelHits,  monitor_innermostHits, monitor_nextToInnermostHits,
+         monitor_expectInnermost, monitor_expectNextToInnermost);
+    return StatusCode::SUCCESS;
+  }
+
+  void TrackParticleAnalysisAlg::monitorTrackStateCounts(const xAOD::TrackParticle &track_particle) const {
+     static const SG::AuxElement::ConstAccessor<ElementLink<ActsTrk::TrackContainer> > actsTrackLink("actsTrack");
+
+     ElementLink<ActsTrk::TrackContainer> link_to_track = actsTrackLink(track_particle);
+     // to ensure that the code does not suggest something stupid (i.e. creating an unnecessary copy)
+     static_assert( std::is_same<ElementLink<ActsTrk::TrackContainer>::ElementConstReference,
+                    std::optional<ActsTrk::TrackContainer::ConstTrackProxy> >::value);
+     std::optional<ActsTrk::TrackContainer::ConstTrackProxy> optional_track = *link_to_track;
+     if (optional_track.has_value()) {
+        ActsTrk::TrackContainer::ConstTrackProxy track = optional_track.value();
+        std::array<uint8_t, Acts::NumTrackStateFlags+1> counts{};
+
+        const ActsTrk::TrackContainer::ConstTrackProxy::IndexType
+           lastMeasurementIndex = track.tipIndex();
+
+        track.container().trackStateContainer().visitBackwards(
+                 lastMeasurementIndex,
+                 [&counts
+                  ](const typename ActsTrk::TrackStateBackend::ConstTrackStateProxy &state) -> void
+                 {
+                    Acts::ConstTrackStateType flag = state.typeFlags();
+                    ++counts[Acts::NumTrackStateFlags];
+                    for (unsigned int flag_i=0; flag_i<Acts::NumTrackStateFlags; ++flag_i) {
+                       if (flag.test(flag_i)) {
+                          if (flag_i == Acts::TrackStateFlag::HoleFlag) {
+                             if (!state.hasReferenceSurface() || !state.referenceSurface().associatedDetectorElement()) continue;
+                          }
+                          ++counts[flag_i];
+                       }
+                    }
+                 });
+        auto monitor_states = Monitored::Scalar<int>("States",counts[Acts::TrackStateFlag::NumTrackStateFlags]);
+        auto monitor_measurement = Monitored::Scalar<int>("Measurements",counts[Acts::TrackStateFlag::MeasurementFlag]);
+        auto monitor_parameter = Monitored::Scalar<int>("Parameters",counts[Acts::TrackStateFlag::ParameterFlag]);
+        auto monitor_outlier = Monitored::Scalar<int>("Outliers",counts[Acts::TrackStateFlag::OutlierFlag]);
+        auto monitor_hole = Monitored::Scalar<int>("Holes",counts[Acts::TrackStateFlag::HoleFlag]);
+        auto monitor_material = Monitored::Scalar<int>("MaterialStates",counts[Acts::TrackStateFlag::MaterialFlag]);
+        auto monitor_sharedHit = Monitored::Scalar<int>("SharedHits",counts[Acts::TrackStateFlag::SharedHitFlag]);
+        fill(m_monGroupName.value(), monitor_states, monitor_measurement, monitor_parameter, monitor_outlier, monitor_hole,
+             monitor_material, monitor_sharedHit);
+     }
+     else {
+        ATH_MSG_WARNING("Invalid track link for particle  " << track_particle.index());
+     }
+  }
+
+
+}
diff --git a/Tracking/Acts/ActsMonitoring/src/TrackParticleAnalysisAlg.h b/Tracking/Acts/ActsMonitoring/src/TrackParticleAnalysisAlg.h
new file mode 100644
index 0000000000000000000000000000000000000000..f7e2c6b448fcfbc9318b7db7e9b8f84dcba836d5
--- /dev/null
+++ b/Tracking/Acts/ActsMonitoring/src/TrackParticleAnalysisAlg.h
@@ -0,0 +1,37 @@
+/*
+  Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
+*/
+
+#ifndef ACTSTRKANALYSIS_TRACKPARTICLEANALYSISALG_H
+#define ACTSTRKANALYSIS_TRACKPARTICLEANALYSISALG_H
+
+#include "AthenaMonitoring/AthMonitorAlgorithm.h"
+#include "xAODTracking/TrackParticleContainer.h"
+#include "StoreGate/ReadHandleKey.h"
+
+namespace ActsTrk {
+
+  class TrackParticleAnalysisAlg final :
+    public AthMonitorAlgorithm {
+  public:
+    TrackParticleAnalysisAlg(const std::string& name, ISvcLocator* pSvcLocator);
+    virtual ~TrackParticleAnalysisAlg() override = default;
+
+    virtual StatusCode initialize() override;
+    virtual StatusCode fillHistograms(const EventContext& ctx) const override;
+
+  private:
+    void monitorTrackStateCounts(const xAOD::TrackParticle &track_particle) const;
+
+    SG::ReadHandleKey<xAOD::TrackParticleContainer> m_tracksKey {this, "TrackParticleLocation", "",
+        "Input track particle collection"};
+    Gaudi::Property< std::string > m_monGroupName
+      {this, "MonGroupName", "TrackParticleAnalysisAlg"};
+
+    Gaudi::Property< bool > m_monitorTrackStateCounts
+      {this, "MonitorTrackStateCounts", true};
+  };
+
+}
+
+#endif
diff --git a/Tracking/Acts/ActsMonitoring/src/components/ActsMonitoring_entries.cxx b/Tracking/Acts/ActsMonitoring/src/components/ActsMonitoring_entries.cxx
index e427cc6d925a07cf94bf6c410cc2cc5cbac3381a..65301e256f785cdb5ed2d673a51310295ff6e267 100644
--- a/Tracking/Acts/ActsMonitoring/src/components/ActsMonitoring_entries.cxx
+++ b/Tracking/Acts/ActsMonitoring/src/components/ActsMonitoring_entries.cxx
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 2002-2023 CERN for the benefit of the ATLAS collaboration
+  Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
 */
 
 // Algs
@@ -11,6 +11,7 @@
 #include "src/EstimatedTrackParamsAnalysisAlg.h"
 #include "src/SeedingAlgorithmAnalysisAlg.h"
 #include "src/TrackAnalysisAlg.h"
+#include "src/TrackParticleAnalysisAlg.h"
 // Tools
 #include "src/PhysValTool.h"
 
@@ -23,5 +24,6 @@ DECLARE_COMPONENT( ActsTrk::SeedAnalysisAlg )
 DECLARE_COMPONENT( ActsTrk::SeedingAlgorithmAnalysisAlg )
 DECLARE_COMPONENT( ActsTrk::EstimatedTrackParamsAnalysisAlg )
 DECLARE_COMPONENT( ActsTrk::TrackAnalysisAlg )
+DECLARE_COMPONENT( ActsTrk::TrackParticleAnalysisAlg )
 // Tools
 DECLARE_COMPONENT( ActsTrk::PhysValTool )
diff --git a/Tracking/Acts/ActsMonitoring/test/ActsReadEDM.py b/Tracking/Acts/ActsMonitoring/test/ActsReadEDM.py
index 4a69de9e65c48dc634caff6059e31c8e807d25a6..0fd14a704345d083cc28f0bc847ed19f76738dee 100755
--- a/Tracking/Acts/ActsMonitoring/test/ActsReadEDM.py
+++ b/Tracking/Acts/ActsMonitoring/test/ActsReadEDM.py
@@ -3,6 +3,7 @@
 # Copyright (C) 2002-2023 CERN for the benefit of the ATLAS collaboration
 
 if __name__ == "__main__":
+    import re
     from AthenaConfiguration.AllConfigFlags import initConfigFlags
     flags = initConfigFlags()
 
@@ -20,6 +21,8 @@ if __name__ == "__main__":
     flags.addFlag("readSpacePoints", False)
     flags.addFlag("readTracks", False)
     flags.addFlag("tracks", "")
+    flags.addFlag("readTrackParticles", False)
+    flags.addFlag("trackParticles", "ActsCombinedTracksParticlesAlt")
     flags.fillFromArgs()
     
     flags.lock()
@@ -58,6 +61,16 @@ if __name__ == "__main__":
                                               name=f"{track}AnalysisAlg",
                                               OutputLevel=2,
                                               TracksLocation=track))
+    if flags.readTrackParticles:
+        from ActsConfig.ActsAnalysisConfig import ActsTrackParticleAnalysisAlgCfg
+        for tp in flags.trackParticles.split(','):
+            src_track_name = re.search(r'^(.*)ParticlesAlt.*',tp).group(1)
+            acc.merge(ActsTrackParticleAnalysisAlgCfg(flags,
+                                                      name=f"{tp}AnalysisAlg",
+                                                      OutputLevel=2,
+                                                      TrackParticleLocation=tp,
+                                                      ExtraInputs={('ActsTrk::TrackContainer',src_track_name)}, # ensure scheduled after reader
+                                                      MonGroupName=f"{tp}Analysis"))
             
     acc.printConfig()
     status = acc.run()