Issues
Discovered problems out of scope for the current change. Each entry has a label, affected files, description, and fix recommendation. Resolved issues are deleted from this file, not archived.
Performance/allocation issues with no behavioural impact are tracked as optimizations (O1, O2, ...), not here.
Active
I26: Sign error in soc_exact.md AS price-diffusion intermediate step
- Files:
docs/src/reference/soc_exact.md(CARA separation and reduced PDE) - Problem: The step deriving the inventory penalty writes
1/2 sigma^2 partial_S^2 V = -1/2 gamma^2 sigma^2 q^2 V. With the ansatzV = -exp(-gamma (X + q S + theta))the correct derivative ispartial_S^2 V = +gamma^2 q^2 V, so the term is+1/2 gamma^2 sigma^2 q^2 V; the written sign is reversed. The final reduced PDE (penalty-1/2 gamma sigma^2 q^2) is correct, so the intermediate equality is internally inconsistent with it. - Fix: Flip the sign of the intermediate equality to
+1/2 gamma^2 sigma^2 q^2 V, or drop the intermediate step and state the reduced penalty directly.
I20: AvellanedaExact::value_function_theta returns a gauge-shifted theta
- Files:
solver/src/numeric/ode.rs(LinearSpectralSolver::solve_tridiagonal),solver/src/analytical/avellaneda/exact.rs(value_function_theta),neural_solver/tests/data/reference.json(theavellaneda_stoikov.thetavalues),neural_solver/src/neural_solver/models/avellaneda_stoikov.py. - Problem:
solve_tridiagonalcomputesexp(M tau) y0but returns it scaled byexp(-max_exp)for numerical stability. The scaling cancels in the spread ratios (v_q / v_{q+1}), soexact_spreadsis correct, butvalue_function_thetaapplieslnto the scaled vector, so its returnedthetais shifted by-max_exp / kappa. The absolute theta (and hence the value function) is therefore not the physical reduced value, even though the observable spreads are unaffected. - Fix: Either multiply the returned vector back by
exp(max_exp)insolve_tridiagonalbefore anylnis taken, or makevalue_function_thetaaddmax_exp / kappaback, and regeneratereference.json.
I19: Heston finite-horizon BSDE diverges at the a = 140 test point
-
Files:
solver/tests/numerical/bsde_vs_fdm/consistency.rs(test_heston_finite_horizon_bsde_matches_fdm_spread_values),solver/src/models/heston.rs -
Problem: The decoupled BSDE regression diverged at
a = 140(bid = 10.0,ask = -5.0, fitted value~5.6e4). Two independent root causes were traced:-
Reward/forward discretization inconsistency.
Heston::optimizecomputes the fill intensity aslambda = a * exp(-kappa * delta)with no cap (unlike the Hawkes models). When a transient regression gradient drives a spread to its lower clamp (delta = -5), the intensity becomesa * exp(7.5). Ata = 140that is~2.5e5, andrunning_reward = (lambda_bid + lambda_ask) / (gamma + kappa)blows up, closing a positive-feedback loop. The forward step clampsp = lambda * dtto[0, 1], but the reward used the unclampedlambda, so the backward step counted fills the forward step cannot produce. Fix: bound the reward rate at1/dtper fill side inbsde_driveronly, matching the forward probability clamp without touching thelambdareturned toto_spreads. Capping the intensity itself (e.g..min(20)) is incorrect:to_spreadsinvertsdelta = -ln(lambda/a)/kappa, so ata = 140even the correct symmetriclambda = 59exceeds any absolute cap, and capping would corrupt the recovered spread and the FD path. -
Decoupled proxy-fill forward pass. The finite-horizon test ran
BsdeSolver::new(decoupled). For a full-value market-making model the inventory dynamics depend on the optimal control, so the decoupled forward pass simulated inventory fills from the frozen base-spread proxy while the backward step recovered a skewed optimal control. This forward/backward inconsistency produced a large anti-symmetric bid/ask skew atq = 0(bid1.03, ask0.13, total still matching FD). The total spread matched FD but each component was off by~0.45. Fix: the test usesBsdeSolver::coupled(Picard iteration), which re-simulates the forward inventory under the current optimal control vianext_step_controlled. With coupled mode the bid/ask components match FD to~0.005.
These two fixes are mathematically required, not duct tape: the reward bound restores the discretization-consistent backward driver, and coupled mode restores the forward/backward consistency of a control-dependent state process.
-
I9: policy_iteration.rs / operator.rs re-export shims
- Files:
solver/src/numeric/policy_iteration.rs,solver/src/numeric/operator.rs - Problem: Both are thin
pub use super::finite_difference::*;shims from Phase 2.numeric/mod.rsstill calls them "backward-compatible." - Fix: Update callers to
solver::numeric::finite_difference::and delete the shims, or document them as permanent.
I11: engine::python::market::legacy duplicates simulation + engine
- File:
engine/src/python/market/legacy.rs - Problem: Defines
PyEngine,PyEngineGbmAs, etc. plusrun_many_*functions, all hardcoded to GBM/Heston/Bates + AS. The newerPySimEngineandrun_simulation{,_batch,_summary}cover the same ground generically. - Fix: Confirm no external consumers, then delete
legacy.rsand its registrations. Otherwise mark#[deprecated].
I14: Python vec_env bindings out of sync with VecEnv
- File:
engine/src/python/market/vec_env.rs - Problem: The binding imports
crate::vec_env::VecEnvProcess(no longer present), setsVecEnvConfig { process }(the field no longer exists; the config now holds Heston parameters directly), and callsVecEnv::state_dim(the method was removed).cargo check -p engine --features pythonfails for this reason. - Fix: Rewrite the binding against the current
VecEnvAPI. Either expose the fixed 6D state width or reintroduce astate_dimaccessor, and drop the removedVecEnvProcess/processindirection.
I15: Orphaned convergence_bump.rs references removed solver APIs
- File:
solver/tests/numerical/exact_vs_finite_diff/convergence_bump.rs - Problem: The file is not declared in
exact_vs_finite_diff/mod.rs, so it is never compiled. It still imports the removedsolver::models::traits::{Gradients, Model}, callsPolicyIterationSolver::solve_grid, and callsOperator::compute_gradients. - Fix: Either delete the file (it duplicates
convergence_base.rsandconvergence_market_impact.rs) or port it to the currentControlProblem/solve_grid_controlAPI and wire it into the module.
I16: Market-making BSDE spread magnitude is ~10x the exact solution
- Files:
solver/src/numeric/bsde/solver.rs,solver/src/models/control.rs,solver/src/models/avellaneda.rs(and drift/impact variants) - Problem: The BSDE backward step used only
running_reward(the reduced HamiltonianH*) and omitted the reduced generator (the local inventory-risk source-0.5 gamma sigma^2 q^2), so the market-making value had no inventory skew and the total spread collapsed to2 * base_spread. The configuredInitializationModewas also never applied: every forward path started at the exact initial state, giving a rank-one design that could not identify the inventory gradient. The streaming normal-equation regression squared the design condition number and added a one-dimensional value bias (Merton9.2%, LQ up to66%). The impact model's forward proxy also omitted the permanent impact shift. - Fix: Added a
ControlProblem::bsde_driverhook (defaultrunning_rewardfor full value problems; reduced market-making models add the local source), appliedInitializationModeto the initial forward cloud (integer lattice for discrete inventory, continuous for diffusion), switched the default regression backend to Householder QR with a minimal ridge floor for rank-deficiency, and included the impact shift in the impact forward proxy.
I18: Finite-horizon LQ BSDE value carries O(dt) discretization bias
- Files:
solver/src/models/lq_regulator.rs,solver/src/numeric/bsde/solver.rs - Problem: The LQ value estimate is biased
0.47%atdt=0.005and0.30%atdt=0.0025, scaling withdt(Euler-Maruyama discretization error), not with path count. The<= 0.1%high-precision target requiresdt <= ~0.001at100_000paths, which is a slow one-time run, not a defect. Documented here so the discretization floor is tracked separately from Monte Carlo noise. - Fix: No code change required; record the achieved
dt-limited precision inbsde_precision.mdand note that the<= 0.1%target for LQ needs a finer time step. - Stale reference: the
0.47%/0.30%figures were measured againstLqRegulator::exact_value, which before the Riccati time-indexing fix returned the constantP(0)for every remaining horizon instead of the time-varyingP(t). Re-measure the bias against the corrected reference before finalizing thebsde_precision.mdnumbers.
I21: BSDE mixed-derivative stencil is computed but never consumed
- Files:
solver/src/numeric/bsde/solution.rs(state_derivatives),solver/src/models/control.rs(StateDerivatives) - Problem: The BSDE
state_derivativescentered four-corner stencil populateshessian_full[i][j]fori != j, but no model'soptimizereads those off-diagonal entries. The LQ regulator'soptimizereturns the closed-form-R^-1 B' P(t) x, which is independent of the diffusion matrixCand ignoresderivsentirely; the market-making models'optimizereads only the directionalfwd/bwdentries. The Hestonrhocorrelation is a reduced drift correction, not ahessian_fullcross term. As a result the BSDE mixed derivative is dead output: it is unit-tested against a synthetic bilinear basis but never validated end to end against a closed form through a real solve. The FD mixed stencil is consumed by the LQ generator and is now guarded bycross_term_changes_fd_value. - Fix: Add a correlated control problem whose optimizer genuinely depends
on the off-diagonal Hessian (for example multiplicative/state-dependent
noise, which has no simple Riccati closed form, or a cost coupling that
makes the optimal control read
hessian_full), then validate FD and BSDE against its exact or manufactured reference.
I23: Quadratic-generator BSDE test blows up from a wide initial range
- Files:
solver/tests/numerical/bsde_solver_validation/analytical_baselines.rs(test_quadratic_generator_produces_finite_values) - Problem:
QuadraticGeneratorModel's forward stepdy = y^2 dthas finite-time blow-up att = 1/y. The solver is configured withwith_initial_range(5.0), which perturbs the initial cloud over a wide interval aroundy_init = 0.5; paths seeded withy > 2blow up before thehorizon = 0.5endpoint, sosolve_trajectory_controlreturns a non-finite value andassert!(bsde_value.is_finite())fails. The test is unseeded, so the failure is flaky. - Fix: Narrow the initial range (for example
with_initial_range(0.5)) so the whole cloud stays inside the blow-up radius for the horizon, addwith_seed(...), or cap the forward state / shorten the horizon.
I24: Hawkes finite-horizon BSDE vs FDM spread test exceeds tolerance
- Files:
solver/tests/numerical/bsde_vs_fdm/consistency.rs(test_hawkes_finite_horizon_bsde_matches_fdm_spread_values) - Problem: The bid or ask spread recovered by the BSDE solver differs from
the FD reference by more than
tol = 0.15. The test uses30_000paths, 3 unseeded repeats, a Hermite(3) basis, and no fixed seed, so it is subject to Monte Carlo variance and the same forward/backward bias family documented in I16, I18, and I19. It fails intermittently rather than deterministically. - Fix: Seed the solver with
with_seed(...)and either raisenum_paths/ repeats or relax the tolerance after measuring the actual bias. Check whether the Hawkes forward proxy omits a local source (cf. I16) before treating the residual as pure noise.