// (c) 2023-2025 Fair Isaac Corporation using System; using System.Linq; using System.Collections.Generic; using Optimizer.Objects; using Optimizer.Objects; using Optimizer; using static Optimizer.Objects.Utils; namespace XpressExamples { /// Modeling a MIP problem to perform portfolio optimization. /// /// There are a number of shares (NSHARES) available in which to invest. /// The problem is to split the available capital between these shares /// to maximize return on investment, while satisfying certain /// constraints on the portfolio: /// frac[v]) <= MAXRISK); // Limits on geographical distribution prob.AddConstraints(REGIONS.Length, r => Sum(Enumerable.Range(0, SHARES.Length).Where(s => LOC[r, s]).Select(v => frac[v])).In(MINREG, MAXREG) ); // Diversification across industry sectors prob.AddConstraints(TYPES.Length, t => Sum(Enumerable.Range(0, SHARES.Length).Where(s => SEC[t, s]).Select(v => frac[v])) <= MAXSEC); // Spend all the capital prob.AddConstraint(Sum(frac) == 1); // Limit the total number of assets prob.AddConstraint(Sum(buy) <= MAXNUM); // Linking the variables for (int s = 0; s < SHARES.Length; s++) { prob.AddConstraint(frac[s] <= buy[s] * MAXVAL); prob.AddConstraint(frac[s] >= buy[s] * MINVAL); } // Set a time limit of 10 seconds prob.TimeLimit = 10; // Solve the problem prob.Optimize(); Console.WriteLine("Problem status: " + prob.MIPStatus); if (prob.MIPStatus != Optimizer.MIPStatus.Solution && prob.MIPStatus != Optimizer.MIPStatus.Optimal) throw new Exception("optimization failed with status " + prob.MIPStatus); // Solution printing Console.WriteLine("Total return: " + prob.ObjVal); double[] sol = prob.GetSolution(); for (int s = 0; s < SHARES.Length; s++) if (buy[s].GetValue(sol) > 0,5) Console.WriteLine(" " + s + ": " + frac[s].GetValue(sol) * 100 + "% (" + buy[s].GetValue(sol) + ")"); } } /// Read a data vector /// Data type. /// Token provider /// Function to turn a string token into an instance of T. /// The next vector read from tokens. private static T[] ReadVector(IEnumerator tokens, Func makeData) { List data = new List(); while (tokens.MoveNext()) { string token = tokens.Current; if (token.Equals(";")) // Semicolon terminates vector break; data.Add(makeData(token)); } return data.ToArray(); } /// Read a table of booleans. /// /// Returns an nrow by ncol table of booleans that is true only in the /// positions that are specified in tokens. /// /// Token provider. /// Number of rows. /// Number of columns. /// nrow by ncol boolean array. private static bool[,] ReadBoolTable(IEnumerator tokens, int nrow, int ncol) { bool[,] table = new bool[nrow, ncol]; for (int r = 0; r < nrow; ++r) { while (tokens.MoveNext()) { string token = tokens.Current; if (token.Equals(";")) break; // Semiconlon terminates row table[r, Int32.Parse(token)] = true; } } return table; } /// Fill the static data fields. private static void ReadData() { // Split the file content into tokens IEnumerator tokens = System.IO.File.ReadAllLines(DATAFILE) .SelectMany(s => System.Text.RegularExpressions.Regex.Split(s, "\\s+")) // Split tokens at whitespace .SelectMany(s => (s.Length > 1 && s.EndsWith(";")) ? new string[] { s.Substring(0, s.Length - 1), ";" } : new string[] { s }) // Split comma into separate token .Where(s => s.Length > 0) // filter empty strings .GetEnumerator(); while (tokens.MoveNext()) { string token = tokens.Current; if (token.Equals("SHARES:")) { SHARES = ReadVector(tokens, s => s); } else if (token.Equals("REGIONS:")) { REGIONS = ReadVector(tokens, s => s); } else if (token.Equals("TYPES:")) { TYPES = ReadVector(tokens, s => s); } else if (token.Equals("RISK:")) { RISK = ReadVector(tokens, s => Int32.Parse(s)); } else if (token.Equals("RET:")) { RET = ReadVector(tokens, s => Double.Parse(s)); } else if (token.Equals("LOC:")) LOC = ReadBoolTable(tokens, REGIONS.Length, SHARES.Length); else if (token.Equals("SEC:")) SEC = ReadBoolTable(tokens, TYPES.Length, SHARES.Length); } } } }