How to calculate XIRR in Python?

There are situations where you want to find the IRR of the strategy for a given timeframe. The most efficient method is equivalent to the Excel XIRR calc, and the code outlined below replicates it in Python.

Is there an XIRR module?

The easiest way to calculate XIRR is to use the XIRR module.

But what if you can’t install the module in your environment like QuantConnect? Then StackOverflow is your friend. I adapted the code in the link into the QuantConnect environment as it doesn’t have the XIRR module, and it worked like a charm.

    def xnpv(rate, values, dates):
        if rate <= -1.0:
            return float('inf')
        d0 = dates[0]    # or min(dates)
        return sum([ vi / (1.0 + rate)**((di - d0).days / 365.0) for vi, di in zip(values, dates)])


    def xirr(self, values, dates):
        try:
            return scipy.optimize.newton(lambda r: MasterAlgo.xnpv(r, values, dates), 0.0)
        except RuntimeError:    # Failed to converge?
            return scipy.optimize.brentq(lambda r: MasterAlgo.xnpv(r, values, dates), -1.0, 1e10)

Calculate XIRR in Quantconnect

I use QuantConnect as my main trading platform, so the code below is the full code for drawing the trades and putting it into Panda and calculating the XIRR of the strategy.

Step 1.

Extract the order details, namely the LastFillTime (not its CreatedTime, although there is an option), the order.Symbol.Value and the order.Value so you don’t need to pull the quantity and fill the price.

Save the data in the self.order_history dictionary.

def initialize
        self.order_history = {}
        self.counter = 0    



def OnOrderEvent(self, orderEvent: OrderEvent) -> None:
        order = self.Transactions.GetOrderById(orderEvent.OrderId)
        if orderEvent.Status == OrderStatus.Filled:
            key = f"{order.LastFillTime} {order.Symbol.Value} {order.Value}"
            self.order_history[key] = {
                'LastFillTime': order.LastFillTime,  # CreatedTime, LastFillTime
                'Symbol': order.Symbol.Value,
                'Value': order.Value
            }

          

Step 2 Define the xnpv and xirr classes


    
    @staticmethod
    def xnpv(rate, values, dates):
        if rate <= -1.0:
            return float('inf')
        d0 = dates[0]    # or min(dates)
        return sum([ vi / (1.0 + rate)**((di - d0).days / 365.0) for vi, di in zip(values, dates)])


    def xirr(self, values, dates):
        try:
            return scipy.optimize.newton(lambda r: MasterAlgo.xnpv(r, values, dates), 0.0)
        except RuntimeError:    # Failed to converge?
            return scipy.optimize.brentq(lambda r: MasterAlgo.xnpv(r, values, dates), -1.0, 1e10)

Step 3 pulls the data and puts it into Pandas DataFrame format, then run self.xirr(values_from_symbol, dates_from_symbol). I also created a counter so that it will calculate the xirr of each symbol.



    def OnEndOfAlgorithm(self) -> None:
                # Extracting values, trades are based on fill time.
            data = {
                'Date': [entry['LastFillTime'].date() for entry in self.order_history.values()],
                'Symbol': [entry['Symbol'] for entry in self.order_history.values()],
                'Value': [entry['Value'] for entry in self.order_history.values()]
            }

            # Creating a DataFrame
            df = pd.DataFrame(data)

            # Print the DataFrame to see the result
            #self.Debug(df)


            for symbol in df['Symbol'].unique():
                    # Filter the DataFrame for the current symbol
                    filtered_df = df[df['Symbol'] == symbol]

                    # Extract dates and values for the current symbol
                    dates_from_symbol = filtered_df['Date'].tolist()
                    values_from_symbol = filtered_df['Value'].tolist()

                    # Calculate IRR using the extracted values
                    result = self.xirr(values_from_symbol, dates_from_symbol)
                    result_value = result * 100
                    self.result = f"{symbol} IRR is {result_value:.2f}%"

                    # Debug IRR results
                    self.Debug(self.result)
                    # self.Debug(f" project is {self.ProjectId}")
                    

                    # Save IRR to the ObjectStore database
                    date = self.Time.date()
                    key = f"{self.__class__.__name__}_{self.counter}"

                    self.ObjectStore.Save(key, self.result)
                    self.counter += 1