← Back to list

MongoDB Aggregation Pipeline: Approximation with Newton’s Method

In this article I will show you an implementation of Newton’s method in an Aggregation Pipeline.

Wernfried Domscheit · 2025-02-12 07:34 · 1 claps · 5.7 min read
#mongodb #newtons-method #aggregation-pipeline #interation
Open on Medium ↗

Newton’s Method

Newton’s Method

MongoDB Aggregation Pipeline: Approximation with Newton’s Method

In this article I will show you an implementation of Newton’s method in an Aggregation Pipeline.

$function is deprecated and I was looking to implement it natively in an Aggregation Pipeline without using any external library.

As example let’s take the XIRR function which calculates the Internal Rate of Return (IRR) for a series of cash flows. For a series of cash flows (N) it is given as:

where:

  • di = the ith payment date.
  • d1 = the 1st payment date.
  • Pi = the ith payment.
db.collection.insertMany([
   { _id: 1, payment: -10000, date: ISODate('2022-01-01') },
   { _id: 2, payment: 2750, date: ISODate('2022-03-01') },
   { _id: 3, payment: 4250, date: ISODate('2022-10-30') },
   { _id: 4, payment: 3250, date: ISODate('2023-02-15') },
   { _id: 5, payment: 2750, date: ISODate('2023-04-01') }
])

With Internal Rate of Return the calculation looks like this one:

const rate = 0.3748585976776016

db.collection.aggregate([
   {
      $group: {
         _id: null,
         data: { $push: "$$ROOT" },
         d1: { $min: "$date" },
      }
   },
   {
      $project: {
         xirr: {
            $sum: {
               $map: {
                  input: "$data",
                  as: 'data',
                  in: {
                     $divide: ["$$data.payment",
                        {
                           $pow: [
                              { $add: [1, rate] },
                              { $divide: [{ $dateDiff: { startDate: "$d1", endDate: "$$data.date", unit: 'day' } }, 365] }]
                        }
                     ]
                  }
               }
            }
         }
      }
   }
])

Desired output -> xirr ≈0 

It is not possible to solve above equation to rate, thus it must be iterated. With native Aggregation Pipeline it is not possible to run recursive functions or condition-controlled loops. Thus, we assume a max. number of 100 iterations.

Numerical calculation with difference quotient

For newton’s method you need to evaluate the deviation of your function. In real life, this can be very difficult and a numerical evaluation is more suitable.

We calculate the first value and then based on 2-Point Equation of Line we calculate next iterations.

Formula for next rate iteration

Formula for next rate iteration

function xirr(arg) {
   // Use this function to improve readability of the code
   return {
      $sum: {
         $map: {
            input: "$data",
            as: 'data',
            in: {
               $divide: ["$$data.payment",
                  {
                     $pow: [
                        { $add: arg },
                        { $divide: [{ $dateDiff: { startDate: "$d1", endDate: "$$data.date", unit: 'day' } }, 365] }]
                  }
               ]
            }
         }
      }
   }
}

db.collection.aggregate([
   {
      $group: {
         _id: null,
         data: { $push: "$$ROOT" },
         d1: { $min: "$date" },
      }
   },
   {
      $set: {
         xirr: {
            $let: {
               vars: { guess: 0.1, y2_y1: 0.0000001 },
               in: {
                  $reduce: {
                     input: { $range: [2, 101] },
                     initialValue: [{
                        // calculate inital value
                        i: 1,
                        rate: "$$guess",
                        sum: xirr([1, "$$guess"]),
                        x2_x1: {
                           $subtract: [
                              xirr([1, "$$guess", "$$y2_y1"]),
                              xirr([1, "$$guess"])
                           ]
                        }
                     }],
                     in: {
                        $concatArrays: [
                           "$$value",
                           [{
                              $cond: {
                                 if: {
                                    // condition to stop iterations
                                    $lt: [{ $abs: { $last: "$$value.sum" } }, 0.000001]
                                 },
                                 then: null,
                                 else: {
                                    $let: {
                                       vars: {
                                          rate: {
                                             // calculate rate using Equation of a Line from 2 Points
                                             $subtract: [
                                                { $last: "$$value.rate" },
                                                {
                                                   $multiply: [
                                                      { $last: "$$value.sum" },
                                                      { $divide: ["$$y2_y1", { $last: "$$value.x2_x1" }] }
                                                   ]
                                                }
                                             ]
                                          }
                                       },
                                       in: {
                                          i: "$$this",
                                          rate: "$$rate",
                                          sum: xirr([1, "$$rate"]),
                                          x2_x1: {
                                             $subtract: [
                                                xirr([1, "$$rate", "$$y2_y1"]),
                                                xirr([1, "$$rate"])
                                             ]
                                          }
                                       }
                                    }
                                 }
                              }
                           }]
                        ]
                     }
                  }
               }
            }
         }
      }
   },
   { $set: { xirr: { $filter: { input: "$xirr", cond: { $ne: ["$$this", null] } } } } },
   {
      $project: {
         result: { $last: "$xirr.rate" },
         data: 1,
         xirr: 1
      }
   }
])

Result will be this one:

[{
   result: 0.3748585976776016,
   data: [
      { _id: 1, payment: -10000, date: ISODate('2022-01-01') },
      { _id: 2, payment: 2750, date: ISODate('2022-03-01') },
      { _id: 3, payment: 4250, date: ISODate('2022-10-30') },
      { _id: 4, payment: 3250, date: ISODate('2023-02-15') },
      { _id: 5, payment: 2750, date: ISODate('2023-04-01') }
   ],
   xirr: [
      { i: 1, rate: 0.1, sum: 1997.642501419953, x2_x1: -0.0000910145276975527 },
      { i: 2, rate: 0.3194861141353446, sum: 335.0546848435074, x2_x1: -0.00006306108866738214 },
      { i: 3, rate: 0.3726178776243584, sum: 13.031307732403775, x2_x1: -0.00005825209996146441 },
      { i: 4, rate: 0.3748549313513526, sum: 0.021287401081508506, x2_x1: -0.00005806193894386524 },
      { i: 5, rate: 0.3748585976776016, sum: 0.00000005672359293384943, x2_x1: -0.000058061627441929886 }
   ]
}]

As you see after 5 iterations we get a sufficient result of 0.3748585976776016 or 37.5%

Calculation with deviation

When you are smart, then you can deviate your function. In our example it would be this one:

Deviation of XIRR Formula

Deviation of XIRR Formula

Expression as aggregation pipeline would be:

function deviate(arg) {
   return {
      $sum: {
         $map: {
            input: "$data",
            as: 'data',
            in: {
               $divide: [
                  {
                     $multiply: [
                        -1,
                        "$$data.payment",
                        { $divide: [{ $dateDiff: { startDate: "$d1", endDate: "$$data.date", unit: 'day' } }, 365] }]
                  },
                  {
                     $pow: [
                        { $add: [1, arg] },
                        {
                           $add: [
                              { $divide: [{ $dateDiff: { startDate: "$d1", endDate: "$$data.date", unit: 'day' } }, 365] },
                              1
                           ]
                        }
                     ]
                  }
               ]
            }
         }
      }
   }
}

With deviation the next rate values is calculated by

This will end in aggregation pipeline which is a little shorter than above:

db.collection.aggregate([
   {
      $group: {
         _id: null,
         data: { $push: "$$ROOT" },
         d1: { $min: "$date" },
      }
   },
   {
      $set: {
         xirr: {
            $let: {
               vars: { guess: 0.1 },
               in: {
                  $reduce: {
                     input: { $range: [2, 101] },
                     initialValue: [{
                        // calculate inital value
                        i: 1,
                        sum: xirr([1, "$$guess"]),
                        rate: "$$guess"
                     }],
                     in: {
                        $concatArrays: [
                           "$$value",
                           [{
                              $cond: {
                                 if: {
                                    // condition to stop iterations
                                    $lt: [{ $abs: { $last: "$$value.sum" } }, 0.00000001]
                                 },
                                 then: null,
                                 else: {
                                    $let: {
                                       vars: {
                                          rate: {
                                             $subtract: [
                                                { $last: "$$value.rate" },
                                                {
                                                   $divide: [
                                                      xirr([1, { $last: "$$value.rate" }]),
                                                      deviate({ $last: "$$value.rate" })]
                                                }
                                             ]
                                          }
                                       },
                                       in: {
                                          i: "$$this",
                                          rate: "$$rate",
                                          sum: xirr([1, "$$rate"]),
                                       }
                                    }
                                 }
                              }
                           }]
                        ]
                     }
                  }
               }
            }
         }
      }
   },
   { $set: { xirr: { $filter: { input: "$xirr", cond: { $ne: ["$$this", null] } } } } },
   {
      $project: {
         result: { $last: "$xirr.rate" },
         data: 1,
         xirr: 1
      }
   }
])

As expected the output is almost the same as above:

[{
   result: 0.3748585976775555,
   data: [
      { _id: 1, payment: -10000, date: ISODate('2022-01-01') },
      { _id: 2, payment: 2750, date: ISODate('2022-03-01') },
      { _id: 3, payment: 4250, date: ISODate('2022-10-30') },
      { _id: 4, payment: 3250, date: ISODate('2023-02-15') },
      { _id: 5, payment: 2750, date: ISODate('2023-04-01') }
   ],
   xirr: [
      { i: 1, rate: 0.1, sum: 1997.642501419953 },
      { i: 2, rate: 0.31948610922427223, sum: 335.0547158132647 },
      { i: 3, rate: 0.3726178766491623, sum: 13.031313413127009 },
      { i: 4, rate: 0.3748549313129668, sum: 0.021287623957505275},
      { i: 5, rate: 0.3748585976775555, sum: 0.00000005699121174984611 }
   ]
}]

Considerations and fine-tuning

The numerical approach with difference quotient can be easily adapted to any function used in your application. You just need to replace the function(args) {} part. For the other approach you need some more math.

I assumed max. 100 iterations. In many cases, this will be way to high. In most cases Newton’s Method returns sufficient result after 5–10 iterations. You may lower this limit. On the other hand, you should check if the size of the array is lower than 100, otherwise a solution was not found.

You may adapt the condition to stop the iteration and the step-size according to your requirements.

If you like to get some math background, have a look at

[embed]Finding y-intercept of linear equation with two points. If we were given two points on a linear equation $(x_1,y_1),(x_2,y_2)$, it is quite easy to find the slope and use…math.stackexchange.com

[embed]Calculus I - Newton's Method In this section we will discuss Newton's Method. Newton's Method is an application of derivatives will allow us to…tutorial.math.lamar.edu


메타데이터
post_id
21df2956e2fa
slug
mongodb-aggregation-pipeline-approximation-with-newtons-method-21df2956e2fa
url
https://medium.com/@github_80805/mongodb-aggregation-pipeline-approximation-with-newtons-method-21df2956e2fa
canonical_url
https://medium.com/@github_80805/mongodb-aggregation-pipeline-approximation-with-newtons-method-21df2956e2fa
author_url
https://medium.com/@github_80805
status
ok
fetched_at
2026-08-06 08:08:52