 FICO Xpress Optimization Examples Repository
 FICO Optimization Community FICO Xpress Optimization Home   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*************************

!***************************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

```   