This is a static, read-only snapshot — sliders and other controls will not respond.
Back to notebook list
·
Run it yourself
%23%20%2F%2F%2F%20script%0A%23%20%5Btool.marimo.opengraph%5D%0A%23%20title%20%3D%20%22Markowitz%20Portfolio%20Optimization%22%0A%23%20description%20%3D%20%22Multi-Objective%20Quadratic%20Programming%22%0A%23%20image%20%3D%20%22__marimo__%2Fthumbnail-markowitz.svg%22%0A%23%20%2F%2F%2F%0A%0Aimport%20marimo%0A%0A__generated_with%20%3D%20%220.24.0%22%0Aapp%20%3D%20marimo.App()%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20import%20marimo%20as%20mo%0A%20%20%20%20import%20xpress%20as%20xp%0A%20%20%20%20import%20numpy%20as%20np%0A%20%20%20%20import%20matplotlib.pyplot%20as%20plt%0A%0A%20%20%20%20return%20mo%2C%20np%2C%20plt%2C%20xp%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%20**Markowitz%20portfolio%20multi-objective%20optimization**%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Markowitz%20portfolio%20optimization.%20A%20multi-objective%20quadratic%20programming%20example.%0A%0A%20%20%20%20In%20Markowitz%20portfolio%20optimization%20there%20are%20two%20objectives%3A%20to%20maximize%20reward%20while%20minimizing%20risk%20(i.e.%20variance).%20This%20example%20plots%20several%20points%20on%20the%20optimal%20frontier%20using%20a%20blended%20multi-objective%20approach%2C%20and%20shows%20that%20a%20point%20computed%20using%20a%20lexicographic%20approach%20also%20lies%20on%20this%20frontier.%0A%0A%20%20%20%20%26copy%3B%20Copyright%202025-2026%20Fair%20Isaac%20Corporation.%20The%20use%20of%20this%20example%20is%20subject%20to%20%5Blegal%20and%20license%20requirements%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%23legal-and-license-requirements).%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20%23%20Install%20the%20necessary%20packages%0A%20%20%20%20%23%20'%25pip%20install%20-q%20xpress%20matplotlib'%20command%20supported%20automatically%20in%20marimo%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Problem%20description%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Markowitz%20portfolio%20optimization%20focuses%20on%20making%20a%20selection%20on%20the%20fraction%20of%20a%20budget%20to%20allocate%20to%20%24S%24%20possible%20stocks%20to%20form%20a%20portfolio%20with%20two%20key%20objectives%3A%0A%0A%20%20%20%20*%20Maximize%20expected%20returns%3A%20%24%5Cmax%20%5Csum_%7Bi%20%5Cin%20%5Cmathcal%7BS%7D%7D%20RET_i%20%5Ccdot%20frac_i%24%0A%20%20%20%20*%20Minimize%20the%20portfolio%20variance%3A%20%24%5Cmin%20%5Csum_%7Bi%2Cj%20%5Cin%20%5Cmathcal%7BS%7D%7D%20frac_i%20%5Ccdot%20frac_j%20%5Ccdot%20COV_%7Bi%2Cj%7D%24%0A%0A%20%20%20%20where%3A%0A%0A%20%20%20%20*%20%24frac%24%20is%20the%20vector%20of%20portfolio%20stock%20allocations.%0A%20%20%20%20*%20%24COV%24%20is%20the%20covariance%20matrix%20of%20asset%20returns.%0A%20%20%20%20*%20%24RET%24%20is%20the%20returns%20vector.%0A%0A%20%20%20%20The%20sum%20of%20the%20portfolio%20stock%20allocations%20should%20be%20equal%20to%201%20(fully%20invested%20portfolio)%3A%20%24%5Csum_%7Bi%20%5Cin%20%5Cmathcal%7BS%7D%7D%20frac_i%20%3D%201%24%0A%0A%20%20%20%20In%20this%20example%2C%20we%20work%20with%20%24S%24%20%3D%205%20stocks%2C%20where%20the%20matrix%20of%20co-variance%20between%20each%20pair%20of%20stocks%20is%20given%20by%3A%0A%0A%20%20%20%20%7C%20%20%20%7C%20Stock%201%20%7C%20Stock%202%20%7C%20Stock%203%20%7C%20Stock%204%20%7C%20Stock%205%20%7C%0A%20%20%20%20%7C---%7C---%7C---%7C---%7C---%7C---%7C%0A%20%20%20%20%7C%20**Stock%201**%20%7C%200.32%20%7C%200.70%20%7C%200.19%20%7C%200.52%20%7C%200.16%20%7C%0A%20%20%20%20%7C%20**Stock%202**%20%7C%200.70%20%7C%204.35%20%7C%20-0.48%20%7C%20-0.06%20%7C%20-0.03%20%7C%0A%20%20%20%20%7C%20**Stock%203**%20%7C%200.19%20%7C%20-0.48%20%7C%200.98%20%7C%201.10%20%7C%200.10%20%7C%0A%20%20%20%20%7C%20**Stock%204**%20%7C%200.52%20%7C%20-0.60%20%7C%201.10%20%7C%202.48%20%7C%200.37%20%7C%0A%20%20%20%20%7C%20**Stock%205**%20%7C%200.16%20%7C%20-0.30%20%7C%200.10%20%7C%200.37%20%7C%200.31%20%7C%0A%0A%20%20%20%20The%20returns%20of%20each%20stock%20are%20given%20as%3A%0A%0A%20%20%20%20%7C%20%20%20%7C%20Returns%20%7C%0A%20%20%20%20%7C---%7C---%7C%0A%20%20%20%20%7C%20**Stock%201**%20%7C%200.31%20%7C%0A%20%20%20%20%7C%20**Stock%202**%20%7C%200.87%20%7C%0A%20%20%20%20%7C%20**Stock%203**%20%7C%200.31%20%7C%0A%20%20%20%20%7C%20**Stock%204**%20%7C%200.66%20%7C%0A%20%20%20%20%7C%20**Stock%205**%20%7C%200.24%20%7C%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Model%20parameters%0A%0A%20%20%20%20**You%20can%20adjust%20the%20parameters%20below%20to%20change%20the%20number%20of%20frontier%20points%20computed%2C%20select%20a%20specific%20blended%20weight%20on%20the%20'Return'%20objective%20to%20mark%20on%20the%20frontier%2C%20and%20control%20the%20lexicographic%20trade-off%20tolerance.**%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20num_points_slider%20%3D%20mo.ui.slider(5%2C%20100%2C%20value%3D50%2C%20step%3D5%2C%20label%3D%22Number%20of%20efficient-frontier%20points%22%2C%20show_value%3DTrue)%0A%20%20%20%20selected_weight_slider%20%3D%20mo.ui.slider(0.05%2C%200.98%2C%20value%3D0.5%2C%20step%3D0.05%2C%20label%3D%22Blended%3A%20weight%20on%20'Return'%20objective%22%2C%20show_value%3DTrue)%0A%20%20%20%20reltol_slider%20%3D%20mo.ui.slider(0.0%2C%200.3%2C%20value%3D0.1%2C%20step%3D0.01%2C%20label%3D%22Lexicographic%3A%20max.%20relative%20loss%20in%20return%22%2C%20show_value%3DTrue)%0A%20%20%20%20mo.vstack(%5Bnum_points_slider%2C%20selected_weight_slider%2C%20reltol_slider%5D)%0A%20%20%20%20return%20num_points_slider%2C%20reltol_slider%2C%20selected_weight_slider%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Data%20preparation%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20The%20necessary%20packages%20are%20imported%2C%20and%20returns%20and%20covariance%20data%20is%20created%20as%20NumPy%20arrays%20to%20allow%20the%20use%20of%20the%20%5Bxpress.Dot()%5D(https%3A%2F%2Fwww.fico.com%2Ffico-xpress-optimization%2Fdocs%2Flatest%2Fsolver%2Foptimizer%2Fpython%2FHTML%2Fxpress.Dot.html)%20operator.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo%2C%20np)%3A%0A%20%20%20%20%23%20The%20historical%20mean%20return%20on%20investment%20for%20five%20stocks%0A%20%20%20%20RET%20%3D%20np.array(%5B0.31%2C%200.87%2C%200.31%2C%200.66%2C%200.24%5D)%0A%0A%20%20%20%20%23%20The%20historical%20covariances%20of%20the%20five%20stocks%0A%20%20%20%20COV%20%3D%20np.array(%5B%0A%20%20%20%20%20%20%20%20%5B0.32%2C%20%200.70%2C%20%200.19%2C%20%200.52%2C%20%200.16%5D%2C%0A%20%20%20%20%20%20%20%20%5B0.70%2C%20%204.35%2C%20-0.48%2C%20-0.06%2C%20-0.03%5D%2C%0A%20%20%20%20%20%20%20%20%5B0.19%2C%20-0.48%2C%20%200.98%2C%20%201.10%2C%20%200.10%5D%2C%0A%20%20%20%20%20%20%20%20%5B0.52%2C%20-0.6%2C%20%20%201.10%2C%20%202.48%2C%20%200.37%5D%2C%0A%20%20%20%20%20%20%20%20%5B0.16%2C%20-0.3%2C%20%20%200.10%2C%20%200.37%2C%20%200.31%5D%0A%20%20%20%20%5D)%0A%20%20%20%20mo.show_code()%0A%20%20%20%20return%20COV%2C%20RET%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Model%20implementation%20and%20visualization%20of%20efficient%20frontier%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Non-negative%20variables%20represent%20percentage%20of%20capital%20to%20invest%20in%20each%20stock.%20In%20order%20to%20use%20Xpress'%20built%20in%20multi-objective%20handling%20functionality%2C%20all%20objectives%20must%20be%20linear%2C%20so%20we%20define%20a%20free%20variable%20*variance*%20to%20serve%20as%20transfer%20variable.%0A%0A%20%20%20%20A%20list%20of%20constraints%20is%20created%20and%20passed%20as%20an%20argument%20to%20%5Bproblem.addConstraint()%5D(https%3A%2F%2Fwww.fico.com%2Ffico-xpress-optimization%2Fdocs%2Flatest%2Fsolver%2Foptimizer%2Fpython%2FHTML%2Fproblem.addConstraint.html).%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(COV%2C%20RET%2C%20mo%2C%20xp)%3A%0A%20%20%20%20p%20%3D%20xp.problem()%0A%0A%20%20%20%20%23%20Non-negative%20variables%20represent%20percentage%20of%20capital%20to%20invest%20in%20each%20stock%0A%20%20%20%20frac%20%3D%20p.addVariables(len(RET))%0A%0A%20%20%20%20%23%20All%20objectives%20must%20be%20linear%2C%20so%20we%20define%20a%20free%20variable%20for%20the%20variance%0A%20%20%20%20variance%20%3D%20p.addVariable(lb%3D-xp.infinity)%0A%0A%20%20%20%20ctrs%20%3D%20%5B%0A%20%20%20%20%20%20%20%20xp.Sum(frac)%20%3D%3D%201%2C%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20Must%20invest%20100%25%20of%20capital%0A%20%20%20%20%20%20%20%20xp.Dot(frac%2C%20COV%2C%20frac)%20-%20variance%20%3C%3D%200%20%20%20%20%23%20Set%20up%20transfer%20variable%20for%20variance%0A%20%20%20%20%5D%0A%0A%20%20%20%20p.addConstraint(ctrs)%0A%20%20%20%20p.controls.outputlog%20%3D%200%20%20%23%20Turn%20off%20output%20log%20for%20cleaner%20output%0A%20%20%20%20mo.show_code()%0A%20%20%20%20return%20frac%2C%20p%2C%20variance%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Next%20we%20define%20the%20two%20objectives.%20First%2C%20we%20call%20%5Bproblem.setObjective()%5D(https%3A%2F%2Fwww.fico.com%2Ffico-xpress-optimization%2Fdocs%2Flatest%2Fsolver%2Foptimizer%2Fpython%2FHTML%2Fproblem.setObjective.html)%20to%20define%20the%20first%20objective%2C%20and%20the%20second%20objective%20is%20added%20using%20the%20%5Bproblem.addObjective()%5D(https%3A%2F%2Fwww.fico.com%2Ffico-xpress-optimization%2Fdocs%2Flatest%2Fsolver%2Foptimizer%2Fpython%2FHTML%2Fproblem.addObjective.html)%20method%2C%20which%20is%20designed%20for%20the%20formulation%20of%20multi-objective%20optimization%20problems%20by%20adding%20a%20new%20objective%20to%20an%20optimization%20problem.%0A%0A%20%20%20%20Alternatively%2C%20the%20%5Bproblem.setObjective()%5D(https%3A%2F%2Fwww.fico.com%2Ffico-xpress-optimization%2Fdocs%2Flatest%2Fsolver%2Foptimizer%2Fpython%2FHTML%2Fproblem.setObjective.html)%20method%20can%20be%20used%20to%20add%20a%20new%20objective%20to%20a%20problem%2C%20as%20long%20as%20an%20*objidx*%20argument%20is%20defined%20for%20each%20objective%2C%20as%20consecutive%20integers%20starting%20from%20zero.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(RET%2C%20frac%2C%20mo%2C%20p%2C%20variance%2C%20xp)%3A%0A%20%20%20%20p.setObjective(xp.Dot(frac%2C%20RET))%20%20%20%20%20%23%20Maximize%20mean%20return%0A%20%20%20%20p.addObjective(variance)%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20Minimize%20variance%0A%0A%20%20%20%20%23%20or%20alternatively%0A%20%20%20%20%23%20p.setObjective(xp.Dot(frac%2C%20RET)%2C%20objidx%3D0)%0A%20%20%20%20%23%20p.setObjective(variance%2C%20objidx%3D1)%0A%20%20%20%20mo.show_code()%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Vary%20the%20objective%20weights%20to%20explore%20the%20optimal%20frontier%2C%20with%20the%20first%20objective%20having%20a%20weight%20ranging%20from%200.05%20up%20to%200.98%20(using%20the%20**Number%20of%20efficient-frontier%20points**%20control%20above%20to%20set%20the%20number%20of%20weights%20tried)%2C%20with%20the%20second%20objective%20(weight)%20being%20the%20complement%20for%20each%20instance.%20Weight%201.0%20is%20deliberately%20excluded%3A%20with%20a%20weight%20of%20exactly%200%20on%20the%20variance%20objective%2C%20its%20value%20becomes%20arbitrary%20among%20the%20(many)%20equally-optimal-return%20solutions%2C%20producing%20a%20spurious%20jump%20at%20the%20very%20end%20of%20the%20curve.%20Stopping%20at%200.98%20keeps%20the%20frontier%20smooth%20while%20still%20reaching%20essentially%20the%20same%20maximum%20return%20the%20lexicographic%20approach%20below%20can%20achieve.%0A%0A%20%20%20%20A%20loop%20allows%20iterating%20through%20each%20weight%20case%2C%20optimizing%20and%20saving%20the%20two%20objective%20values%20in%20each%20instance%20(coordinates).%0A%0A%20%20%20%20When%20using%20either%20%5Bproblem.setObjective()%5D(https%3A%2F%2Fwww.fico.com%2Ffico-xpress-optimization%2Fdocs%2Flatest%2Fsolver%2Foptimizer%2Fpython%2FHTML%2Fproblem.setObjective.html)%20or%20%5Bproblem.addObjective()%5D(https%3A%2F%2Fwww.fico.com%2Ffico-xpress-optimization%2Fdocs%2Flatest%2Fsolver%2Foptimizer%2Fpython%2FHTML%2Fproblem.addObjective.html)%2C%20if%20**objectives%20have%20equal%20priority%2C%20a%20Blended%20(or%20Archimedean)%20approach%20is%20applied**%2C%20setting%20as%20objective%20function%20of%20the%20problem%20the%20linear%20combination%20of%20the%20added%20objectives%20and%20their%20weights.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(RET%2C%20frac%2C%20mo%2C%20np%2C%20num_points_slider%2C%20p%2C%20variance%2C%20xp)%3A%0A%20%20%20%20%23%20Vary%20the%20objective%20weights%20to%20explore%20the%20optimal%20frontier%0A%20%20%20%20weights%20%3D%20np.linspace(0.05%2C%200.98%2C%20num_points_slider.value)%0A%20%20%20%20means%20%3D%20%5B%5D%0A%20%20%20%20variances%20%3D%20%5B%5D%0A%0A%20%20%20%20for%20w%20in%20weights%3A%0A%20%20%20%20%20%20%20%20%23%20priority%3D0%20on%20both%20objectives%20keeps%20this%20a%20blended%20(not%20lexicographic)%20solve%2C%0A%20%20%20%20%20%20%20%20%23%20regardless%20of%20what%20priority%20a%20previous%20solve%20on%20this%20same%20problem%20may%20have%20set.%0A%20%20%20%20%20%20%20%20p.setObjective(objidx%3D0%2C%20weight%3Dw%2C%20priority%3D0%2C%20sense%3Dxp.ObjSense.MAXIMIZE)%20%20%23%20First%20objective%20defines%20the%20sense%20of%20the%20problem%0A%20%20%20%20%20%20%20%20p.setObjective(objidx%3D1%2C%20weight%3Dw%20-%201%2C%20priority%3D0)%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20Reverse%20the%20sense%20by%20assigning%20a%20negative%20weight%20because%20we%20minimize%20variance%0A%20%20%20%20%20%20%20%20p.optimize()%0A%20%20%20%20%20%20%20%20means.append(xp.Dot(p.getSolution(frac)%2C%20RET).item())%0A%20%20%20%20%20%20%20%20variances.append(p.getSolution(variance))%0A%20%20%20%20mo.show_code()%0A%20%20%20%20return%20means%2C%20variances%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20The%20**Blended%3A%20weight%20on%20'Return'%20objective**%20control%20above%20picks%20a%20single%2C%20arbitrary%20weight%20for%20the%20'Return'%20objective%20(not%20necessarily%20one%20of%20the%20points%20from%20the%20sweep%20above)%20and%20re-solves%20the%20model%20once%20for%20that%20value%2C%20so%20the%20resulting%20point%20can%20be%20marked%20on%20the%20frontier%20below.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(RET%2C%20frac%2C%20mo%2C%20p%2C%20selected_weight_slider%2C%20variance%2C%20xp)%3A%0A%20%20%20%20selected_weight%20%3D%20selected_weight_slider.value%20%20%23%20Weight%20on%20the%20'Return'%20objective%0A%20%20%20%20%23%20priority%3D0%20on%20both%20objectives%20keeps%20this%20a%20blended%20(not%20lexicographic)%20solve%2C%0A%20%20%20%20%23%20regardless%20of%20what%20priority%20a%20previous%20solve%20on%20this%20same%20problem%20may%20have%20set.%0A%20%20%20%20p.setObjective(objidx%3D0%2C%20weight%3Dselected_weight%2C%20priority%3D0%2C%20sense%3Dxp.ObjSense.MAXIMIZE)%0A%20%20%20%20p.setObjective(objidx%3D1%2C%20weight%3Dselected_weight%20-%201%2C%20priority%3D0)%0A%20%20%20%20p.optimize()%0A%20%20%20%20selected_mean%20%3D%20xp.Dot(p.getSolution(frac)%2C%20RET).item()%0A%20%20%20%20selected_variance%20%3D%20p.getSolution(variance)%0A%20%20%20%20mo.show_code()%0A%20%20%20%20return%20selected_mean%2C%20selected_variance%2C%20selected_weight%0A%0A%0A%40app.cell%0Adef%20_(mo%2C%20selected_mean%2C%20selected_variance%2C%20selected_weight)%3A%0A%20%20%20%20mo.md(f%22%22%22%0A%20%20%20%20**Selected%20weight%20on%20'Return'%3A%20%7Bselected_weight%3A.2f%7D**%20%26nbsp%3B%26nbsp%3B%20**Expected%20return%3A**%20%7Bselected_mean%3A.4f%7D%20%26nbsp%3B%26nbsp%3B%20**Variance%3A**%20%7Bselected_variance%3A.4f%7D%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Draw%20the%20efficient%20frontier%20with%20*matplotlib*%2C%20marking%20the%20point%20for%20the%20selected%20'Return'%20weight%20in%20red.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(means%2C%20plt%2C%20selected_mean%2C%20selected_variance%2C%20variances)%3A%0A%20%20%20%20fig_frontier%2C%20ax_frontier%20%3D%20plt.subplots()%0A%20%20%20%20ax_frontier.plot(means%2C%20variances%2C%20label%3D%22Efficient%20frontier%22)%0A%20%20%20%20ax_frontier.scatter(%5Bselected_mean%5D%2C%20%5Bselected_variance%5D%2C%20c%3D%22tab%3Ared%22%2C%20zorder%3D3%2C%20label%3D%22Selected%20weight%20on%20'Return'%22)%0A%20%20%20%20ax_frontier.set_title(%22Return%20on%20investment%20vs%20variance%22)%0A%20%20%20%20ax_frontier.set_xlabel(%22Expected%20return%22)%0A%20%20%20%20ax_frontier.set_ylabel(%22Variance%22)%0A%20%20%20%20ax_frontier.legend()%0A%20%20%20%20fig_frontier%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Applying%20a%20lexicographic%20approach%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Now%20we%20will%20maximize%20profit%20alone%2C%20and%20then%20minimize%20variance%20while%20not%20sacrificing%20more%20than%20the%20**Lexicographic%3A%20max.%20relative%20loss%20in%20return**%20control's%20percentage%20of%20the%20maximum%20profit%20possible.%0A%0A%20%20%20%20When%20**objectives%20have%20a%20different%20priority%20but%20the%20same%20weight%2C%20a%20Lexicographic%20approach%20is%20applied**.%20Xpress%20will%20solve%20the%20problem%20once%20for%20each%20distinct%20objective%20priority%20that%20is%20defined.%20All%20objectives%20from%20previous%20iterations%20are%20fixed%20to%20their%20optimal%20values%20within%20the%20tolerances%3A%0A%0A%20%20%20%20*%20For%20minimization%20objectives%3A%20%60objective%20%3C%3D%20optimal_value%20*%20(1%20%2B%20reltol)%20%2B%20abstol%60%0A%20%20%20%20*%20For%20maximization%20objectives%3A%20%60objective%20%3C%3D%20optimal_value%20*%20(1%20-%20reltol)%20-%20abstol%60%0A%0A%20%20%20%20with%20the%20parameter%20%60reltol%60%20being%20the%20relative%20tolerance%20and%20%60abstol%60%20the%20absolute%20tolerance%20for%20the%20objective%20in%20subsequent%20runs.%0A%0A%20%20%20%20Further%20calls%20to%20%5Bproblem.setObjective()%5D(https%3A%2F%2Fwww.fico.com%2Ffico-xpress-optimization%2Fdocs%2Flatest%2Fsolver%2Foptimizer%2Fpython%2FHTML%2Fproblem.setObjective.html)%20allow%20users%20to%20configure%20each%20objective%20via%20the%20%60objidx%60%20argument%2C%20which%20receives%20the%20%60id%60%20of%20each%20objective%2C%20a%20counter%20starting%20with%200%20in%20the%20order%20objectives%20are%20created.%0A%0A%20%20%20%20By%20running%20the%20code%20cell%20below%2C%20we%20can%20observe%20that%20the%20final%20solution%20from%20the%20lexicographic%20method%20falls%20within%20the%20efficient%20frontier%20previously%20generated.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(RET%2C%20frac%2C%20mo%2C%20p%2C%20reltol_slider%2C%20variance%2C%20xp)%3A%0A%20%20%20%20%23%20Now%20we%20will%20maximize%20profit%20alone%2C%20and%20then%20minimize%20variance%20while%20not%0A%20%20%20%20%23%20sacrificing%20more%20than%20reltol_slider.value%20of%20the%20maximum%20profit%0A%20%20%20%20p.setObjective(objidx%3D0%2C%20priority%3D1%2C%20weight%3D1%2C%20reltol%3Dreltol_slider.value%2C%20sense%3Dxp.ObjSense.MAXIMIZE)%0A%20%20%20%20p.setObjective(objidx%3D1%2C%20priority%3D0%2C%20weight%3D-1)%0A%20%20%20%20p.optimize()%0A%20%20%20%20lex_mean%20%3D%20xp.Dot(p.getSolution(frac)%2C%20RET).item()%0A%20%20%20%20lex_variance%20%3D%20p.getSolution(variance)%0A%20%20%20%20mo.show_code()%0A%20%20%20%20return%20lex_mean%2C%20lex_variance%0A%0A%0A%40app.cell%0Adef%20_(lex_mean%2C%20lex_variance%2C%20mo)%3A%0A%20%20%20%20mo.md(f%22%22%22%0A%20%20%20%20**Expected%20return%3A**%20%7Blex_mean%3A.4f%7D%20%26nbsp%3B%26nbsp%3B%20**Variance%3A**%20%7Blex_variance%3A.4f%7D%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Plot%20the%20lexicographic%20solution%20(red)%20against%20the%20efficient%20frontier%20(blue)%20to%20confirm%20it%20lies%20on%20the%20frontier.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(lex_mean%2C%20lex_variance%2C%20means%2C%20plt%2C%20variances)%3A%0A%20%20%20%20fig_lex%2C%20ax_lex%20%3D%20plt.subplots()%0A%20%20%20%20ax_lex.plot(means%2C%20variances)%0A%20%20%20%20ax_lex.scatter(%5Blex_mean%5D%2C%20%5Blex_variance%5D%2C%20c%3D%22tab%3Ared%22%2C%20zorder%3D3%2C%20label%3D%22Lexicographic%20solution%22)%0A%20%20%20%20ax_lex.set_title(%22Return%20on%20investment%20vs%20variance%22)%0A%20%20%20%20ax_lex.set_xlabel(%22Expected%20return%22)%0A%20%20%20%20ax_lex.set_ylabel(%22Variance%22)%0A%20%20%20%20ax_lex.legend()%0A%20%20%20%20fig_lex%0A%20%20%20%20return%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20app.run()%0A
b9bd21dbab622c64f5f7364170df6cea