Folio - Examples from 'Getting Started'
|
|
Type: | Portfolio optimization |
Rating: | 3 (intermediate) |
Description: | Different versions of a portfolio optimization problem. Basic modelling and solving tasks:
|
File(s): | Folio.cs, Folio.csproj, FolioCB.cs, FolioCB.csproj, FolioHeuristic.cs, FolioHeuristic.csproj, FolioIIS.cs, FolioIIS.csproj, FolioInit.cs, FolioInit.csproj, FolioMip1.cs, FolioMip1.csproj, FolioMip2.cs, FolioMip2.csproj, FolioMipIIS.cs, FolioMipIIS.csproj, FolioQC.cs, FolioQC.csproj, FolioQP.cs, FolioQP.csproj |
|
Folio.cs |
// (c) 2023-2024 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 { /// <summary>Modeling a MIP problem to perform portfolio optimization.</summary> /// <remarks> /// 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: /// <list type="bullet'> /// <item><description> /// A maximum of 7 distinct shares can be invest in. /// </description></item> /// <item><description> /// Each share has an associated industry sector and geographic /// region, and the capital may not be overly concentrated in any /// one sector or region. /// </description></item> /// <item><description> /// Some of the shares are considered to be high-risk, and the /// maximum investment in these shares is limited. /// </description></item> /// </list> /// </remarks> class Folio { static readonly String DATAFILE = (Environment.GetEnvironmentVariable("EXAMPLE_DATA_DIR") != null ? Environment.GetEnvironmentVariable("EXAMPLE_DATA_DIR") : ".") + "/folio10.cdat"; static readonly int MAXNUM = 7; /* Max. number of different assets */ static readonly double MAXRISK = 1.0 / 3; /* Max. investment into high-risk values */ static readonly double MINREG = 0.2; /* Min. investment per geogr. region */ static readonly double MAXREG = 0.5; /* Max. investment per geogr. region */ static readonly double MAXSEC = 0.25; /* Max. investment per ind. sector */ static readonly double MAXVAL = 0.2; /* Max. investment per share */ static readonly double MINVAL = 0.1; /* Min. investment per share */ static double[] RET; /* Estimated return in investment */ static int[] RISK; /* High-risk values among shares */ static bool[,] LOC; /* Geogr. region of shares */ static bool[,] SEC; /* Industry sector of shares */ static String[] SHARES; static String[] REGIONS; static String[] TYPES; public static void Main(string[] args) { ReadData(); // Read data from file using (XpressProblem prob = new XpressProblem()) { // Create the decision variables // Fraction of capital used per share Variable[] frac = prob.AddVariables(SHARES.Length) .WithUB(MAXVAL) .WithName("frac {0}") .ToArray(); // 1 if asset is in portfolio, 0 otherwise Variable[] buy = prob.AddVariables(SHARES.Length) .WithType(ColumnType.Binary) .WithName("buy {0}") .ToArray(); // Objective: total return prob.SetObjective(ScalarProduct(frac, RET), Optimizer.ObjSense.Maximize); // Limit the percentage of high-risk values prob.AddConstraint(Sum(RISK, v => 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.0; // 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) + ")"); } } /// <summary>Read a data vector</summary> /// <typeparam name="T">Data type.</typeparam> /// <param name="tokens">Token provider</param> /// <param name="makeData">Function to turn a <c>string</c> token into an instance of <c>T</c>.</param> /// <returns>The next vector read from <c>tokens</c>.</returns> private static T[] ReadVector<T>(IEnumerator<string> tokens, Func<string, T> makeData) { List<T> data = new List<T>(); while (tokens.MoveNext()) { string token = tokens.Current; if (token.Equals(";")) // Semicolon terminates vector break; data.Add(makeData(token)); } return data.ToArray(); } /// <summary>Read a table of booleans.</summary> /// <remarks> /// Returns an <c>nrow</c> by <c>ncol</c> table of booleans that is true only in the /// positions that are specified in <c>tokens</c>. /// </remarks> /// <param name="tokens">Token provider.</param> /// <param name="nrow">Number of rows.</param> /// <param name="ncol">Number of columns.</param> /// <returns><c>nrow</c> by <c>ncol</c> boolean array.</returns> private static bool[,] ReadBoolTable(IEnumerator<string> 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; } /// <summary>Fill the static data fields.</summary> private static void ReadData() { // Split the file content into tokens IEnumerator<string> 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); } } } } |
Folio.csproj |
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net5.0</TargetFramework> <IsPackable>false</IsPackable> <XpressExampleFiles Condition="'$(XpressExampleFiles)'==''">../../../data</XpressExampleFiles> </PropertyGroup> <ItemGroup> <Content Include="$(XpressExampleFiles)/folio10.cdat"> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </Content> </ItemGroup> <ItemGroup> <PackageReference Include="FICO.Xpress.XPRSdn" Version="41.1.1" /> <!-- Version 41.1.1 or later --> </ItemGroup> <!-- This is for execution with "dotnet run" and friends which runs from the project directory rather than the output directory. --> <Target Name="CopyExampleData" AfterTargets="AfterBuild"> <Copy SourceFiles="$(XpressExampleFiles)/folio10.cdat" DestinationFolder="$(ProjectDir)" /> </Target> </Project> |
FolioCB.cs |
// (c) 2023-2024 Fair Isaac Corporation using System; using System.Linq; using System.Collections.Generic; using Optimizer.Objects; using Optimizer; using static Optimizer.Objects.Utils; namespace XpressExamples { /// <summary>Modeling a MIP problem to perform portfolio optimization.</summary> /// <remarks>Defining an integer solution callback</remarks> class FolioCB { /* Path to Data file */ private static readonly String DATAFILE = (Environment.GetEnvironmentVariable("EXAMPLE_DATA_DIR") != null ? Environment.GetEnvironmentVariable("EXAMPLE_DATA_DIR") : ".") + "/folio10.cdat"; private static readonly int MAXNUM = 15; /* Max. number of different assets */ private static readonly double MAXRISK = 1.0 / 3; /* Max. investment into high-risk values */ private static readonly double MINREG = 0.2; /* Min. investment per geogr. region */ private static readonly double MAXREG = 0.5; /* Max. investment per geogr. region */ private static readonly double MAXSEC = 0.25; /* Max. investment per ind. sector */ private static readonly double MAXVAL = 0.2; /* Max. investment per share */ private static readonly double MINVAL = 0.1; /* Min. investment per share */ private static double[] RET; /* Estimated return in investment */ private static int[] RISK; /* High-risk values among shares */ private static bool[,] LOC; /* Geogr. region of shares */ private static bool[,] SEC; /* Industry sector of shares */ private static String[] SHARES; private static String[] REGIONS; private static String[] TYPES; /* Fraction of capital used per share */ private static Variable[] frac; /* 1 if asset is in portfolio, 0 otherwise */ private static Variable[] buy; private static void PrintProblemStatus(XpressProblem prob) { Console.WriteLine("Problem status:"); Console.WriteLine($"\tSolve status: {prob.SolveStatus}"); Console.WriteLine($"\tSol status: {prob.SolStatus}"); } private static void PrintProblemSolution(XpressProblem prob) { double[] sol = prob.GetSolution(); Console.WriteLine($"Total return: {prob.ObjVal}"); foreach (int i in Enumerable.Range(0, SHARES.Length)) { if (buy[i].GetValue(sol) > 0.5) Console.WriteLine(String.Format("{0} : {1:f2}% ({2:f1})", i, 100.0 * frac[i].GetValue(sol), buy[i].GetValue(sol))); } } public static void Main(string[] args) { ReadData(); using (XpressProblem prob = new XpressProblem()) { // Output all messages. prob.callbacks.AddMessageCallback(DefaultMessageListener.Console); /****VARIABLES****/ frac = prob.AddVariables(SHARES.Length) /* Fraction of capital used per share */ .WithName(i => $"frac_{i}") /* Upper bounds on the investment per share */ .WithUB(MAXVAL) .ToArray(); buy = prob.AddVariables(SHARES.Length) .WithName(i => $"buy_{i}") .WithType(ColumnType.Binary) .ToArray(); /**** CONSTRAINTS ****/ /* Limit the percentage of high-risk values */ prob.AddConstraint(Sum(RISK.Length, i => frac[RISK[i]]).Leq(MAXRISK).SetName("Risk")); /* 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).Eq(1.0).SetName("Cap")); /* Limit the total number of assets */ prob.AddConstraint(Sum(buy).Leq(MAXNUM).SetName("MaxAssets")); /* Linking the variables */ prob.AddConstraints(SHARES.Length, i => frac[i].Geq(buy[i].Mul(MINVAL)).SetName($"link_lb_{i}") ); prob.AddConstraints(SHARES.Length, i => frac[i].Leq(buy[i].Mul(MAXVAL)).SetName($"link_ub_{i}") ); /* Objective: maximize total return */ prob.SetObjective( ScalarProduct(frac, RET), Optimizer.ObjSense.Maximize ); /* Callback for each new integer solution found */ prob.callbacks.AddIntsolCallback(p => { PrintProblemSolution(p); }); /* Solve */ prob.Optimize(); /* Solution printing */ PrintProblemStatus(prob); PrintProblemSolution(prob); } } /// <summary>Read a data vector</summary> /// <typeparam name="T">Data type.</typeparam> /// <param name="tokens">Token provider</param> /// <param name="makeData">Function to turn a <c>string</c> token into an instance of <c>T</c>.</param> /// <returns>The next vector read from <c>tokens</c>.</returns> private static T[] ReadVector<T>(IEnumerator<string> tokens, Func<string, T> makeData) { List<T> data = new List<T>(); while (tokens.MoveNext()) { string token = tokens.Current; if (token.Equals(";")) // Semicolon terminates vector break; data.Add(makeData(token)); } return data.ToArray(); } /// <summary>Read a table of booleans.</summary> /// <remarks> /// Returns an <c>nrow</c> by <c>ncol</c> table of booleans that is true only in the /// positions that are specified in <c>tokens</c>. /// </remarks> /// <param name="tokens">Token provider.</param> /// <param name="nrow">Number of rows.</param> /// <param name="ncol">Number of columns.</param> /// <returns><c>nrow</c> by <c>ncol</c> boolean array.</returns> private static bool[,] ReadBoolTable(IEnumerator<string> 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; } /// <summary>Fill the static data fields.</summary> private static void ReadData() { // Split the file content into tokens IEnumerator<string> 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); } } } } |
FolioCB.csproj |
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net5.0</TargetFramework> <IsPackable>false</IsPackable> <XpressExampleFiles Condition="'$(XpressExampleFiles)'==''">../../../data</XpressExampleFiles> </PropertyGroup> <ItemGroup> <Content Include="$(XpressExampleFiles)/folio10.cdat"> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </Content> </ItemGroup> <ItemGroup> <PackageReference Include="FICO.Xpress.XPRSdn" Version="41.1.1" /> <!-- Version 41.1.1 or later --> </ItemGroup> <!-- This is for execution with "dotnet run" and friends which runs from the project directory rather than the output directory. --> <Target Name="CopyExampleData" AfterTargets="AfterBuild"> <Copy SourceFiles="$(XpressExampleFiles)/folio10.cdat" DestinationFolder="$(ProjectDir)" /> </Target> </Project> |
FolioHeuristic.cs |
// (c) 2023-2024 Fair Isaac Corporation using System; using System.Linq; using System.Collections; using Optimizer.Objects; using Optimizer; using static System.Linq.Enumerable; using static Optimizer.Objects.Utils; namespace XpressExamples { /// <summary> Modeling a small LP problem to perform portfolio optimization.</summary> /// <remarks>Heuristic solution</remarks> class FolioHeuristic { /* Max. number of different assets */ private static readonly int MAXNUM = 4; /* Number of shares */ private static readonly int NSHARES = 10; /* Number of high-risk shares */ private static readonly int NRISK = 5; /* Number of North-American shares */ private static readonly int NNA = 4; /* Estimated return in investment */ private static readonly double[] RET = new double[] { 5, 17, 26, 12, 8, 9, 7, 6, 31, 21 }; /* High-risk values among shares */ private static readonly int[] RISK = new int[] { 1, 2, 3, 8, 9 }; /* Shares issued in N.-America */ private static readonly int[] NA = new int[] { 0, 1, 2, 3 }; public XpressProblem prob; /* Fraction of capital used per share */ public Variable[] frac; /* 1 if asset is in portfolio, 0 otherwise */ public Variable[] buy; public FolioHeuristic(XpressProblem p) { prob = p; /****VARIABLES****/ frac = p.AddVariables(NSHARES) /* Fraction of capital used per share */ .WithName(i => $"frac_{i}") /* Upper bounds on the investment per share */ .WithUB(0.3) .ToArray(); buy = p.AddVariables(NSHARES) /* Fraction of capital used per share */ .WithName(i => $"buy_{i}") .WithType(ColumnType.Binary) .ToArray(); } private static void PrintProblemStatus(XpressProblem prob) { Console.WriteLine("Problem status:"); Console.WriteLine($"\tSolve status: {prob.SolveStatus}"); Console.WriteLine($"\tLP status: {prob.LPStatus}"); Console.WriteLine($"\tMIP status: {prob.MIPStatus}"); Console.WriteLine($"\tSol status: {prob.SolStatus}"); } private static void PrintProblemSolution(FolioHeuristic folio, String solveFlag) { XpressProblem prob = folio.prob; double[] sol = prob.GetSolution(); Console.WriteLine($"Total return {solveFlag}: {prob.ObjVal}"); foreach (int i in Range(0, NSHARES)) { Console.WriteLine(String.Format("{0} : {1:f2}% ({2:f1})", folio.frac[i].GetName(), 100.0 * folio.frac[i].GetValue(sol), folio.buy[i].GetValue(sol))); } } private static void Model(FolioHeuristic folio) { XpressProblem prob = folio.prob; // Output all messages. prob.callbacks.AddMessageCallback(DefaultMessageListener.Console); /**** CONSTRAINTS ****/ /* Limit the percentage of high-risk values */ prob.AddConstraint(Sum(NRISK, i => folio.frac[RISK[i]]).Leq(1.0 / 3.0).SetName("Risk")); /* Minimum amount of North-American values */ prob.AddConstraint(Sum(NNA, i => folio.frac[NA[i]]).Geq(0.5).SetName("NA")); /* Spend all the capital */ prob.AddConstraint(Sum(folio.frac).Eq(1.0).SetName("Cap")); /* Limit the total number of assets */ prob.AddConstraint(Sum(folio.buy).Leq(MAXNUM).SetName("MaxAssets")); /* Linking the variables */ /* frac .<= buy */ prob.AddConstraints(NSHARES, i => folio.frac[i].Leq(folio.buy[i]).SetName($"link_{i}") ); /* Objective: maximize total return */ prob.SetObjective( ScalarProduct(folio.frac, RET), Optimizer.ObjSense.Maximize ); } private static void SolveHeuristic(FolioHeuristic folio) { XpressProblem p = folio.prob; /* Disable automatic cuts - we use our own */ p.CutStrategy = (int)Optimizer.CutStrategy.None; /* Switch presolve off */ p.Presolve = (int)Optimizer.Presolve.None; p.MIPPresolve = 0; /* Get feasibility tolerance */ double tol = p.FeasTol; /* Solve the LP-problem */ p.LpOptimize(); /* Get Solution */ double[] sol = p.GetSolution(); /* Basis information */ int[] rowstat = new int[p.Rows]; int[] colstat = new int[p.Cols]; /* Save the current basis */ p.GetBasis(rowstat, colstat); /* Fix all variables `buy' for which `frac' is at 0 or at a relatively large value */ double[] fsol = new double[NSHARES]; foreach (int i in Range(0, NSHARES)) { /* Get the solution values of `frac' */ fsol[i] = folio.frac[i].GetValue(sol); if (fsol[i] < tol) folio.buy[i].Fix(0); else if (fsol[i] > 0.2 - tol) folio.buy[i].Fix(1); } /* Solve with the new bounds on 'buy' */ p.MipOptimize(); PrintProblemStatus(p); PrintProblemSolution(folio, "Heuristic solution"); /* Reset variables to their original bounds */ foreach (int i in Range(0, NSHARES)) { if ((fsol[i] < tol) || (fsol[i] > 0.2 - tol)) { folio.buy[i].SetLB(0); folio.buy[i].SetUB(1); } } /* Load basis */ p.LoadBasis(rowstat, colstat); } public static void Main(string[] args) { using (XpressProblem prob = new XpressProblem()) { /* Solve with heuristic */ FolioHeuristic folio = new FolioHeuristic(prob); Model(folio); SolveHeuristic(folio); } using (XpressProblem prob = new XpressProblem()) { FolioHeuristic folio = new FolioHeuristic(prob); Model(folio); /* Solve */ folio.prob.Optimize(); /* Solution printing */ PrintProblemStatus(prob); PrintProblemSolution(folio, "Exact Solve"); } } } } |
FolioHeuristic.csproj |
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net5.0</TargetFramework> <IsPackable>false</IsPackable> <XpressExampleFiles Condition="'$(XpressExampleFiles)'==''">../../../data</XpressExampleFiles> </PropertyGroup> <ItemGroup> <PackageReference Include="FICO.Xpress.XPRSdn" Version="41.1.1" /> <!-- Version 41.1.1 or later --> </ItemGroup> </Project> |
FolioIIS.cs |
// (c) 2023-2024 Fair Isaac Corporation using System; using System.Linq; using System.Collections.Generic; using Optimizer.Objects; using Optimizer; using static Optimizer.Objects.Utils; namespace XpressExamples { /// <summary>Modeling a MIP problem to perform portfolio optimization.</summary> /// <remarks> /// Uses infeasible model parameter values and illustrates retrieving IIS. /// </remarks> class FolioIIS { /* Path to Data file */ private static readonly String DATAFILE = (Environment.GetEnvironmentVariable("EXAMPLE_DATA_DIR") != null ? Environment.GetEnvironmentVariable("EXAMPLE_DATA_DIR") : ".") + "/folio10.cdat"; private static readonly int MAXNUM = 5; /* Max. number of different assets */ private static readonly double MAXRISK = 1.0 / 3; /* Max. investment into high-risk values */ private static readonly double MINREG = 0.1; /* Min. investment per geogr. region */ private static readonly double MAXREG = 0.2; /* Max. investment per geogr. region */ private static readonly double MAXSEC = 0.1; /* Max. investment per ind. sector */ private static readonly double MAXVAL = 0.2; /* Max. investment per share */ private static readonly double MINVAL = 0.1; /* Min. investment per share */ private static double[] RET; /* Estimated return in investment */ private static int[] RISK; /* High-risk values among shares */ private static bool[,] LOC; /* Geogr. region of shares */ private static bool[,] SEC; /* Industry sector of shares */ private static String[] SHARES; private static String[] REGIONS; private static String[] TYPES; /* Fraction of capital used per share */ private static Variable[] frac; /* 1 if asset is in portfolio, 0 otherwise */ private static Variable[] buy; private static void PrintProblemStatus(XpressProblem prob) { Console.WriteLine("Problem status:"); Console.WriteLine($"\tSolve status: {prob.SolveStatus}"); Console.WriteLine($"\tLP status: {prob.LPStatus}"); Console.WriteLine($"\tSol status: {prob.SolStatus}"); } public static void Main(string[] args) { ReadData(); using (XpressProblem prob = new XpressProblem()) { // Output all messages. prob.callbacks.AddMessageCallback(DefaultMessageListener.Console); /****VARIABLES****/ frac = prob.AddVariables(SHARES.Length) /* Fraction of capital used per share */ .WithName(i => $"frac_{i}") /* Upper bounds on the investment per share */ .WithUB(MAXVAL) .ToArray(); buy = prob.AddVariables(SHARES.Length) .WithName(i => $"buy_{i}") .WithType(ColumnType.Binary) .ToArray(); /**** CONSTRAINTS ****/ /* Limit the percentage of high-risk values */ prob.AddConstraint(Sum(RISK.Length, i => frac[RISK[i]]).Leq(MAXRISK).SetName("Risk")); /* 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).SetName($"MinMaxReg_{REGIONS[r]}") ); /* Diversification across industry sectors */ prob.AddConstraints(TYPES.Length, t => Sum(Enumerable.Range(0, SHARES.Length).Where(s => SEC[t, s]).Select(v => frac[v])).In(0.0, MAXSEC).SetName($"LimSec({TYPES[t]})") ); /* Spend all the capital */ prob.AddConstraint(Sum(frac).Eq(1.0).SetName("Cap")); /* Limit the total number of assets */ prob.AddConstraint(Sum(buy).Leq(MAXNUM).SetName("MaxAssets")); /* Linking the variables */ prob.AddConstraints(SHARES.Length, i => frac[i].Geq(buy[i].Mul(MINVAL)).SetName($"link_lb_{i}") ); prob.AddConstraints(SHARES.Length, i => frac[i].Leq(buy[i].Mul(MAXVAL)).SetName($"link_ub_{i}") ); /* Objective: maximize total return */ prob.SetObjective( ScalarProduct(frac, RET), Optimizer.ObjSense.Maximize ); /* Solve */ prob.Optimize(); /* Solution printing */ PrintProblemStatus(prob); if (prob.SolStatus == Optimizer.SolStatus.Infeasible) { // Check there is at least one IIS int status = prob.FirstIIS(1); if (status != 0) throw new Exception("FirstIIS() failed with status " + status); int iisIndex = 1; // First IIS has index 1 do { XPRSprob.IISStatusInfo info = prob.IISStatus(); Console.WriteLine($"IIS has {info.rowsizes[iisIndex]} constraints and {info.colsizes[iisIndex]} columns, {info.numinfeas[iisIndex]} infeasibilities with an infeasibility of {info.suminfeas[iisIndex]}"); IIS data = prob.GetIIS(iisIndex); Console.WriteLine("Variables in IIS:"); foreach (IISVariable v in data.Variables) { // Note that the IISVariable class has more fields than // we print here. See the reference documentation for // details. Console.WriteLine($"\t{v.Variable.GetName()} {v.Domain}"); } Console.WriteLine("Constraints in IIS:"); foreach (IISConstraint c in data.Constraints) { // Note that the IISVariable class has more fields than // we print here. See the reference documentation for // details. Console.WriteLine($"\t{c.Constraint.GetName()}"); } ++iisIndex; // Prepare for next IIS (if any) } while (prob.NextIIS() == 0); } } } /// <summary>Read a data vector</summary> /// <typeparam name="T">Data type.</typeparam> /// <param name="tokens">Token provider</param> /// <param name="makeData">Function to turn a <c>string</c> token into an instance of <c>T</c>.</param> /// <returns>The next vector read from <c>tokens</c>.</returns> private static T[] ReadVector<T>(IEnumerator<string> tokens, Func<string, T> makeData) { List<T> data = new List<T>(); while (tokens.MoveNext()) { string token = tokens.Current; if (token.Equals(";")) // Semicolon terminates vector break; data.Add(makeData(token)); } return data.ToArray(); } /// <summary>Read a table of booleans.</summary> /// <remarks> /// Returns an <c>nrow</c> by <c>ncol</c> table of booleans that is true only in the /// positions that are specified in <c>tokens</c>. /// </remarks> /// <param name="tokens">Token provider.</param> /// <param name="nrow">Number of rows.</param> /// <param name="ncol">Number of columns.</param> /// <returns><c>nrow</c> by <c>ncol</c> boolean array.</returns> private static bool[,] ReadBoolTable(IEnumerator<string> 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; } /// <summary>Fill the static data fields.</summary> private static void ReadData() { // Split the file content into tokens IEnumerator<string> 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); } } } } |
FolioIIS.csproj |
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net5.0</TargetFramework> <IsPackable>false</IsPackable> <XpressExampleFiles Condition="'$(XpressExampleFiles)'==''">../../../data</XpressExampleFiles> </PropertyGroup> <ItemGroup> <Content Include="$(XpressExampleFiles)/folio10.cdat"> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </Content> </ItemGroup> <ItemGroup> <PackageReference Include="FICO.Xpress.XPRSdn" Version="41.1.1" /> <!-- Version 41.1.1 or later --> </ItemGroup> <!-- This is for execution with "dotnet run" and friends which runs from the project directory rather than the output directory. --> <Target Name="CopyExampleData" AfterTargets="AfterBuild"> <Copy SourceFiles="$(XpressExampleFiles)/folio10.cdat" DestinationFolder="$(ProjectDir)" /> </Target> </Project> |
FolioInit.cs |
// (c) 2023-2024 Fair Isaac Corporation using System; using Optimizer.Objects; using Optimizer; using static Optimizer.Objects.Utils; namespace XpressExamples { /// <summary>Modeling a small LP problem to perform portfolio optimization.</summary> class FolioInit { /* Number of shares */ private static readonly int NSHARES = 10; /* Number of high-risk shares */ private static readonly int NRISK = 5; /* Number of North-American shares */ private static readonly int NNA = 4; /* Estimated return in investment */ private static readonly double[] RET = new double[] { 5, 17, 26, 12, 8, 9, 7, 6, 31, 21 }; /* High-risk values among shares */ private static readonly int[] RISK = new int[] { 1, 2, 3, 8, 9 }; /* Shares issued in N.-America */ private static readonly int[] NA = new int[] { 0, 1, 2, 3 }; private static void PrintProblemStatus(XpressProblem prob) { Console.WriteLine("Problem status:"); Console.WriteLine($"\tSolve status: {prob.SolveStatus}"); Console.WriteLine($"\tSol status: {prob.SolStatus}"); } public static void Main(string[] args) { using (XpressProblem prob = new XpressProblem()) { // Output all messages. prob.callbacks.AddMessageCallback(DefaultMessageListener.Console); /****VARIABLES****/ Variable[] frac = prob.AddVariables(NSHARES) /* Fraction of capital used per share */ .WithName(i => $"frac_{i}") /* Upper bounds on the investment per share */ .WithUB(0.3) .ToArray(); /**** CONSTRAINTS ****/ /* Limit the percentage of high-risk values */ prob.AddConstraint(Sum(NRISK, i => frac[RISK[i]]).Leq(1.0 / 3.0).SetName("Risk")); /* Minimum amount of North-American values */ prob.AddConstraint(Sum(NNA, i => frac[NA[i]]).Geq(0.5).SetName("NA")); /* Spend all the capital */ prob.AddConstraint(Sum(frac).Eq(1.0).SetName("Cap")); /* Objective: maximize total return */ prob.SetObjective( ScalarProduct(frac, RET), Optimizer.ObjSense.Maximize ); /* Solve */ prob.Optimize(); /* Solution printing */ PrintProblemStatus(prob); double[] sol = prob.GetSolution(); Console.WriteLine($"Total return: {prob.ObjVal}"); foreach (Variable f in frac) { Console.WriteLine(String.Format("{0} : {1:f2}%", f.GetName(), 100.0 * f.GetValue(sol))); } } } } } |
FolioInit.csproj |
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net5.0</TargetFramework> <IsPackable>false</IsPackable> <XpressExampleFiles Condition="'$(XpressExampleFiles)'==''">../../../data</XpressExampleFiles> </PropertyGroup> <ItemGroup> <PackageReference Include="FICO.Xpress.XPRSdn" Version="41.1.1" /> <!-- Version 41.1.1 or later --> </ItemGroup> </Project> |
FolioMip1.cs |
// (c) 2023-2024 Fair Isaac Corporation using System; using System.Linq; using Optimizer.Objects; using Optimizer; using static Optimizer.Objects.Utils; namespace XpressExamples { /// <summary>Modeling a small MIP problem to perform portfolio optimization.</summary> class FolioMip1 { /* Max. number of different assets */ private static readonly int MAXNUM = 4; /* Number of shares */ private static readonly int NSHARES = 10; /* Number of high-risk shares */ private static readonly int NRISK = 5; /* Number of North-American shares */ private static readonly int NNA = 4; /* Estimated return in investment */ private static readonly double[] RET = new double[] { 5, 17, 26, 12, 8, 9, 7, 6, 31, 21 }; /* High-risk values among shares */ private static readonly int[] RISK = new int[] { 1, 2, 3, 8, 9 }; /* Shares issued in N.-America */ private static readonly int[] NA = new int[] { 0, 1, 2, 3 }; private static void PrintProblemStatus(XpressProblem prob) { Console.WriteLine("Problem status:"); Console.WriteLine($"\tSolve status: {prob.SolveStatus}"); Console.WriteLine($"\tSol status: {prob.SolStatus}"); } public static void Main(string[] args) { using (XpressProblem prob = new XpressProblem()) { // Output all messages. prob.callbacks.AddMessageCallback(DefaultMessageListener.Console); /****VARIABLES****/ Variable[] frac = prob.AddVariables(NSHARES) /* Fraction of capital used per share */ .WithName(i => $"frac_{i}") /* Upper bounds on the investment per share */ .WithUB(0.3) .ToArray(); Variable[] buy = prob.AddVariables(NSHARES) /* Fraction of capital used per share */ .WithName(i => $"buy_{i}") .WithType(ColumnType.Binary) .ToArray(); /**** CONSTRAINTS ****/ /* Limit the percentage of high-risk values */ prob.AddConstraint(Sum(NRISK, i => frac[RISK[i]]).Leq(1.0 / 3.0).SetName("Risk")); /* Minimum amount of North-American values */ prob.AddConstraint(Sum(NNA, i => frac[NA[i]]).Geq(0.5).SetName("NA")); /* Spend all the capital */ prob.AddConstraint(Sum(frac).Eq(1.0).SetName("Cap")); /* Limit the total number of assets */ prob.AddConstraint(Sum(buy).Leq(MAXNUM).SetName("MaxAssets")); /* Linking the variables */ /* frac .<= buy */ prob.AddConstraints(NSHARES, i => frac[i].Leq(buy[i]).SetName($"link_{i}") ); /* Objective: maximize total return */ prob.SetObjective( ScalarProduct(frac, RET), Optimizer.ObjSense.Maximize ); /* Solve */ prob.Optimize(); /* Solution printing */ PrintProblemStatus(prob); double[] sol = prob.GetSolution(); Console.WriteLine($"Total return: {prob.ObjVal}"); foreach (String v in frac.Zip(buy, (f, b) => String.Format("{0} : {1:f2}% ({2:f1})", f.GetName(), 100.0 * f.GetValue(sol), b.GetValue(sol)))) { Console.WriteLine(v); } } } } } |
FolioMip1.csproj |
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net5.0</TargetFramework> <IsPackable>false</IsPackable> <XpressExampleFiles Condition="'$(XpressExampleFiles)'==''">../../../data</XpressExampleFiles> </PropertyGroup> <ItemGroup> <PackageReference Include="FICO.Xpress.XPRSdn" Version="41.1.1" /> <!-- Version 41.1.1 or later --> </ItemGroup> </Project> |
FolioMip2.cs |
// (c) 2023-2024 Fair Isaac Corporation using System; using Optimizer.Objects; using Optimizer; using static Optimizer.Objects.Utils; namespace XpressExamples { /// <summary>Modeling a small LP problem to perform portfolio optimization.</summary> /// <remarks>Imposing a minimum investment per share</remarks> class FolioMip2 { /* Number of shares */ private static readonly int NSHARES = 10; /* Number of high-risk shares */ private static readonly int NRISK = 5; /* Number of North-American shares */ private static readonly int NNA = 4; /* Estimated return in investment */ private static readonly double[] RET = new double[] { 5, 17, 26, 12, 8, 9, 7, 6, 31, 21 }; /* High-risk values among shares */ private static readonly int[] RISK = new int[] { 1, 2, 3, 8, 9 }; /* Shares issued in N.-America */ private static readonly int[] NA = new int[] { 0, 1, 2, 3 }; private static void PrintProblemStatus(XpressProblem prob) { Console.WriteLine("Problem status:"); Console.WriteLine($"\tSolve status: {prob.SolveStatus}"); Console.WriteLine($"\tSol status: {prob.SolStatus}"); } public static void Main(string[] args) { using (XpressProblem prob = new XpressProblem()) { // Output all messages. prob.callbacks.AddMessageCallback(DefaultMessageListener.Console); /****VARIABLES****/ Variable[] frac = prob.AddVariables(NSHARES) /* Fraction of capital used per share */ .WithName(i => $"frac_{i}") .WithType(ColumnType.SemiContinuous) /* Upper bounds on the investment per share */ .WithUB(0.3) /* Investment limit */ .WithLimit(0.1) .ToArray(); /**** CONSTRAINTS ****/ /* Limit the percentage of high-risk values */ prob.AddConstraint(Sum(NRISK, i => frac[RISK[i]]).Leq(1.0 / 3.0).SetName("Risk")); /* Minimum amount of North-American values */ prob.AddConstraint(Sum(NNA, i => frac[NA[i]]).Geq(0.5).SetName("NA")); /* Spend all the capital */ prob.AddConstraint(Sum(frac).Eq(1.0).SetName("Cap")); /* Objective: maximize total return */ prob.SetObjective( ScalarProduct(frac, RET), Optimizer.ObjSense.Maximize ); /* Solve */ prob.Optimize(); /* Solution printing */ PrintProblemStatus(prob); double[] sol = prob.GetSolution(); Console.WriteLine($"Total return: {prob.ObjVal}"); foreach (Variable f in frac) { Console.WriteLine(String.Format("{0} : {1:f2}%", f.GetName(), 100.0 * f.GetValue(sol))); } } } } } |
FolioMip2.csproj |
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net5.0</TargetFramework> <IsPackable>false</IsPackable> <XpressExampleFiles Condition="'$(XpressExampleFiles)'==''">../../../data</XpressExampleFiles> </PropertyGroup> <ItemGroup> <PackageReference Include="FICO.Xpress.XPRSdn" Version="41.1.1" /> <!-- Version 41.1.1 or later --> </ItemGroup> </Project> |
FolioMipIIS.cs |
// (c) 2023-2024 Fair Isaac Corporation using System; using System.Linq; using System.Collections.Generic; using Optimizer.Objects; using Optimizer; using static Optimizer.Objects.Utils; namespace XpressExamples { /// <summary>Modeling a MIP problem to perform portfolio optimization.</summary> /// <remarks> /// Same model as in FolioMip3.cs. /// Uses infeasible model parameter values and illustrates /// retrieving MIIS /// </remarks> class FolioMipIIS { /* Path to Data file */ private static readonly String DATAFILE = (Environment.GetEnvironmentVariable("EXAMPLE_DATA_DIR") != null ? Environment.GetEnvironmentVariable("EXAMPLE_DATA_DIR") : ".") + "/folio10.cdat"; private static readonly int MAXNUM = 5; /* Max. number of different assets */ private static readonly double MAXRISK = 1.0 / 4; /* Max. investment into high-risk values */ private static readonly double MINREG = 0.1; /* Min. investment per geogr. region */ private static readonly double MAXREG = 0.25; /* Max. investment per geogr. region */ private static readonly double MAXSEC = 0.15; /* Max. investment per ind. sector */ private static readonly double MAXVAL = 0.225; /* Max. investment per share */ private static readonly double MINVAL = 0.1; /* Min. investment per share */ private static double[] RET; /* Estimated return in investment */ private static int[] RISK; /* High-risk values among shares */ private static bool[,] LOC; /* Geogr. region of shares */ private static bool[,] SEC; /* Industry sector of shares */ private static String[] SHARES; private static String[] REGIONS; private static String[] TYPES; /* Fraction of capital used per share */ private static Variable[] frac; /* 1 if asset is in portfolio, 0 otherwise */ private static Variable[] buy; private static void PrintProblemStatus(XpressProblem prob) { Console.WriteLine("Problem status:"); Console.WriteLine($"\tSolve status: {prob.SolveStatus}"); Console.WriteLine($"\tLP status: {prob.LPStatus}"); Console.WriteLine($"\tMIP status: {prob.MIPStatus}"); Console.WriteLine($"\tSol status: {prob.SolStatus}"); } public static void Main(string[] args) { ReadData(); using (XpressProblem prob = new XpressProblem()) { // Output all messages. prob.callbacks.AddMessageCallback(DefaultMessageListener.Console); /****VARIABLES****/ frac = prob.AddVariables(SHARES.Length) /* Fraction of capital used per share */ .WithName(i => $"frac_{i}") /* Upper bounds on the investment per share */ .WithUB(MAXVAL) .ToArray(); buy = prob.AddVariables(SHARES.Length) .WithName(i => $"buy_{i}") .WithType(ColumnType.Binary) .ToArray(); /**** CONSTRAINTS ****/ /* Limit the percentage of high-risk values */ prob.AddConstraint(Sum(RISK.Length, i => frac[RISK[i]]).Leq(MAXRISK).SetName("Risk")); /* 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).SetName($"MinMaxReg_{REGIONS[r]}") ); /* Diversification across industry sectors */ prob.AddConstraints(TYPES.Length, t => Sum(Enumerable.Range(0, SHARES.Length).Where(s => SEC[t, s]).Select(v => frac[v])).In(0.0, MAXSEC).SetName($"LimSec({TYPES[t]})") ); /* Spend all the capital */ prob.AddConstraint(Sum(frac).Eq(1.0).SetName("Cap")); /* Limit the total number of assets */ prob.AddConstraint(Sum(buy).Leq(MAXNUM).SetName("MaxAssets")); /* Linking the variables */ prob.AddConstraints(SHARES.Length, i => frac[i].Geq(buy[i].Mul(MINVAL)).SetName($"link_lb_{i}") ); prob.AddConstraints(SHARES.Length, i => frac[i].Leq(buy[i].Mul(MAXVAL)).SetName($"link_ub_{i}") ); /* Objective: maximize total return */ prob.SetObjective( ScalarProduct(frac, RET), Optimizer.ObjSense.Maximize ); /* Solve */ prob.Optimize(); /* Solution printing */ PrintProblemStatus(prob); if (prob.SolStatus == Optimizer.SolStatus.Infeasible) { Console.WriteLine("MIP infeasible. Retrieving IIS."); // Check there is at least one IIS int status = prob.FirstIIS(1); if (status != 0) throw new Exception("FirstIIS() failed with status " + status); XPRSprob.IISStatusInfo info = prob.IISStatus(); Console.WriteLine($"IIS has {info.rowsizes[1]} constraints and {info.colsizes[1]} columns, {info.numinfeas[1]} infeasibilities with an infeasibility of {info.suminfeas[1]}"); IIS data = prob.GetIIS(1); Console.WriteLine("Variables in IIS:"); foreach (IISVariable v in data.Variables) { // Note that the IISVariable class has more fields than // we print here. See the reference documentation for // details. Console.WriteLine($"\t{v.Variable.GetName()} {v.Domain}"); } Console.WriteLine("Constraints in IIS:"); foreach (IISConstraint c in data.Constraints) { // Note that the IISVariable class has more fields than // we print here. See the reference documentation for // details. Console.WriteLine($"\t{c.Constraint.GetName()}"); } } } } /// <summary>Read a data vector</summary> /// <typeparam name="T">Data type.</typeparam> /// <param name="tokens">Token provider</param> /// <param name="makeData">Function to turn a <c>string</c> token into an instance of <c>T</c>.</param> /// <returns>The next vector read from <c>tokens</c>.</returns> private static T[] ReadVector<T>(IEnumerator<string> tokens, Func<string, T> makeData) { List<T> data = new List<T>(); while (tokens.MoveNext()) { string token = tokens.Current; if (token.Equals(";")) // Semicolon terminates vector break; data.Add(makeData(token)); } return data.ToArray(); } /// <summary>Read a table of booleans.</summary> /// <remarks> /// Returns an <c>nrow</c> by <c>ncol</c> table of booleans that is true only in the /// positions that are specified in <c>tokens</c>. /// </remarks> /// <param name="tokens">Token provider.</param> /// <param name="nrow">Number of rows.</param> /// <param name="ncol">Number of columns.</param> /// <returns><c>nrow</c> by <c>ncol</c> boolean array.</returns> private static bool[,] ReadBoolTable(IEnumerator<string> 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; } /// <summary>Fill the static data fields.</summary> private static void ReadData() { // Split the file content into tokens IEnumerator<string> 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); } } } } |
FolioMipIIS.csproj |
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net5.0</TargetFramework> <IsPackable>false</IsPackable> <XpressExampleFiles Condition="'$(XpressExampleFiles)'==''">../../../data</XpressExampleFiles> </PropertyGroup> <ItemGroup> <Content Include="$(XpressExampleFiles)/folio10.cdat"> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </Content> </ItemGroup> <ItemGroup> <PackageReference Include="FICO.Xpress.XPRSdn" Version="41.1.1" /> <!-- Version 41.1.1 or later --> </ItemGroup> <!-- This is for execution with "dotnet run" and friends which runs from the project directory rather than the output directory. --> <Target Name="CopyExampleData" AfterTargets="AfterBuild"> <Copy SourceFiles="$(XpressExampleFiles)/folio10.cdat" DestinationFolder="$(ProjectDir)" /> </Target> </Project> |
FolioQC.cs |
// (c) 2023-2024 Fair Isaac Corporation using System; using Optimizer.Objects; using Optimizer; using static Optimizer.Objects.Utils; using static System.Linq.Enumerable; using QuadExpression = Optimizer.Objects.QuadExpression; namespace XpressExamples { /// <summary>Modeling a small QCQP problem to perform portfolio optimization.</summary> /// <remarks>Maximize return with limit on variance</remarks> class FolioQC { /* Max. allowed variance */ private static readonly double MAXVAR = 0.55; /* Number of shares */ private static readonly int NSHARES = 10; /* Number of high-risk shares */ private static readonly int NRISK = 5; /* Number of North-American shares */ private static readonly int NNA = 4; /* Estimated return in investment */ private static readonly double[] RET = new double[] { 5, 17, 26, 12, 8, 9, 7, 6, 31, 21 }; /* High-risk values among shares */ private static readonly int[] RISK = new int[] { 1, 2, 3, 8, 9 }; /* Shares issued in N.-America */ private static readonly int[] NA = new int[] { 0, 1, 2, 3 }; /* Variance/covariance matrix of estimated returns */ private static readonly double[,] VAR = new double[,]{ // trs haw thr tel brw hgw car bnk sof elc {0.1, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // treasury {0, 19, -2, 4, 1, 1, 1, 0.5, 10, 5}, // hardware {0, -2, 28, 1, 2, 1, 1, 0, -2, -1}, // theater {0, 4, 1, 22, 0, 1, 2, 0, 3, 4}, // telecom {0, 1, 2, 0, 4,-1.5, -2, -1, 1, 1}, // brewery {0, 1, 1, 1,-1.5, 3.5, 2, 0.5, 1, 1.5}, // highways {0, 1, 1, 2, -2, 2, 5, 0.5, 1, 2.5}, // cars {0, 0.5, 0, 0, -1, 0.5, 0.5, 1, 0.5, 0.5}, // bank {0, 10, -2, 3, 1, 1, 1, 0.5, 25, 8}, // software {0, 5, -1, 4, 1, 1.5, 2.5, 0.5, 8, 16} // electronics }; private static void PrintProblemStatus(XpressProblem prob) { Console.WriteLine("Problem status:"); Console.WriteLine($"\tSolve status: {prob.SolveStatus}"); Console.WriteLine($"\tSol status: {prob.SolStatus}"); } public static void Main(string[] args) { using (XpressProblem prob = new XpressProblem()) { // Output all messages. prob.callbacks.AddMessageCallback(DefaultMessageListener.Console); /****VARIABLES****/ Variable[] frac = prob.AddVariables(NSHARES) /* Fraction of capital used per share */ .WithName(i => $"frac_{i}") /* Upper bounds on the investment per share */ .WithUB(0.3) .ToArray(); /**** CONSTRAINTS ****/ /* Limit variance */ QuadExpression variance = QuadExpression.Create(); foreach (int s in Range(0, NSHARES)) foreach (int t in Range(0, NSHARES)) variance.AddTerm(frac[s], frac[t], VAR[s, t]); prob.AddConstraint(variance.Leq(MAXVAR).SetName("Variance")); /* Minimum amount of North-American values */ prob.AddConstraint(Sum(NNA, i => frac[NA[i]]).Geq(0.5).SetName("NA")); /* Spend all the capital */ prob.AddConstraint(Sum(frac).Eq(1.0).SetName("Cap")); /* Objective: maximize total return */ prob.SetObjective( ScalarProduct(frac, RET), Optimizer.ObjSense.Maximize ); /* Solve */ prob.Optimize(); /* Solution printing */ PrintProblemStatus(prob); double[] sol = prob.GetSolution(); Console.WriteLine($"With of max variance of {MAXVAR} total return: {prob.ObjVal}"); foreach (Variable f in frac) { Console.WriteLine(String.Format("{0} : {1:f2}%", f.GetName(), 100.0 * f.GetValue(sol))); } } } } } |
FolioQC.csproj |
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net5.0</TargetFramework> <IsPackable>false</IsPackable> <XpressExampleFiles Condition="'$(XpressExampleFiles)'==''">../../../data</XpressExampleFiles> </PropertyGroup> <ItemGroup> <PackageReference Include="FICO.Xpress.XPRSdn" Version="41.1.1" /> <!-- Version 41.1.1 or later --> </ItemGroup> </Project> |
FolioQP.cs |
// (c) 2023-2024 Fair Isaac Corporation using System; using Optimizer.Objects; using Optimizer; using static Optimizer.Objects.Utils; using static System.Linq.Enumerable; using QuadExpression = Optimizer.Objects.QuadExpression; namespace XpressExamples { /// <summary>Modeling a small QP problem to perform portfolio optimization.</summary> /// <remarks> /// <list type='number'> /// <item><description> /// QP: minimize variance /// </description></item> /// <item><description> /// MIQP: limited number of assets /// </description></item> /// </remarks> class FolioQP { /* Target yield */ private static readonly int TARGET = 9; /* Max. number of different assets */ private static readonly int MAXNUM = 4; /* Number of shares */ private static readonly int NSHARES = 10; /* Number of high-risk shares */ private static readonly int NRISK = 5; /* Number of North-American shares */ private static readonly int NNA = 4; /* Estimated return in investment */ private static readonly double[] RET = new double[] { 5, 17, 26, 12, 8, 9, 7, 6, 31, 21 }; /* High-risk values among shares */ private static readonly int[] RISK = new int[] { 1, 2, 3, 8, 9 }; /* Shares issued in N.-America */ private static readonly int[] NA = new int[] { 0, 1, 2, 3 }; /* Variance/covariance matrix of estimated returns */ private static readonly double[,] VAR = new double[,]{ // trs haw thr tel brw hgw car bnk sof elc {0.1, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // treasury {0, 19, -2, 4, 1, 1, 1, 0.5, 10, 5}, // hardware {0, -2, 28, 1, 2, 1, 1, 0, -2, -1}, // theater {0, 4, 1, 22, 0, 1, 2, 0, 3, 4}, // telecom {0, 1, 2, 0, 4,-1.5, -2, -1, 1, 1}, // brewery {0, 1, 1, 1,-1.5, 3.5, 2, 0.5, 1, 1.5}, // highways {0, 1, 1, 2, -2, 2, 5, 0.5, 1, 2.5}, // cars {0, 0.5, 0, 0, -1, 0.5, 0.5, 1, 0.5, 0.5}, // bank {0, 10, -2, 3, 1, 1, 1, 0.5, 25, 8}, // software {0, 5, -1, 4, 1, 1.5, 2.5, 0.5, 8, 16} // electronics }; private static void PrintProblemStatus(XpressProblem prob) { Console.WriteLine("Problem status:"); Console.WriteLine($"\tSolve status: {prob.SolveStatus}"); Console.WriteLine($"\tSol status: {prob.SolStatus}"); } public static void Main(string[] args) { using (XpressProblem prob = new XpressProblem()) { // Output all messages. prob.callbacks.AddMessageCallback(DefaultMessageListener.Console); /****VARIABLES****/ Variable[] frac = prob.AddVariables(NSHARES) /* Fraction of capital used per share */ .WithName(i => $"frac_{i}") /* Upper bounds on the investment per share */ .WithUB(0.3) .ToArray(); /**** CONSTRAINTS ****/ /* Minimum amount of North-American values */ prob.AddConstraint(Sum(NNA, i => frac[NA[i]]).Geq(0.5).SetName("NA")); /* Spend all the capital */ prob.AddConstraint(Sum(frac).Eq(1.0).SetName("Cap")); /* Target yield */ prob.AddConstraint(ScalarProduct(frac, RET).Geq(TARGET).SetName("TargetYield")); /* Objective: minimize mean variance */ QuadExpression variance = QuadExpression.Create(); foreach (int s in Range(0, NSHARES)) foreach (int t in Range(0, NSHARES)) variance.AddTerm(frac[s], frac[t], VAR[s, t]); prob.SetObjective( variance, Optimizer.ObjSense.Minimize ); /* Solve */ prob.Optimize(); /* Solution printing */ PrintProblemStatus(prob); double[] sol = prob.GetSolution(); Console.WriteLine($"With a target of {TARGET} minimum variance is {prob.ObjVal}"); foreach (Variable f in frac) { Console.WriteLine(String.Format("{0} : {1:f2}%", f.GetName(), 100.0 * f.GetValue(sol))); } Variable[] buy = prob.AddVariables(NSHARES) /* Fraction of capital used per share */ .WithName(i => $"buy_{i}") .WithType(ColumnType.Binary) .ToArray(); /* Limit the total number of assets */ prob.AddConstraint(Sum(buy).Leq(MAXNUM).SetName("MaxAssets")); /* Linking the variables */ /* frac .<= buy */ prob.AddConstraints(NSHARES, i => frac[i].Leq(buy[i]).SetName($"link_{i}") ); /* Solve */ prob.Optimize(); /* Solution printing */ Console.WriteLine($"With a target of {TARGET} and at most {MAXNUM} assets, minimum variance is {prob.ObjVal}"); double[] solmip = prob.GetSolution(); foreach (String v in frac.Zip(buy, (f, b) => String.Format("{0} : {1:f2}% ({2:f1})", f.GetName(), 100.0 * f.GetValue(solmip), b.GetValue(solmip)))) { Console.WriteLine(v); } } } } } |
FolioQP.csproj |
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net5.0</TargetFramework> <IsPackable>false</IsPackable> <XpressExampleFiles Condition="'$(XpressExampleFiles)'==''">../../../data</XpressExampleFiles> </PropertyGroup> <ItemGroup> <PackageReference Include="FICO.Xpress.XPRSdn" Version="41.1.1" /> <!-- Version 41.1.1 or later --> </ItemGroup> </Project> |
© 2001-2024 Fair Isaac Corporation. All rights reserved. This documentation is the property of Fair Isaac Corporation (“FICO”). Receipt or possession of this documentation does not convey rights to disclose, reproduce, make derivative works, use, or allow others to use it except solely for internal evaluation purposes to determine whether to purchase a license to the software described in this documentation, or as otherwise set forth in a written software license agreement between you and FICO (or a FICO affiliate). Use of this documentation and the software described in it must conform strictly to the foregoing permitted uses, and no other use is permitted.