Initializing help system before first use

Robust portfolio optimization


Type: Portfolio optimization
Rating: 3 (intermediate)
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.
File(s): 021folio_robust.mos


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