Modern portfolio theory has a Shariah problem.
Markowitz's mean-variance framework the foundation of quantitative portfolio construction since 1952 assumes that return uncertainty can be captured by a probability distribution, that risk is synonymous with variance, and that the optimizer is free to allocate to any asset in any proportion. None of these assumptions hold cleanly in an Islamic finance context.
The probability distribution assumption runs into a theological objection: in classical Islamic jurisprudence, gharar (excessive uncertainty) is prohibited. A model that treats future returns as draws from a known distribution and then maximises a risk-adjusted expected value of those draws is making a claim about the future that some scholars consider epistemically overconfident, if not outright problematic. More practically, the assumption that any asset can receive any allocation breaks immediately when you introduce Shariah screening: certain sectors are binary exclusions (riba-bearing instruments, alcohol, tobacco, weapons, entertainment), and certain financial ratios must fall within defined thresholds for a stock to qualify as halal at all.
The result is a combinatorial problem with hard binary constraints embedded inside a continuous optimization. Standard quadratic programming doesn't handle this gracefully. And the fuzzy semi-spread approach from genetic algorithm-based Shariah portfolio research offers something that standard approaches don't: a way to model return uncertainty without claiming to know the probability distribution, while still being able to enforce the binary constraints that Shariah compliance requires.
I want to walk through why this matters, how the mathematics works, and what it implies for quantitative Islamic finance more broadly.
The Problem with Probabilistic Risk in a Shariah Context
Standard mean-variance optimization works like this. You estimate expected returns μ and a covariance matrix Σ for a universe of assets. You then solve:
minimise wᵀ Σ w
subject to wᵀ μ ≥ R_target
Σᵢ wᵢ = 1
wᵢ ≥ 0
The output is a set of portfolio weights w that minimise variance (proxy for risk) for a given target return.
The problems for Shariah-compliant portfolios are threefold:
1. The universe must be pre-screened. Before optimization runs, every asset must pass a Shariah filter sector exclusions (no conventional banks, no alcohol, etc.) and financial ratio screens (total debt to total assets < 33%, accounts receivable to total assets < 49%, etc.). This reduces the investable universe and makes it harder to achieve diversification.
2. Weights must respect sector constraints. Certain interpretations of Shariah require minimum allocations to productive sectors or prohibit concentration in specific areas. These are not smooth constraints they introduce discontinuities.
3. Return uncertainty is genuinely fuzzy, not probabilistic. For equities listed on Bursa Malaysia or the Gulf exchanges, historical return data is thin, volatile, and affected by regulatory, political, and religious factors that don't follow stationary distributions. Claiming to know μ and Σ with confidence is epistemically dubious. The Shariah-aware investor is, in a meaningful sense, operating under ambiguity rather than risk and ambiguity calls for a different mathematical treatment.
Fuzzy Numbers as a Model of Ambiguity
Fuzzy set theory, developed by Lotfi Zadeh in 1965, provides a mathematical framework for reasoning under ambiguity. Where probability assigns a single number P(x) representing the likelihood of an event, fuzzy logic assigns a membership function μ(x) ∈ [0, 1] representing the degree to which x belongs to a fuzzy set.
For portfolio returns, the key construct is a trapezoidal fuzzy number, which represents a return estimate as a range of possibilities with varying degrees of credibility:
R̃ = (a, b, c, d)
Where:
- [b, c] is the core - the range of returns considered most plausible (membership = 1)
- a is the pessimistic lower bound (membership = 0)
- d is the optimistic upper bound (membership = 0)
- The membership function rises linearly from a to b and falls linearly from c to d
Visually:
Membership
1 | ┌────┐
| / \
| / \
0 |__/ \__
a b c d
Return
This is more honest than a point estimate. You're saying: "I believe the return will be somewhere between b and c with full confidence, could be as low as a or as high as d with diminishing confidence, and I make no claim beyond d or below a." No probability distribution required.
Fuzzy Semi-Spreads: Replacing Variance with Ambiguity-Aware Risk
The central innovation in the genetic algorithm approach to Shariah portfolio optimization is replacing variance (the standard risk measure) with fuzzy semi-spreads.
For a fuzzy return R̃ = (a, b, c, d), the semi-spreads are:
Left semi-spread: lₛ = (b - a) / 2 ← downside ambiguity
Right semi-spread: rₛ = (d - c) / 2 ← upside ambiguity
The expected value of the fuzzy return is:
E(R̃) = (a + b + c + d) / 4
The risk measure used is the left semi-spread lₛ capturing downside uncertainty specifically, rather than symmetric variance. This is analogous to semi-variance in classical finance (which only penalises downside volatility) but applied to fuzzy numbers rather than historical returns.
For a portfolio of n assets with weights w = (w₁, ..., wₙ) and fuzzy returns R̃ᵢ = (aᵢ, bᵢ, cᵢ, dᵢ):
Portfolio fuzzy return: R̃ₚ = Σᵢ wᵢ · R̃ᵢ
= (Σwᵢaᵢ, Σwᵢbᵢ, Σwᵢcᵢ, Σwᵢdᵢ)
Portfolio expected return: E(R̃ₚ) = Σᵢ wᵢ · E(R̃ᵢ)
Portfolio left semi-spread: lₛ(R̃ₚ) = Σᵢ wᵢ · lₛ(R̃ᵢ)
Note that both expected return and left semi-spread are linear in the weights which is computationally convenient, but the Shariah binary constraints mean standard linear/quadratic solvers still can't handle the full problem cleanly.
The Shariah Compliance Constraints
The full set of constraints in a Shariah-compliant fuzzy portfolio problem looks like this:
Binary screening constraints (hard, asset-level):
xᵢ ∈ {0, 1} ← asset either included or excluded
xᵢ = 0 if asset fails any Shariah screen
Shariah screens typically include: - Sector exclusion: conventional finance, alcohol, pork, tobacco, weapons, adult entertainment - Debt ratio: total debt / total assets < 33% - Illiquid assets ratio: (cash + receivables) / total assets < 49% - Interest income ratio: interest income / total revenue < 5%
Allocation constraints (continuous, portfolio-level):
Σᵢ wᵢ = 1 (fully invested)
wᵢ ≥ 0 (no short selling - prohibited under Shariah)
wᵢ ≤ wᵢ_max (concentration limits)
wᵢ = 0 if xᵢ = 0 (can't hold excluded assets)
Objective:
maximise E(R̃ₚ) - λ · lₛ(R̃ₚ)
where λ is the investor's risk aversion parameter.
The coupling of binary xᵢ variables with continuous wᵢ variables makes this a mixed-integer programming problem NP-hard in general. This is why a genetic algorithm is the natural solver.
Why Genetic Algorithms
A genetic algorithm (GA) treats the portfolio selection problem as an evolutionary search. A chromosome encodes both the binary selection vector x and the continuous weight vector w:
Chromosome: [x₁, x₂, ..., xₙ | w₁, w₂, ..., wₙ]
←── binary part ──→ ←─ continuous part ─→
The algorithm:
- Initialise a population of random chromosomes (respecting Shariah screens any chromosome with
xᵢ = 1for a screened-out asset is immediately repaired) - Evaluate fitness compute
E(R̃ₚ) λ · lₛ(R̃ₚ)for each chromosome - Select tournament or roulette selection biased toward higher fitness
- Crossover combine binary and continuous segments from two parents
- Mutate flip binary bits, perturb continuous weights (with renormalization)
- Repair enforce Shariah constraints and weight normalization after mutation
- Repeat for
Ngenerations
The repair step is critical. After crossover and mutation, a chromosome may violate the Shariah binary constraints or produce weights that don't sum to 1. A dedicated repair operator:
- Sets wᵢ = 0 for any asset where xᵢ = 0
- Renormalizes remaining weights to sum to 1
- Checks financial ratios for all selected assets and flips xᵢ = 0 for violators
Python Implementation Sketch
import numpy as np
from dataclasses import dataclass
@dataclass
class FuzzyReturn:
"""Trapezoidal fuzzy number (a, b, c, d)."""
a: float # pessimistic lower bound
b: float # lower core
c: float # upper core
d: float # optimistic upper bound
@property
def expected_value(self) -> float:
return (self.a + self.b + self.c + self.d) / 4
@property
def left_semi_spread(self) -> float:
return (self.b - self.a) / 2
@property
def right_semi_spread(self) -> float:
return (self.d - self.c) / 2
def portfolio_fitness(
weights: np.ndarray,
selection: np.ndarray, # binary: 1 = included, 0 = excluded
fuzzy_returns: list[FuzzyReturn],
risk_aversion: float = 1.0,
) -> float:
"""
Compute portfolio fitness: E(R̃ₚ) - λ · lₛ(R̃ₚ)
Higher is better.
"""
w = weights * selection # zero out excluded assets
if w.sum() == 0:
return -np.inf
w = w / w.sum() # renormalize
exp_returns = np.array([r.expected_value for r in fuzzy_returns])
semi_spreads = np.array([r.left_semi_spread for r in fuzzy_returns])
portfolio_return = np.dot(w, exp_returns)
portfolio_risk = np.dot(w, semi_spreads)
return portfolio_return - risk_aversion * portfolio_risk
def shariah_screen(
asset_idx: int,
debt_ratio: float,
interest_income_ratio: float,
illiquid_ratio: float,
sector: str,
excluded_sectors: list[str],
) -> bool:
"""Returns True if asset passes Shariah screen."""
if sector in excluded_sectors:
return False
if debt_ratio >= 0.33:
return False
if interest_income_ratio >= 0.05:
return False
if illiquid_ratio >= 0.49:
return False
return True
def repair_chromosome(
weights: np.ndarray,
selection: np.ndarray,
shariah_eligible: np.ndarray, # pre-computed eligibility
) -> tuple[np.ndarray, np.ndarray]:
"""
Enforce Shariah constraints and renormalize weights.
"""
# Force exclude non-eligible assets
selection = selection * shariah_eligible
# Zero weights of excluded assets
weights = weights * selection
# Renormalize
total = weights.sum()
if total > 0:
weights = weights / total
else:
# Fallback: equal weight eligible assets
eligible = shariah_eligible.astype(float)
weights = eligible / eligible.sum()
selection = shariah_eligible.copy()
return weights, selection
class SharıahGA:
"""
Genetic Algorithm for Shariah-compliant fuzzy portfolio optimization.
"""
def __init__(
self,
fuzzy_returns: list[FuzzyReturn],
shariah_eligible: np.ndarray,
risk_aversion: float = 1.0,
population_size: int = 100,
n_generations: int = 200,
crossover_rate: float = 0.8,
mutation_rate: float = 0.05,
seed: int = 42,
):
self.fuzzy_returns = fuzzy_returns
self.eligible = shariah_eligible
self.lam = risk_aversion
self.pop_size = population_size
self.n_gen = n_generations
self.cx_rate = crossover_rate
self.mut_rate = mutation_rate
self.n_assets = len(fuzzy_returns)
self.rng = np.random.default_rng(seed)
def _init_population(self):
"""Generate initial population respecting Shariah eligibility."""
population = []
for _ in range(self.pop_size):
# Random binary selection (only from eligible assets)
sel = self.rng.integers(0, 2, self.n_assets) * self.eligible
# Random weights
w = self.rng.dirichlet(np.ones(self.n_assets))
w, sel = repair_chromosome(w, sel, self.eligible)
population.append((w, sel))
return population
def _fitness(self, w, sel):
return portfolio_fitness(w, sel, self.fuzzy_returns, self.lam)
def _tournament_select(self, population, fitness_scores, k=3):
"""Tournament selection."""
idx = self.rng.choice(len(population), k, replace=False)
best = idx[np.argmax([fitness_scores[i] for i in idx])]
return population[best]
def _crossover(self, parent1, parent2):
"""Single-point crossover on both binary and weight segments."""
point = self.rng.integers(1, self.n_assets)
w1, s1 = parent1
w2, s2 = parent2
child_w = np.concatenate([w1[:point], w2[point:]])
child_s = np.concatenate([s1[:point], s2[point:]])
return repair_chromosome(child_w, child_s, self.eligible)
def _mutate(self, w, sel):
"""Bit-flip mutation on selection, Gaussian perturbation on weights."""
for i in range(self.n_assets):
if self.rng.random() < self.mut_rate and self.eligible[i]:
sel[i] = 1 - sel[i] # flip
# Perturb weights
noise = self.rng.normal(0, 0.05, self.n_assets)
w = np.maximum(w + noise, 0)
return repair_chromosome(w, sel, self.eligible)
def optimise(self) -> tuple[np.ndarray, np.ndarray, float]:
"""Run GA and return (best_weights, best_selection, best_fitness)."""
population = self._init_population()
best_w, best_sel = population[0]
best_fit = self._fitness(best_w, best_sel)
for gen in range(self.n_gen):
fitness_scores = [self._fitness(w, s) for w, s in population]
# Track best
gen_best_idx = np.argmax(fitness_scores)
if fitness_scores[gen_best_idx] > best_fit:
best_fit = fitness_scores[gen_best_idx]
best_w, best_sel = population[gen_best_idx]
# Build next generation
next_pop = [population[gen_best_idx]] # elitism: keep best
while len(next_pop) < self.pop_size:
p1 = self._tournament_select(population, fitness_scores)
p2 = self._tournament_select(population, fitness_scores)
if self.rng.random() < self.cx_rate:
child_w, child_sel = self._crossover(p1, p2)
else:
child_w, child_sel = p1
child_w, child_sel = self._mutate(child_w.copy(), child_sel.copy())
next_pop.append((child_w, child_sel))
population = next_pop
if (gen + 1) % 50 == 0:
print(f" Gen {gen+1:3d}/{self.n_gen} - best fitness: {best_fit:.4f}")
return best_w, best_sel, best_fit
Running It
# Example: 5-asset Shariah-eligible universe
# Fuzzy returns estimated from analyst range estimates
fuzzy_returns = [
FuzzyReturn(0.04, 0.07, 0.11, 0.15), # MAYBANK (screened - conventional bank, excluded)
FuzzyReturn(0.06, 0.09, 0.13, 0.18), # TENAGA
FuzzyReturn(0.05, 0.08, 0.12, 0.16), # PETRONAS CHEMICALS
FuzzyReturn(0.03, 0.06, 0.09, 0.13), # DIALOG GROUP
FuzzyReturn(0.07, 0.10, 0.14, 0.20), # HARTALEGA
]
# Pre-computed Shariah eligibility (0 = excluded, 1 = eligible)
shariah_eligible = np.array([0, 1, 1, 1, 1]) # MAYBANK excluded (riba)
ga = SharıahGA(
fuzzy_returns=fuzzy_returns,
shariah_eligible=shariah_eligible,
risk_aversion=1.5,
population_size=150,
n_generations=300,
)
best_weights, best_selection, best_fitness = ga.optimise()
print(f"\nOptimal Shariah Portfolio:")
assets = ["MAYBANK", "TENAGA", "PETCHEM", "DIALOG", "HARTALEGA"]
for i, (asset, w, sel) in enumerate(zip(assets, best_weights, best_selection)):
status = "EXCLUDED" if sel == 0 else f"{w*100:.1f}%"
ev = fuzzy_returns[i].expected_value
lss = fuzzy_returns[i].left_semi_spread
print(f" {asset:<15} {status:<12} E(R̃)={ev:.3f} lₛ={lss:.3f}")
print(f"\nPortfolio E(R̃ₚ): {sum(best_weights[i]*fuzzy_returns[i].expected_value for i in range(5)):.4f}")
print(f"Portfolio lₛ(R̃ₚ): {sum(best_weights[i]*fuzzy_returns[i].left_semi_spread for i in range(5)):.4f}")
print(f"Fitness (E - λ·lₛ): {best_fitness:.4f}")
Why This Is More Honest Than Standard Mean-Variance
The fuzzy approach forces you to be explicit about what you actually know and don't know.
When you estimate R̃ = (0.06, 0.09, 0.13, 0.18) for a stock, you're saying: "I'm confident the annual return is somewhere between 9% and 13%, I think it could be as low as 6% or as high as 18%, and I won't pretend to have a view beyond that." There's no claim that this follows a normal distribution. There's no pretense that a three-year historical covariance matrix reliably predicts future co-movements.
The left semi-spread lₛ = (b a) / 2 captures only downside ambiguity. This is both more Islamic in spirit (the prohibition on gharar is specifically about uncertainty that disadvantages the counterparty downside risk) and more practically conservative.
Standard mean-variance optimization is symmetric in its risk treatment it penalises upside volatility as much as downside. A Shariah-aware investor who is genuinely uncertain about the future but optimistic about a halal business shouldn't be penalised for that optimism in the same way they should be penalised for downside exposure.
Implications for Takaful and Islamic Fund Management
In the Malaysian context where takaful operators are required to maintain Shariah-compliant investment portfolios under the Islamic Financial Services Act 2013 and BNM's guidelines the practical application of this framework has direct relevance.
Takaful funds invest participants' tabarru' contributions into a portfolio that must: 1. Be fully Shariah-screened (enforced by the Shariah Supervisory Board) 2. Achieve sufficient returns to cover claims and expenses 3. Maintain adequate reserves against adverse loss scenarios
The fuzzy semi-spread approach maps naturally onto this requirement structure. The left semi-spread is a conservative estimate of downside ambiguity in investment returns exactly the kind of risk measure a takaful operator should use when sizing reserves against investment shortfalls. The genetic algorithm handles the Shariah binary constraints cleanly without requiring a custom MILP solver.
What I find intellectually compelling about this approach is that it doesn't try to fit Islamic finance into a Western quantitative framework with constraints bolted on. It starts from the epistemological position that the future is genuinely uncertain in ways that probability distributions don't capture well a position that is both mathematically defensible and theologically coherent in the Islamic tradition.
That's rare in quantitative finance. Most "Islamic" financial products are conventional instruments with Shariah labels. A portfolio optimization framework built on fuzzy logic instead of probabilistic risk is, in a meaningful sense, Islamic not just in its outputs but in its foundations.
What Comes Next
The framework described here is a starting point. Obvious extensions include:
Multi-period optimization. The current framework is single-period. A multi-period fuzzy portfolio model would incorporate the evolution of fuzzy return estimates over time relevant for takaful funds with long-duration liabilities.
Zakat-adjusted returns. A truly Islamic portfolio model should account for zakat obligations on investment gains. This adjusts the effective return distribution and could be incorporated as a linear reduction in expected fuzzy return.
ESG integration. Shariah screening overlaps significantly with ESG criteria. A combined Shariah-ESG screen with fuzzy return estimates for green sukuk and Islamic REITs would be a natural next step for Malaysian institutional portfolios.
Calibration from analyst ranges. The trapezoidal fuzzy numbers need to be estimated from real data. Analyst forecast ranges (low/base/high) map directly onto (a, b, c, d) making this more practical than it might appear. Bloomberg consensus estimates already provide exactly the range structure that fuzzy returns require.
The quantitative Islamic finance literature is thinner than it should be. Papers like this one which take the epistemological constraints of Islamic finance seriously rather than treating Shariah compliance as just another constraint set are exactly the kind of work that deserves wider attention from practitioners.
This post draws on the framework from "A Genetic Algorithm Approach to Shariah-Compliant Portfolio Optimization in a Fuzzy Environment." The Python implementation is original. All financial examples are illustrative not investment advice.