Five parameters in the variance equation
Use independent normal shocks and , then set . At each step use inside drift and diffusion terms. The stored Euler variance can become negative, so use its positive part at the next update too. The continuous square-root process is nonnegative under admissible parameters, and the Feller condition ensures the zero boundary is unattainable when started positive. This property of the continuous model does not prevent a finite Euler step from overshooting.
It helps to perturb one parameter at a time. Increasing changes today's instantaneous variance, while changing moves the level the variance drifts towards. Raising brings that long-run level into the forecast faster, in either direction. A larger increases the spread of possible future variance paths. A negative tends to pair negative stock shocks with positive variance shocks. Because these effects interact in prices, compare a variance-path moment before using a single option price to diagnose the implementation.
In a simulation, record how often the variance update would cross zero and how the chosen scheme handles it. The variance diffusion scales as , so a discrete Euler step can overshoot the boundary even when the continuous process stays nonnegative. Full truncation controls where you take the square root and how the drift is evaluated. It does not remove all finite-step bias. To check whether a discrepancy comes from the numerical scheme, refine the step size with comparable sampling error before changing the calibrated parameters.
Two benchmarks for your simulation
Full-truncation Euler in Python
import numpy as np
rng = np.random.default_rng(29)
n, steps = 60_000, 128
spot, strike, rate, maturity = 100, 100, 0.03, 0.5
v0, kappa, theta, xi, rho = 0.06, 2.0, 0.04, 0.30, -0.65
dt = maturity / steps
log_s = np.full(n, np.log(spot))
variance = np.full(n, v0)
for _ in range(steps):
zv, z_independent = rng.standard_normal((2, n))
zs = rho * zv + np.sqrt(1 - rho**2) * z_independent
v_pos = np.maximum(variance, 0)
diffusion = np.sqrt(v_pos * dt)
log_s += (rate - v_pos / 2) * dt + diffusion * zs
variance += kappa * (theta - v_pos) * dt + xi * diffusion * zv
pv = np.exp(-rate * maturity) * np.maximum(np.exp(log_s) - strike, 0)
print("price, standard error", pv.mean(), pv.std(ddof=1) / np.sqrt(n))