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

Robust portfolio optimization

Description
Robust single period portfolio allocation with uncertain variability of share prices formulated as 'ellipsoid' uncertainty. The example also shows how to quantify the robustness of a solution using Monte-Carlo simulation.

Further explanation of this example: Whitepaper 'Robust Optimization with Xpress', Section 5 Robust portfolio optimization


Source Files





021folio_robust.mos

  (!******************************************************
   Mosel Example Problems
   ======================

   file 021folio_robust.mos
   ````````````````````````
   Single period portfolio allocation problem.
   The present model produces a selection of
   assets that is robust against realization of
   worst case with a high probability.

   Features:
     - Using the 'ellipsoid' uncertainty set
     - Retrieving the worst value of an uncertain variable

   What to look at:
     - Selection of highest worst case value asset,
       but low expected value for the problem
       with highest protection level
     - Selection of highest exepected value asset, 
       but low worst case value for the problem
       with high protection level
     - Selection of a diversied set of assets for
       various value of the protection level.
     - Using worst value of an uncertain variable as
       an indicator for reoptimizing the portfolio
    
   Model is inspired by work from the following book:
     A. Ben-Tal, L. El Ghaoui, and A. Nemirovski. Robust Optimization. 
     Princeton Series in Applied Mathematics, 2009.

   (c) 2014 Fair Isaac Corporation
       author: S. Lannez, Mar. 2014, rev. Dec. 2014
*******************************************************!)

model "Robust portfolio optimization"
  uses "mmrobust", "random"                       
  uses "mmsystem"

  parameters
    ZEROTOL=1e-6
    SEED=12345
    NMC=500000
    N=1.5                              ! Worst case metric
  end-parameters

  declarations
    Problems: set of mpproblem
    ProtectLevel: set of real

    mp_problemA: mpproblem             ! Worst case optimization
    mp_problemB: array(ProtectLevel) of mpproblem  ! Robust optimization
    NSHARES = 25                       ! Max number of shares
    Shares = 1..NSHARES                ! Set of shares

    PRICE: array(Shares) of real       ! Expected return on investment
    VAR: array(Shares) of real         ! Uncertainty measure (deviation) per SHARE

    expReturn: mpvar                   ! Expected portfolio value
    wstReturn: mpvar                   ! Worst case portfolio value
    frac: array(Shares) of mpvar       ! Fraction of capital used per share
    frac_sol: array(Shares,Problems) of real      
    obj: mpvar

    e: array(Shares) of uncertain      ! Deviation of share values
    PRICEthr: array(Problems,Shares) of real    ! The price threshold
   
  end-declarations
 
!***************************Configuration*************************
 setparam("XPRS_LOADNAMES",true)

!***************************Subroutines***************************
 !**** Create the nominal model ****
  procedure create_nominalmodel
    ! Nominal model
    expReturn = (sum(s in Shares) PRICE(s)*frac(s)) 
    wstReturn = sum(s in Shares) ( PRICE(s) - N*VAR(s) )*frac(s)

    ! Spend all the budget
    sum(s in Shares) frac(s) = 1
  end-procedure
 
 !**** Optimize for the worst case realization ****
  procedure solve_det
    obj = wstReturn
    maximize(obj)
  end-procedure

 !**** Optimize for a given protection level ****
  procedure solve_rob(k: real)
    ! The value variation domain

    assert(k>=0  and k<=1)
    sum(s in Shares) (e(s) / (N*VAR(s)))^2 <= (k)^2

    ! The robust constraint
    obj <= sum(s in Shares) (PRICE(s) + e(s)) * frac(s)

    maximize(obj)
  end-procedure

!***************************Main model***************************
  !**** Input data generation ****
  setmtrandseed(12345)
  cnt := 0
  forall(s in Shares, cnt as counter) do
    PRICE(s) := round((100*(NSHARES-cnt+1)/NSHARES))
    c := (1+sqrt((NSHARES-s)/NSHARES))/3
    VAR(s) := round(PRICE(s)*c*100)/100
  end-do

  writeln("\n *** Input Data *** ")
  writeln
  writeln("Shares | Mean | S.Dev. | Worst value")
  forall(s in Shares)
    writeln(strfmt(s,6), " |", strfmt(PRICE(s),5), " |", strfmt(VAR(s),7,2), 
      " |", strfmt(PRICE(s)-N*VAR(s),6,3))
  writeln
  
  !**** Optimize the worst case ****
  with mp_problemA do
    create_nominalmodel
    solve_det
    forall(s in Shares) frac_sol(s,mp_problemA) := frac(s).sol*100
    ! Price set by opponent is worst case price
    forall(s in Shares) PRICEthr(mp_problemA,s) := PRICE(s) - N*VAR(s)
  end-do

  !**** Optimize the 'budgeted' worst case ****
  Ks := [0,       ! No variation on average
         0.01,    ! +/-  1% variation on the list
         0.10,    ! +/- 10% variation on the list
         0.25,    ! +/- 25%
         1]       ! +/-100% variation on the list
  forall(k in Ks) do 
    create(mp_problemB(k))
    with mp_problemB(k) do
      create_nominalmodel
      solve_rob(k)
      forall(s in Shares) frac_sol(s,mp_problemB(k)) := frac(s).sol*100
      forall(s in Shares) PRICEthr(mp_problemB(k),s) := (PRICE(s) + getsol(e(s)))
    end-do
  end-do

  !**** Print results ****
  writeln("\n *** Portfolio allocation results *** ")
  write("\n                                  |  protection level")
  write("\nShares | Wst price | Price |  A   |")
  forall(k in Ks) write(" ", strfmt(k*100,3), "% |" ) ; writeln
  forall(s in Shares) do
    write(strfmt(s,6), " |", strfmt(PRICE(s)-N*VAR(s),10,0), " |",
      strfmt(PRICE(s),6), " | ")
    forall(mp in Problems) do
      if (frac_sol(s,mp)>1) then
        write(strfmt(frac_sol(s,mp),3,0), "% | ")
      else
        write("     | ")
      end-if
    end-do
    writeln
  end-do

  writeln("\n *** Worst case price *** ")
  write("\n                                  |  protection level")
  write("\nShares | Wst price | Price |  A   |")
  forall(k in Ks) write(" ", strfmt(k*100,3), "% |" ) ; writeln
  forall(s in Shares) do
    write(strfmt(s,6), " |", strfmt(PRICE(s)-N*VAR(s),10,0), " |",
      strfmt(PRICE(s),6), " | ")
    forall(mp in Problems) do
      if (frac_sol(s,mp)>1) then
        write(strfmt(PRICEthr(mp,s),3,0), "* | ")
      else
        write(strfmt(PRICEthr(mp,s),4,0), " | ")
        ! write("     | ")
      end-if
    end-do
    writeln
  end-do
  
  !**** Simulate results (Monte-Carlo simulation) ****
  writeln("\nRunning Monte-Carlo simulation...")
  Values := {0,20,40,60,80,100}
  declarations
    cntV: dynamic array(Problems,Values) of real
    s1: real
    s2: real
  end-declarations
  forall(mp in Problems) do
    expRev(mp) := 0.0 ; wstRev(mp) := 0.0 ; expDev(mp) := 0.0
    forall(v in Values) cntV(mp,v) := 0
    c := 0.0 ! Number of draws
    s1 := 0 ; s2 := 0;
    forall(i in 1..NMC, c as counter) do
      totalVal := 0.0 ; totalRisk := 0.0
      forall(s in Shares | frac_sol(s,mp)>0) do
       	! draw a price
        p := normal(PRICE(s),VAR(s))
        ! calculate share revenue
        r := frac_sol(s,mp)*p/100
        ! calculate total revenue
        totalVal += r ! summing up revenue
      end-do
      forall(v in Values) do
        if (totalVal>v) then 
          cntV(mp,v) += 1
        end-if
      end-do
      expRev(mp) += totalVal
      s1 += totalVal
      s2 += totalVal^2
    end-do
    expRev(mp) := expRev(mp) / c
    expDev(mp) := sqrt(NMC*s2 - s1^2)/(NMC)
    forall(v in Values) cntV(mp,v) := cntV(mp,v) / c
  end-do

  !**** Print simulation results ****
  write("\n                                   |  protection level")
  write("\n                            |  A   |")

  forall(k in Ks) write(" ", strfmt(k*100,3), "% |" )
  write("\n            Expected value  |")
  forall(mp in Problems) write(strfmt(round(expRev(mp)),5), " |")
  write("\n        Standard deviation  |")
  forall(mp in Problems) write(strfmt(round(expDev(mp)),5), " |")

  writeln
  forall(v in Values) do 
    write("\n             P (value>"+textfmt(v,3)+"   |")
    forall(mp in Problems) write(strfmt(cntV(mp,v),5,2) , "  |")
  end-do

end-model 

Back to examples browserPrevious exampleNext example