diff --git a/include/evalhyd/detail/probabilist/brier.hpp b/include/evalhyd/detail/probabilist/brier.hpp
index b8d80eb927751567ff8c156d117890d804266c07..1da4fdd01dc3982591d6fba9bbf18cb33d478d98 100644
--- a/include/evalhyd/detail/probabilist/brier.hpp
+++ b/include/evalhyd/detail/probabilist/brier.hpp
@@ -37,11 +37,21 @@ namespace evalhyd
             template<class XV1D2>
             inline xt::xtensor<double, 2> calc_o_k(
                     const XV1D2& q_obs,
-                    const XV1D2& q_thr
+                    const XV1D2& q_thr,
+                    bool high_flow_events
             )
             {
-                // determine observed realisation of threshold(s) exceedance
-                return q_obs >= xt::view(q_thr, xt::all(), xt::newaxis());
+                if (high_flow_events)
+                {
+                    // observations above threshold(s)
+                    return q_obs >= xt::view(q_thr, xt::all(), xt::newaxis());
+                }
+                else
+                {
+                    // observations below threshold(s)
+                    return q_obs < xt::view(q_thr, xt::all(), xt::newaxis());
+                }
+
             }
 
             // Determine mean observed realisation of threshold(s) exceedance.
@@ -112,15 +122,28 @@ namespace evalhyd
             template<class XV2D4, class XV1D2>
             inline xt::xtensor<double, 2> calc_sum_f_k(
                     const XV2D4& q_prd,
-                    const XV1D2& q_thr
+                    const XV1D2& q_thr,
+                    bool high_flow_events
             )
             {
-                // determine if members have exceeded threshold(s)
-                auto f_k = q_prd >=
-                        xt::view(q_thr, xt::all(), xt::newaxis(), xt::newaxis());
+                if (high_flow_events)
+                {
+                    // determine if members are above threshold(s)
+                    auto f_k = q_prd >=
+                               xt::view(q_thr, xt::all(), xt::newaxis(), xt::newaxis());
+
+                    // calculate how many members are above threshold(s)
+                    return xt::sum(f_k, 1);
+                }
+                else
+                {
+                    // determine if members are below threshold(s)
+                    auto f_k = q_prd <
+                               xt::view(q_thr, xt::all(), xt::newaxis(), xt::newaxis());
 
-                // calculate how many members have exceeded threshold(s)
-                return xt::sum(f_k, 1);
+                    // calculate how many members are below threshold(s)
+                    return xt::sum(f_k, 1);
+                }
             }
 
             // Determine forecast probability of threshold(s) exceedance to occur.
diff --git a/include/evalhyd/detail/probabilist/evaluator.hpp b/include/evalhyd/detail/probabilist/evaluator.hpp
index d4d4c1537be9dd892298d0dff1d0048f92c5df02..90c65d037838a86f9fda98c842dbed90c88155c4 100644
--- a/include/evalhyd/detail/probabilist/evaluator.hpp
+++ b/include/evalhyd/detail/probabilist/evaluator.hpp
@@ -57,6 +57,7 @@ namespace evalhyd
             const view1d_xtensor2d_double_type& q_obs;
             const view2d_xtensor4d_double_type& q_prd;
             const view1d_xtensor2d_double_type& q_thr;
+            const bool high_flow_events;
             xt::xtensor<bool, 2> t_msk;
             const std::vector<xt::xkeep_slice<int>>& b_exp;
 
@@ -112,7 +113,7 @@ namespace evalhyd
                 if (!o_k.has_value())
                 {
                     o_k = elements::calc_o_k(
-                            q_obs, q_thr
+                            q_obs, q_thr, high_flow_events
                     );
                 }
                 return o_k.value();
@@ -134,7 +135,7 @@ namespace evalhyd
                 if (!sum_f_k.has_value())
                 {
                     sum_f_k = elements::calc_sum_f_k(
-                            q_prd, q_thr
+                            q_prd, q_thr, high_flow_events
                     );
                 }
                 return sum_f_k.value();
@@ -300,9 +301,12 @@ namespace evalhyd
             Evaluator(const view1d_xtensor2d_double_type& obs,
                       const view2d_xtensor4d_double_type& prd,
                       const view1d_xtensor2d_double_type& thr,
+                      const bool high_flow_events,
                       const view2d_xtensor4d_bool_type& msk,
                       const std::vector<xt::xkeep_slice<int>>& exp) :
-                    q_obs{obs}, q_prd{prd}, q_thr{thr}, t_msk(msk), b_exp(exp)
+                    q_obs{obs}, q_prd{prd}, q_thr{thr},
+                    high_flow_events{high_flow_events},
+                    t_msk(msk), b_exp(exp)
             {
                 // initialise a mask if none provided
                 // (corresponding to no temporal subset)
diff --git a/include/evalhyd/detail/utils.hpp b/include/evalhyd/detail/utils.hpp
index 8d966610bdff6fd4e935b33decb3e5c173d26a12..1c55572163fa0200ff91771a0746f17fe356ef6e 100644
--- a/include/evalhyd/detail/utils.hpp
+++ b/include/evalhyd/detail/utils.hpp
@@ -9,20 +9,23 @@
 #include <unordered_set>
 #include <vector>
 #include <stdexcept>
+
+#include <xtl/xoptional.hpp>
 #include <xtensor/xtensor.hpp>
 #include <xtensor/xrandom.hpp>
 
+
 namespace evalhyd
 {
     namespace utils
     {
-        /// Procedure to check that all elements in the list of metrics are
-        /// valid metrics.
-        ///
-        /// \param [in] requested_metrics:
-        ///     Vector of strings for the metric(s) to be computed.
-        /// \param [in] valid_metrics:
-        ///     Vector of strings for the metric(s) to can be computed.
+        // Procedure to check that all elements in the list of metrics are
+        // valid metrics.
+        //
+        // \param requested_metrics
+        //     Vector of strings for the metric(s) to be computed.
+        // \param valid_metrics
+        //     Vector of strings for the metric(s) to can be computed.
         inline void check_metrics (
                 const std::vector<std::string>& requested_metrics,
                 const std::vector<std::string>& valid_metrics
@@ -40,11 +43,11 @@ namespace evalhyd
             }
         }
 
-        /// Procedure to check that all elements for a bootstrap experiment
-        /// are provided and valid.
-        ///
-        /// \param [in] bootstrap:
-        ///     Map of parameters for the bootstrap experiment.
+        // Procedure to check that all elements for a bootstrap experiment
+        // are provided and valid.
+        //
+        // \param bootstrap
+        //     Map of parameters for the bootstrap experiment.
         inline void check_bootstrap (
                 const std::unordered_map<std::string, int>& bootstrap
         )
@@ -105,27 +108,34 @@ namespace evalhyd
 
         namespace evalp
         {
-            /// Procedure to check that optional parameters are provided
-            /// as arguments when required metrics need them.
-            ///
-            /// \param [in] metrics:
-            ///     Vector of strings for the metric(s) to be computed.
-            /// \param [in] thresholds:
-            ///     Array of thresholds for metrics based on exceedance events.
-            inline void check_optionals (
+            // Procedure to check that optional parameters are provided
+            // as arguments when required metrics need them.
+            //
+            // \param metrics
+            //     Vector of strings for the metric(s) to be computed.
+            // \param thresholds
+            //     Array of thresholds for metrics based on exceedance events.
+            // \param events
+            //     Kind of streamflow exceedance events.
+            // \return
+            //     Whether high flow events are considered.
+            inline bool check_optionals (
                     const std::vector<std::string>& metrics,
-                    const xt::xtensor<double, 2>& thresholds
+                    const xt::xtensor<double, 2>& thresholds,
+                    xtl::xoptional<const std::string, bool> events
             )
             {
                 std::vector<std::string>threshold_metrics =
                         {"BS", "BS_CRD", "BS_LBD", "BSS",
                          "POD", "POFD", "FAR", "CSI", "ROCSS"};
 
+                bool thresholds_required_and_provided = false;
                 for (const auto& metric : metrics)
                 {
                     if (std::find(threshold_metrics.begin(), threshold_metrics.end(),
                                   metric) != threshold_metrics.end())
                     {
+                        // check thresholds
                         if (thresholds.size() < 1)
                         {
                             throw std::runtime_error(
@@ -133,8 +143,45 @@ namespace evalhyd
                                     "compute " + metric
                             );
                         }
+                        else
+                        {
+                            thresholds_required_and_provided = true;
+                            break;
+                        }
                     }
                 }
+
+                // check events
+                if (thresholds_required_and_provided)
+                {
+                    if (events.has_value())
+                    {
+                        if (events.value() == "high")
+                        {
+                            return true;
+                        }
+                        else if (events.value() == "low")
+                        {
+                            return false;
+                        }
+                        else
+                        {
+                            throw std::runtime_error(
+                                    "invalid value for streamflow *events*"
+                            );
+                        }
+                    }
+                    else
+                    {
+                        throw std::runtime_error(
+                                "*q_thr* provided but *events* is missing"
+                        );
+                    }
+                }
+                else
+                {
+                    return true;
+                }
             }
         }
     }
diff --git a/include/evalhyd/evalp.hpp b/include/evalhyd/evalp.hpp
index e56e5b14ef009da1d67cb0e158949f51e3a57d83..5582e1f1090404751a054dc4cf92980b74c9c409 100644
--- a/include/evalhyd/evalp.hpp
+++ b/include/evalhyd/evalp.hpp
@@ -60,9 +60,19 @@ namespace evalhyd
     ///       The sequence of evaluation metrics to be computed.
     ///
     ///    q_thr: ``XD2``, optional
-    ///       Streamflow exceedance threshold(s).
+    ///       Streamflow exceedance threshold(s). If provided, *events* must
+    ///       also be provided.
     ///       shape: (sites, thresholds)
     ///
+    ///    events: ``std::string``, optional
+    ///       The type of streamflow events to consider for threshold
+    ///       exceedance-based metrics. It can either be set as "high" when
+    ///       flooding conditions/high flow events are evaluated (i.e. event
+    ///       occurring when streamflow goes above threshold) or as "low" when
+    ///       drought conditions/low flow events are evaluated (i.e. event
+    ///       occurring when streamflow goes below threshold). It must be
+    ///       provided if *q_thr* is provided.
+    ///
     ///    t_msk: ``XB4``, optional
     ///       Mask(s) used to generate temporal subsets of the whole streamflow
     ///       time series (where True/False is used for the time steps to
@@ -147,6 +157,8 @@ namespace evalhyd
             const xt::xexpression<XD4>& q_prd,
             const std::vector<std::string>& metrics,
             const xt::xexpression<XD2>& q_thr = XD2({}),
+            xtl::xoptional<const std::string, bool> events =
+                    xtl::missing<const std::string>(),
             const xt::xexpression<XB4>& t_msk = XB4({}),
             const xt::xtensor<std::array<char, 32>, 2>& m_cdt = {},
             xtl::xoptional<const std::unordered_map<std::string, int>, bool> bootstrap =
@@ -190,7 +202,9 @@ namespace evalhyd
         );
 
         // check that optional parameters are given as arguments
-        utils::evalp::check_optionals(metrics, q_thr_);
+        bool high_flow_events =
+                utils::evalp::check_optionals(metrics, q_thr_, events);
+
         if (bootstrap.has_value())
         {
             utils::check_bootstrap(bootstrap.value());
@@ -374,7 +388,9 @@ namespace evalhyd
                          xt::view(t_msk_, s, l, xt::all(), xt::all()));
 
                 probabilist::Evaluator<XD2, XD4, XB4> evaluator(
-                        q_obs_v, q_prd_v, q_thr_v, t_msk_v, b_exp
+                        q_obs_v, q_prd_v, q_thr_v,
+                        high_flow_events,
+                        t_msk_v, b_exp
                 );
 
                 // retrieve or compute requested metrics
diff --git a/tests/test_probabilist.cpp b/tests/test_probabilist.cpp
index d406c29b2da95f60c7bf6a23332a8bf6d2a83dd4..ee863a7ab165ebb7e07a924649917bbd26a32ecf 100644
--- a/tests/test_probabilist.cpp
+++ b/tests/test_probabilist.cpp
@@ -53,7 +53,8 @@ TEST(ProbabilistTests, TestBrier)
                     // shape: (sites [1], lead times [1], members [m], time [t])
                     xt::eval(xt::view(predicted, xt::newaxis(), xt::newaxis(), xt::all(), xt::all())),
                     {"BS", "BSS", "BS_CRD", "BS_LBD"},
-                    thresholds
+                    thresholds,
+                    "high"
             );
 
     // check results
@@ -96,6 +97,78 @@ TEST(ProbabilistTests, TestBrier)
     );
 }
 
+TEST(ProbabilistTests, TestContingency)
+{
+    // read in data
+    xt::xtensor<double, 1> observed;
+    xt::xtensor<double, 2> predicted;
+    std::tie(observed, predicted) = load_data_p();
+
+    // compute scores
+    xt::xtensor<double, 2> thresholds = {{690, 534, 445, NAN}};
+
+    std::vector<xt::xarray<double>> metrics =
+            evalhyd::evalp(
+                    // shape: (sites [1], time [t])
+                    xt::eval(xt::view(observed, xt::newaxis(), xt::all())),
+                    // shape: (sites [1], lead times [1], members [m], time [t])
+                    xt::eval(xt::view(predicted, xt::newaxis(), xt::newaxis(), xt::all(), xt::all())),
+                    {"POD", "POFD", "FAR", "CSI", "ROCSS"},
+                    thresholds,
+                    "high"
+            );
+
+    std::cout << "POD" << std::endl;
+    std::cout << metrics[0] << std::endl;
+    std::cout << "POFD" << std::endl;
+    std::cout << metrics[1] << std::endl;
+    std::cout << "FAR" << std::endl;
+    std::cout << metrics[2] << std::endl;
+    std::cout << "CSI" << std::endl;
+    std::cout << metrics[3] << std::endl;
+    std::cout << "ROCSS" << std::endl;
+    std::cout << metrics[4] << std::endl;
+
+//    // check results
+//    // POD
+//    xt::xtensor<double, 6> pod =
+//            {{{{{{0.10615136, 0.07395622, 0.08669186, NAN}}}}}};
+//    EXPECT_TRUE(
+//            xt::sum(xt::isclose(metrics[0], pod, 1e-05, 1e-08, true))
+//            == xt::xscalar<double>(4)
+//    );
+//
+//    // POFD
+//    xt::xtensor<double, 6> pofd =
+//            {{{{{{0.5705594, 0.6661165, 0.5635126, NAN}}}}}};
+//    EXPECT_TRUE(
+//            xt::sum(xt::isclose(metrics[1], pofd, 1e-05, 1e-08, true))
+//            == xt::xscalar<double>(4)
+//    );
+//
+//    // FAR
+//    xt::xtensor<double, 6> far =
+//            {{{{{{{0.011411758, 0.1524456, 0.2471852},
+//                  {0.005532413, 0.1530793, 0.2215031},
+//                  {0.010139431, 0.1220601, 0.1986125},
+//                  {NAN, NAN, NAN}}}}}}};
+//    EXPECT_TRUE(
+//            xt::sum(xt::isclose(metrics[2], far, 1e-05, 1e-08, true))
+//            == xt::xscalar<double>(12)
+//    );
+//
+//    // CSI
+//    xt::xtensor<double, 6> csi =
+//            {{{{{{0.012159881, 0.1506234, 0.2446149},
+//                 {0.008031746, 0.1473869, 0.2133114},
+//                 {0.017191279, 0.1048221, 0.1743227},
+//                 {NAN, NAN, NAN}}}}}};
+//    EXPECT_TRUE(
+//            xt::sum(xt::isclose(metrics[3], csi, 1e-05, 1e-08, true))
+//            == xt::xscalar<double>(12)
+//    );
+}
+
 TEST(ProbabilistTests, TestQuantiles)
 {
     // read in data
@@ -161,6 +234,7 @@ TEST(ProbabilistTests, TestMasks)
                     xt::eval(xt::view(predicted, xt::newaxis(), xt::newaxis(), xt::all(), xt::all())),
                     metrics,
                     thresholds,
+                    "high",
                     // shape: (sites [1], lead times [1], subsets [1], time [t])
                     masks
             );
@@ -173,7 +247,8 @@ TEST(ProbabilistTests, TestMasks)
                     // shape: (sites [1], lead times [1], members [m], time [t-20])
                     xt::eval(xt::view(predicted, xt::newaxis(), xt::newaxis(), xt::all(), xt::range(20, _))),
                     metrics,
-                    thresholds
+                    thresholds,
+                    "high"
             );
 
     // check results are identical
@@ -214,6 +289,7 @@ TEST(ProbabilistTests, TestMaskingConditions)
                     xt::eval(xt::view(predicted, xt::newaxis(), xt::newaxis(), xt::all(), xt::all())),
                     metrics,
                     thresholds,
+                    "high",
                     masks,
                     q_conditions
             );
@@ -224,7 +300,8 @@ TEST(ProbabilistTests, TestMaskingConditions)
                     xt::eval(xt::where((observed < 2000) | (observed > 3000), observed, NAN)),
                     xt::eval(xt::view(predicted, xt::newaxis(), xt::newaxis(), xt::all(), xt::all())),
                     metrics,
-                    thresholds
+                    thresholds,
+                    "high"
             );
 
     // check results are identical
@@ -253,6 +330,7 @@ TEST(ProbabilistTests, TestMaskingConditions)
                     xt::eval(xt::view(predicted, xt::newaxis(), xt::newaxis(), xt::all(), xt::all())),
                     metrics,
                     thresholds,
+                    "high",
                     masks,
                     q_conditions_
             );
@@ -263,7 +341,8 @@ TEST(ProbabilistTests, TestMaskingConditions)
                     xt::eval(xt::where(q_prd_mean >= median, observed, NAN)),
                     xt::eval(xt::view(predicted, xt::newaxis(), xt::newaxis(), xt::all(), xt::all())),
                     metrics,
-                    thresholds
+                    thresholds,
+                    "high"
             );
 
     // check results are identical
@@ -289,6 +368,7 @@ TEST(ProbabilistTests, TestMaskingConditions)
                     xt::eval(xt::view(predicted, xt::newaxis(), xt::newaxis(), xt::all(), xt::all())),
                     metrics,
                     thresholds,
+                    "high",
                     masks,
                     t_conditions
             );
@@ -299,7 +379,8 @@ TEST(ProbabilistTests, TestMaskingConditions)
                     xt::eval(xt::view(observed_, xt::newaxis(), xt::range(0, 100))),
                     xt::eval(xt::view(predicted, xt::newaxis(), xt::newaxis(), xt::all(), xt::range(0, 100))),
                     metrics,
-                    thresholds
+                    thresholds,
+                    "high"
             );
 
     // check results are identical
@@ -340,7 +421,8 @@ TEST(ProbabilistTests, TestMissingData)
                 observed_nan,
                 forecast_nan,
                 metrics,
-                thresholds
+                thresholds,
+                "high"
         );
 
     // compute metrics on manually subset series (one leadtime at a time)
@@ -359,7 +441,8 @@ TEST(ProbabilistTests, TestMissingData)
                 observed_pp1,
                 forecast_pp1,
                 metrics,
-                thresholds
+                thresholds,
+                "high"
         );
 
     xt::xtensor<double, 4> forecast_pp2 {{
@@ -377,7 +460,8 @@ TEST(ProbabilistTests, TestMissingData)
                 observed_pp2,
                 forecast_pp2,
                 metrics,
-                thresholds
+                thresholds,
+                "high"
         );
 
     // check that numerical results are identical
@@ -434,6 +518,7 @@ TEST(ProbabilistTests, TestBootstrap)
                     xt::eval(xt::view(predicted, xt::newaxis(), xt::newaxis(), xt::all(), xt::all())),
                     metrics,
                     thresholds,
+                    "high",  // events
                     xt::xtensor<bool, 4>({}),  // t_msk
                     {},  // m_cdt
                     bootstrap,
@@ -456,7 +541,8 @@ TEST(ProbabilistTests, TestBootstrap)
                     xt::eval(xt::view(observed_x3, xt::newaxis(), xt::all())),
                     xt::eval(xt::view(predicted_x3, xt::newaxis(), xt::newaxis(), xt::all(), xt::all())),
                     metrics,
-                    thresholds
+                    thresholds,
+                    "high"
             );
 
     // check results are identical