@railpath/finance-toolkit
Version:
Production-ready finance library for portfolio construction, risk analytics, quantitative metrics, and ML-based regime detection
152 lines (151 loc) • 6 kB
JavaScript
;
/**
* Solve Quadratic Programming Problem (Simplified Implementation)
*
* Minimizes: ½xᵀQx + cᵀx
* Subject to: Ax = b (equality constraints)
* x ≥ 0 (non-negativity constraints)
*
* Uses a simplified gradient descent with constraint projection.
* This is a practical implementation suitable for portfolio optimization.
*
* @param Q - Quadratic coefficient matrix (n×n, symmetric, positive semi-definite)
* @param c - Linear coefficient vector (n×1)
* @param options - Solver options and constraints
* @returns Optimization result with solution vector and metadata
*
* @example
* ```typescript
* // Portfolio optimization: min wᵀΣw subject to wᵀ1=1, w≥0
* const result = solveQuadraticProgram(
* covarianceMatrix, // Q = Σ
* [0, 0, 0], // c = 0 (minimum variance)
* {
* equalityConstraints: { A: [[1,1,1]], b: [1] }, // wᵀ1 = 1
* nonNegative: true, // w ≥ 0
* maxIterations: 1000,
* tolerance: 1e-6
* }
* );
* ```
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.solveQuadraticProgram = solveQuadraticProgram;
const QuadraticProgramOptionsSchema_1 = require("../schemas/QuadraticProgramOptionsSchema");
const QuadraticProgramResultSchema_1 = require("../schemas/QuadraticProgramResultSchema");
const constraintProjection_1 = require("./constraintProjection");
const vectorOperations_1 = require("./vectorOperations");
const matrixOperations_1 = require("./matrixOperations");
function solveQuadraticProgram(Q, c, options = {}) {
// Parse und validiere mit Zod (füllt Defaults!)
const validated = QuadraticProgramOptionsSchema_1.QuadraticProgramOptionsSchema.parse(options);
const { equalityConstraints, nonNegative, maxIterations, tolerance, initialGuess } = validated;
const n = Q.length;
// Validate inputs
if (Q.length !== n || Q[0].length !== n) {
throw new Error('Quadratic matrix Q must be square');
}
if (c.length !== n) {
throw new Error('Linear coefficient vector c must match Q dimensions');
}
// Initialize solution
let x = initialGuess ? [...initialGuess] : initializeSolution(n, equalityConstraints);
// Validate initial guess
if (x.length !== n) {
throw new Error('Initial guess must match problem dimensions');
}
let converged = false;
let iterations = 0;
let gradientNorm = Infinity;
let constraintViolation = 0;
// Simple gradient descent with constraint projection
for (iterations = 0; iterations < maxIterations; iterations++) {
// Calculate gradient: ∇f = Qx + c
const gradient = calculateGradient(Q, c, x);
// Use adaptive step size
const stepSize = 0.1 / (1 + iterations * 0.01);
// Gradient descent step
x = (0, vectorOperations_1.vectorSubtract)(x, (0, vectorOperations_1.vectorScale)(gradient, stepSize));
// Project onto constraints
x = projectOntoConstraints(x, equalityConstraints, nonNegative);
// Calculate metrics
gradientNorm = (0, vectorOperations_1.vectorNorm)(gradient);
constraintViolation = (0, constraintProjection_1.calculateEqualityConstraintViolation)(x, equalityConstraints?.A || [], equalityConstraints?.b || []);
// Check convergence
if (gradientNorm < tolerance && constraintViolation < tolerance) {
converged = true;
break;
}
}
// Calculate final objective value
const objectiveValue = calculateObjective(Q, c, x);
return QuadraticProgramResultSchema_1.QuadraticProgramResultSchema.parse({
solution: x,
objectiveValue,
converged,
iterations,
gradientNorm,
constraintViolation: constraintViolation || 0
});
}
/**
* Initialize solution vector
*/
function initializeSolution(n, equalityConstraints) {
if (equalityConstraints && equalityConstraints.A.length > 0) {
// For equality constraints, find feasible solution
return findFeasibleSolution(equalityConstraints.A, equalityConstraints.b, n);
}
// Default: equal weights
return new Array(n).fill(1 / n);
}
/**
* Find feasible solution for equality constraints
*/
function findFeasibleSolution(A, b, n) {
const m = A.length;
if (m === 0) {
return new Array(n).fill(1 / n);
}
// For simple cases, use analytical solution
if (m === 1 && A[0].every(val => Math.abs(val - A[0][0]) < 1e-12)) {
// All coefficients are the same (e.g., [1,1,1])
const coefficient = A[0][0];
const target = b[0];
const value = target / (coefficient * n);
return new Array(n).fill(value);
}
// For other cases, use equal weights as fallback
return new Array(n).fill(1 / n);
}
/**
* Calculate gradient: ∇f = Qx + c
*/
function calculateGradient(Q, c, x) {
const Qx = (0, matrixOperations_1.matrixVectorMultiply)(Q, x);
return (0, vectorOperations_1.vectorAdd)(Qx, c);
}
/**
* Project solution onto constraints
*/
function projectOntoConstraints(x, equalityConstraints, nonNegative) {
let result = [...x];
// Project onto equality constraints
if (equalityConstraints && equalityConstraints.A.length > 0) {
result = (0, constraintProjection_1.projectOntoEqualityConstraints)(result, equalityConstraints.A, equalityConstraints.b);
}
// Project onto non-negative constraints
if (nonNegative) {
result = (0, constraintProjection_1.projectOntoNonNegativityConstraints)(result);
}
return result;
}
/**
* Calculate objective value: f(x) = ½xᵀQx + cᵀx
*/
function calculateObjective(Q, c, x) {
const quadraticTerm = 0.5 * (0, vectorOperations_1.vectorDot)((0, matrixOperations_1.matrixVectorMultiply)(Q, x), x);
const linearTerm = (0, vectorOperations_1.vectorDot)(c, x);
return quadraticTerm + linearTerm;
}
// Utility functions are now imported from separate modules