// (c) 2023-2025 Fair Isaac Corporation
using System;
using System.Linq;
using System.Collections.Generic;
using System.Globalization;
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, new CultureInfo("en-US")));
}
else if (token.Equals("LOC:"))
LOC = ReadBoolTable(tokens, REGIONS.Length, SHARES.Length);
else if (token.Equals("SEC:"))
SEC = ReadBoolTable(tokens, TYPES.Length, SHARES.Length);
}
}
}
}