Skip to content

Example 12: Portfolio Optimization Problem

Problem Description

There are n assets Si (i=1,2,,n) available in the market for investment, using funds amounting to M for a one-period investment. The average return rate for purchasing Si during this period is ri, the risk loss rate is qi. The more diversified the investment, the smaller the total risk. The overall risk can be obtained by weighting the investment amount of Si with its investment risk.

When purchasing Si, a transaction fee is charged at rate pi. When the purchase amount does not exceed a given value ui, the transaction fee is calculated based on purchasing ui. Additionally, assume the bank deposit interest rate during the same period is r0, which has neither transaction fees nor risk. (r0=5%)

Siri(%)qi(%)pi(%)ui(yuan)
S1284.08103
S2211.52198
S3235.54.552
S4252.6440

Design an investment portfolio strategy with given funds of 1000000 yuan, selectively purchasing assets or bank deposits, to maximize net return while controlling risk within 2%.

Mathematical Model

Variables

xi: Purchase funds for investment product i.

Intermediate Values

1. Whether Purchased

Assignmenti=If(xi>0),iS

2. Transaction Fee

Premiumi=Max(pixi,uiAssignmenti),iS

3. Return

Yield=iS(rixiPremiumi)

4. Risk

Risk=iSqixiM

Objective Function

1. Maximize Return

maxYield

Constraints

1. Sum of Funds Equals M

s.t.iS(1+pi)xi=M

2. Risk Limit

s.t.iSRiskiRiskMax

Expected Results

Invest 494505 yuan in S4 and 476191 yuan in S2.

Code Implementation

kotlin
import fuookami.ospf.kotlin.utils.math.*
import fuookami.ospf.kotlin.utils.concept.*
import fuookami.ospf.kotlin.utils.functional.*
import fuookami.ospf.kotlin.utils.multi_array.*
import fuookami.ospf.kotlin.core.frontend.variable.*
import fuookami.ospf.kotlin.core.frontend.expression.polynomial.*
import fuookami.ospf.kotlin.core.frontend.expression.symbol.*
import fuookami.ospf.kotlin.core.frontend.inequality.*
import fuookami.ospf.kotlin.core.frontend.model.mechanism.*
import fuookami.ospf.kotlin.core.backend.plugins.scip.*

data class Product(
    val yield: Flt64,
    val risk: Flt64,
    val premium: Flt64,
    val minPremium: Flt64
) : AutoIndexed(Product::class)

val products: List<Product> = ... // Product list
val funds = Flt64(1000000.0)
val maxRisk = Flt64(0.02)

// Create model instance
val metaModel = LinearMetaModel("demo12")

// Define variables
val x = UIntVariable1("x", Shape1(products.size))
metaModel.add(x)

// Define intermediate values
val assignment = LinearIntermediateSymbols1(
    "assignment",
    Shape1(products.size)
) { i, _ ->
    BinaryzationFunction(
        x = LinearPolynomial(x[i]),
        name = "assignment_$i"
    )
}
metaModel.add(assignment)

val premium = LinearIntermediateSymbols1(
    "premium",
    Shape1(products.size)
) { i, _ ->
    val product = products[i]
    MaxFunction(
        listOf(
            LinearPolynomial(product.premium * x[i]),
            LinearPolynomial(product.minPremium * assignment[i])
        ),
        "premium_$i"
    )
}
metaModel.add(premium)

val risk = LinearExpressionSymbol(
    sum(products.map { p -> p.risk * x[p] / funds }),
    "risk"
)
metaModel.add(risk)

val yield = LinearExpressionSymbol(
    sum(products.map { p -> p.yield * x[p] - premium[p] }),
    "yield"
)
metaModel.add(yield)

// Define objective function
metaModel.maximize(yield, "yield")

// Define constraints
metaModel.addConstraint(
    sum(products.map { p -> x[p] + premium[p] }) eq funds,
    "funs"
)

metaModel.addConstraint(
    risk leq maxRisk,
    "risk"
)

// Call solver to solve
val solver = ScipLinearSolver()
when (val ret = solver(metaModel)) {
    is Ok -> {
        metaModel.tokens.setSolution(ret.value.solution)
    }

    is Failed -> {}
}

// Parse results
val solution = HashMap<Product, UInt64>()
for (token in metaModel.tokens.tokens) {
    if (token.result!! geq Flt64.one && token.variable.belongsTo(x)) {
        val vector = token.variable.vectorView
        val product = products[vector[0]]
        solution[product] = token.result!!.round().toUInt64()
    }
}

Complete Implementation Reference: