FICO
FICO Xpress Optimization Examples Repository
FICO Optimization Community FICO Xpress Optimization Home
Back to examples browserPrevious exampleNext example

Folio - Examples from 'Getting Started'

Description
Different versions of a portfolio optimization problem.

Basic modelling and solving tasks:
  • modeling and solving a small LP problem (FolioInit)
  • modeling and solving a small MIP problem with binary variables (FolioMip1)
  • modeling and solving a small MIP problem with semi-continuous variables (FolioMip2)
  • modeling and solving QP, MIQP, QCQP problems (FolioQP, FolioQC)
  • heuristic solution of a MIP problem (FolioHeuristic)
Advanced modeling and solving tasks:
  • enlarged version of the basic MIP model (Folio, to be used with data set folio10.cdat)
  • defining an integer solution callback (FolioCB, to be used with data set folio10.cdat)
  • retrieving IIS (FolioIIS, FolioMipIIS, to be used with data set folio10.cdat)

folio_dnet.zip[download all files]

Source Files





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") : "../../data") + "/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);
                }

                // 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);
            }
        }
    }
}

Back to examples browserPrevious exampleNext example