2025年12月30日 星期二

[python]計算不定期不定額的年化報酬率

 ## Gemini 解釋 XIRR


對於「不定期不定額」的投資,傳統的 CAGR 公式(只看期初和期末)已經不適用了,因為它無法反映資金進出的時間點對獲利的貢獻。


在金融界,這種情況標準的計算方式是 **XIRR (Internal Rate of Return for a schedule of cash flows)**。它考慮了每一筆資金投入的精確日期,並算出一個等效的年化報酬率。


要在 Python 中計算,最推薦的方式是使用 `pyxirr` 庫,或是手動透過 `scipy` 進行數值求解。


---


### 1. XIRR 的數學原理


XIRR 是在找一個折現率 ,使得所有現金流的現值(Present Value)總和等於零:


$$0 = \sum_{i=1}^{N} \frac{P_i}{(1+r)^{\frac{d_i - d_1}{365}}}$$


* $P_i$:第  筆現金流(投入為負數,領回或當前價值為正數)。

* $d_i$:第  筆現金流發生的日期。

* $d_1$:第一筆投資發生的日期。

* $r$:就是我們要找的年化報酬率 (XIRR)。

* $N$:現金流的總次數


---


### 2. Python 程式碼實作


#### 方法 A:使用 `pyxirr` 庫(最簡單、精準)


這是目前處理 XIRR 最推薦的套件,速度快且支援各種格式。


**安裝:** `pip install pyxirr`


```python

from pyxirr import xirr

from datetime import date


# 定義現金流與日期

# 注意:投入資金設為「負值」,最後一筆「當前市值」或「贖回金額」設為「正值」

dates = [

    date(2023, 1, 1),   # 第一次投入 10,000

    date(2023, 4, 15),  # 第二次投入 5,000

    date(2023, 10, 20), # 第三次投入 8,000

    date(2024, 5, 20)   # 當前日期與帳面總價值 (假設現在值 26,000)

]


amounts = [-10000, -5000, -8000, 26000]


# 計算 XIRR

annual_return = xirr(dates, amounts)


print(f"不定期不定額的年化報酬率 (XIRR) 為: {annual_return:.2%}")


```


---


#### 方法 B:使用 `scipy` 手寫邏輯(不依賴金融庫)


如果你不想安裝額外的金融庫,可以用 `scipy` 的優化器來解方程式。


```python

import numpy as np

from scipy.optimize import newton

from datetime import date


def calculate_xirr(dates, amounts):

    # 將日期轉換為天數差距

    days = [(d - dates[0]).days for d in dates]

    

    # 定義 NPV 函數,我們要求解 NPV = 0 的 r

    def npv(r):

        return sum(amt / (1 + r) ** (d / 365) for amt, d in zip(amounts, days))

    

    # 使用牛頓法求解,初始猜測值設為 0.1 (10%)

    try:

        return newton(npv, 0.1)

    except RuntimeError:

        return None # 無法收斂時回傳 None


# 測試資料

test_dates = [date(2023, 1, 1), date(2023, 6, 1), date(2024, 1, 1)]

test_amounts = [-100, -100, 250] # 投兩次 100,最後拿回 250


result = calculate_xirr(test_dates, test_amounts)

print(f"手動計算結果: {result:.2%}")


```


---


### 3. 寫程式時的注意事項


1. **正負號邏輯:**

* **流出 (投資):** 必須是**負數**。

* **流入 (提領或結餘):** 必須是**正數**。如果你還沒賣掉,最後一筆資料就是「今天的日期」和「當前的總市值」。



2. **現金流排序:** 日期建議由遠到近排序,雖然 `pyxirr` 會自動處理,但手寫邏輯時排序能避免計算錯誤。

3. **多重解問題:** 在極少數現金流劇烈震盪的情況下,XIRR 可能會有數學上的多重解,但對於一般的投資行為,數值解通常很穩定。

2025年12月8日 星期一

[點網]使用 openxml 從 CSV 檔轉成 excel 檔

僅供參考

參考網址:

 https://learn.microsoft.com/en-us/office/open-xml/spreadsheet/how-to-create-a-spreadsheet-document-by-providing-a-file-name

https://learn.microsoft.com/en-us/office/open-xml/getting-started

https://www.nuget.org/packages/DocumentFormat.OpenXml

https://blog.darkthread.net/blog/csvhelper/

https://www.cnblogs.com/geovindu/p/19161493


程式:

```

DataTable dataTable = ReadCsv(inputPath, encoding, delimiter);


ConvertDataTableToXls(dataTable, outputPath);


public static DataTable ReadCsv(string filePath, Encoding encoding, char delimiter)

        {

            try

            {

                using (var reader = new StreamReader(filePath, encoding))

                using (var csv = new CsvReader(reader, new CsvHelper.Configuration.CsvConfiguration(CultureInfo.InvariantCulture)

                {

                    Delimiter = delimiter.ToString(),

                    HasHeaderRecord = true,

                    IgnoreBlankLines = true,

                    TrimOptions = CsvHelper.Configuration.TrimOptions.Trim

                }))

                {

                    using (var dr = new CsvDataReader(csv))

                    {

                        var dt = new DataTable();

                        dt.Load(dr);

                        return dt;

                    }

                }

            }

            catch (Exception ex)

            {

                throw new Exception($"Failed to read CSV file: {ex.Message}", ex);

            }

        }


public static void ConvertDataTableToXls(DataTable dataTable, string outputPath)

        {

            try

            {

                // Create a new spreadsheet document

                using (SpreadsheetDocument document = SpreadsheetDocument.Create(outputPath, SpreadsheetDocumentType.Workbook))

                {

                    // Add a WorkbookPart to the document

                    WorkbookPart workbookPart = document.AddWorkbookPart();

                    workbookPart.Workbook = new Workbook();

 

                    // Add a WorksheetPart to the WorkbookPart

                    WorksheetPart worksheetPart = workbookPart.AddNewPart<WorksheetPart>();

                    worksheetPart.Worksheet = new Worksheet(new SheetData());

 

                    // Add Sheets to the Workbook

                    Sheets sheets = workbookPart.Workbook.AppendChild(new Sheets());

 

                    // Append a new worksheet and associate it with the workbook

                    Sheet sheet = new Sheet()

                    {

                        Id = workbookPart.GetIdOfPart(worksheetPart),

                        SheetId = 1,

                        Name = "Sheet1"

                    };

                    sheets.Append(sheet);

 

                    // Get the SheetData object

                    SheetData sheetData = worksheetPart.Worksheet.GetFirstChild<SheetData>();

 

                    // Add header row

                    Row headerRow = new Row();

                    foreach (DataColumn column in dataTable.Columns)

                    {

                        Cell cell = CreateCell(column.ColumnName, CellValues.String);

                        headerRow.AppendChild(cell);

                    }

                    sheetData.AppendChild(headerRow);

 

                    // Add data rows

                    foreach (DataRow row in dataTable.Rows)

                    {

                        Row dataRow = new Row();

                        foreach (var item in row.ItemArray)

                        {

                            CellValues cellType = GetCellValueType(item);

                            string cellValue = GetCellValueAsString(item, cellType);

                            Cell cell = CreateCell(cellValue, cellType);

                            dataRow.AppendChild(cell);

                        }

                        sheetData.AppendChild(dataRow);

                    }

 

                    // Save the workbook

                    workbookPart.Workbook.Save();

                }

            }

            catch (Exception ex)

            {

                throw new Exception($"Failed to create XLS file: {ex.Message}", ex);

            }

        }


        private static Cell CreateCell(string value, CellValues cellType)

        {

            Cell cell = new Cell();

            cell.DataType = new EnumValue<CellValues>(cellType);

            cell.CellValue = new CellValue(value);

            return cell;

        }

        

        private static CellValues GetCellValueType(object value)

        {

            if (value == DBNull.Value)

                return CellValues.String;

 

            Type type = value.GetType();

 

            if (type == typeof(int) || type == typeof(long) || type == typeof(short) || type == typeof(byte))

                return CellValues.Number;

            else if (type == typeof(float) || type == typeof(double) || type == typeof(decimal))

                return CellValues.Number;

            else if (type == typeof(DateTime))

                return CellValues.Date;

            else if (type == typeof(bool))

                return CellValues.Boolean;

            else

                return CellValues.String;

        }


         private static string GetCellValueAsString(object value, CellValues cellType)

        {

            if (value == DBNull.Value)

                return string.Empty;

 

            switch (cellType)

            {

                case CellValues.Boolean:

                    return (bool)value ? "1" : "0";

                case CellValues.Date:

                    DateTime dateValue = (DateTime)value;

                    // Excel stores dates as OLE Automation dates

                    return dateValue.ToOADate().ToString(CultureInfo.InvariantCulture);

                case CellValues.Number:

                    return Convert.ToString(value, CultureInfo.InvariantCulture);

                default:

                    return Convert.ToString(value);

            }

        }

```