使用Google OR-Tools分析過去20年中國金融資產最佳配置組合

車騎發表於2022-12-13

前兩天,在朋友圈裡看到一張截至2022年Q2的金融資產歷年收益圖如下,圖中列舉了國內從2005年到2022年近20年主要的金融資產歷年收益率,隨產生想法分析和驗證下面幾個問題:

  • 過去20年,基於怎樣的資產配置才能讓收益最大化?
  • 如果完全拒絕風險,是否可以理財,收益率會是多少?
  • 是否有風險小,收益高的資產配置組合?
  • 拋開擇時和運氣,資產配置的最佳持有時長是多少年?

分析方法
使用工具:Google OR-Tools,OR-Tools是谷歌用於組合最佳化的軟體工具,可以從大量可能的解決方案中找到問題的最佳解決方案。比如本例中,假如2005年初我手上有100元錢,怎麼把這100元錢分到不同的金融資產上有太多方案。但基本上只會有一種組合讓最終收益最大化,也基本只會有一種組合讓每年本金不出現虧損的前提下實現收益儘可能最大化。這些都會藉助於這個工具進行分析和驗證。
一些計算條件:

  • 由於圖中的信託和房地產門檻比較高,不適於一般理財者,所以刨除掉。只保留銀行理財、貨幣基金、債卷基金、股票基金、股票等5種金融資產。
  • 假定只是2005年初投入100元本金,中間不增加本金,也不減少賬戶資金。
  • 投資組合是固定的,比如銀行理財佔比x%,股票佔比y%等,這個比例會保持不變。每年年初會基於上一年剩餘本金進行比例的動態平衡調整,調整的目的是讓投資組合依然保持這個比例。
  • 無風險是相對於每年年初的剩餘資金。舉例:2005年初投入100元,那麼2005年末剩餘資金必須大於等於100元。如果2005年獲得了10元收益也就是說2006年年初的賬戶資金是110元(100元本金+10元收益),那麼2006年末剩餘資金必須大於等於年初資金110元。

分析結果
分析結果請參考:文章

程式碼

//定義單年最大允許虧損比例。(比如:0.2代表單年最大允許虧損比例為20%;1代表無限制;0代表不允許虧損)
            float allowableMaximumLossRatio = 1f; //無限制
            //float allowableMaximumLossRatio = 0.2f; //單年最大允許虧損比例為20%
            //float allowableMaximumLossRatio = 0f; //不允許虧損

            //待處理資料,此處全部轉換為整數處理
            (String year, long[] values)[] data = new[]
            {
                ("2005", new long[]{ 10273, 10236, 10912, 10140, 8848}),
                ("2006", new long[]{ 10280, 10150, 11494, 22263, 21190}),
                ("2007", new long[]{ 10360, 10336, 11822, 22833, 26621}),
                ("2008", new long[]{ 11542, 10356, 10646, 4858, 3708}),
                ("2009", new long[]{ 10425, 10142, 10504, 17117, 20547}),
                ("2010", new long[]{ 10392, 10181, 10690, 9972, 9312}),
                ("2011", new long[]{ 10463, 10355, 9711, 7547, 7759}),
                ("2012", new long[]{ 10588, 10397, 10622, 10545, 10468}),
                ("2013", new long[]{ 10482,10395,10061,11013,10544}),
                ("2014", new long[]{ 10597,10460,11848,12939,15244}),
                ("2015", new long[]{ 10556,10362,10993,13467,13850}),
                ("2016", new long[]{ 10471,10261,9965,8969,8709}),
                ("2017", new long[]{ 10422,10384,10165,11063,10493}),
                ("2018", new long[]{ 10496,10375,10543,7683,7175}),
                ("2019", new long[]{ 10446,10266,10422,14109,13302}),
                ("2020", new long[]{ 10414,10213,10315,14454,12343}),
                ("2021", new long[]{ 10310, 10228, 10393, 10587, 10917}),
                ("2022", new long[]{ 10350, 10101, 10090, 8928, 9047}),
            };

            // 建立CP模型.
            CpModel model = new CpModel();

            //定義變數:各類資產配置比例
            IntVar a = model.NewIntVar(0, 100, "a"); //銀行理財
            IntVar b = model.NewIntVar(0, 100, "b"); //貨幣基金
            IntVar c = model.NewIntVar(0, 100, "c"); //債卷基金
            IntVar d = model.NewIntVar(0, 100, "d"); //股票基金
            IntVar e = model.NewIntVar(0, 100, "e"); //股票

            //建立約束條件:配置比例總和為100%
            model.Add(a + b + c + d + e <= 100);
            model.Add(a + b + c + d + e >= 100);

            //建立約束條件:限定低風險配置比例
            //model.Add(a >= 40);
            //model.Add(d + e <= 40);

            //定義變數陣列:單年年末資金
            IntVar[] yearResults = new IntVar[data.Length];
            //定義變數陣列:單年收益率
            IntVar[] yearRatios = new IntVar[data.Length];

            for (int i = 0; i<data.Length; i++)
            {
                var yearItem = data[i];

                //定義變數:當前年度收益率
                IntVar ratio = model.NewIntVar(0, 100 * 10000 * 3, $"ratio{i}");
                model.Add(ratio == a * yearItem.values[0] + b * yearItem.values[1] + c * yearItem.values[2] + d * yearItem.values[3] + e * yearItem.values[4]);
                yearRatios[i] = ratio;

                //建立約束條件:單年最大允許虧損比例
                model.Add(ratio >= Convert.ToInt32(100 * (1 - allowableMaximumLossRatio)) * 10000);

                //定義變數:當前年末資金
                IntVar resultA = model.NewIntVar(0, 100 * 100 * 10000 * Convert.ToInt64(Math.Pow(3, i+1)), $"resultA{i}");
                model.AddMultiplicationEquality(resultA, i==0? model.NewConstant(100) : yearResults[i-1], ratio);

                //定義變數:由於原生資料的收益率和配置比例是使用轉換後的整數計算的,所以這裡使用當前年末資金除以100*10000
                IntVar result = model.NewIntVar(0, 100 * Convert.ToInt64(Math.Pow(3, i+1)), $"result{i}");
                model.AddDivisionEquality(result, resultA, model.NewConstant(100 * 10000));
                yearResults[i] = result;
            }

            //設定求解目標為最終資金最大
            model.Maximize(yearResults[data.Length -1]);

            //求解
            CpSolver solver = new CpSolver();
            CpSolverStatus status = solver.Solve(model);

            //輸出求解結果
            if (status == CpSolverStatus.Optimal || status == CpSolverStatus.Feasible)
            {
                Console.WriteLine("銀行理財配置比: " + solver.Value(a)+"%");
                Console.WriteLine("貨幣基金配置比: " + solver.Value(b)+"%");
                Console.WriteLine("債卷基金配置比: " + solver.Value(c)+"%");
                Console.WriteLine("股票基金配置比: " + solver.Value(d)+"%");
                Console.WriteLine("股票配置比: " + solver.Value(e)+"%");

                for (int i = 0; i<data.Length; i++)
                {
                    Console.WriteLine($"{data[i].year} 年末資金:{solver.Value(yearResults[i])} 收益率:{String.Format("{0:P}", solver.Value(yearRatios[i]) / 1000000.00 - 1)}");
                }

                Console.WriteLine($"最終資金: {solver.ObjectiveValue}");
                Console.WriteLine($"年化收益率: {String.Format("{0:P}", Math.Pow((solver.ObjectiveValue - 100)/100, 1.00/data.Length)-1)}");
            }
            else
            {
                Console.WriteLine("求解失敗,未找到合適結果.");
            }

            Console.WriteLine($"求解耗時: {solver.WallTime()}s");

Github地址:程式碼

相關文章