more fixes

This commit is contained in:
Bojan Kucera 2025-06-04 18:37:26 -04:00
parent ab7ef2b678
commit 4c2220d148
5 changed files with 138 additions and 43 deletions

View file

@ -645,19 +645,81 @@ function erf(x: number): number {
}
function betaIncomplete(a: number, b: number, x: number): number {
// Simplified beta incomplete function
// Better approximation of incomplete beta function
if (x === 0) return 0;
if (x === 1) return 1;
// Use continued fraction approximation
let result = 0;
for (let i = 0; i < 100; i++) {
const term = Math.pow(x, a + i) * Math.pow(1 - x, b) / (a + i);
result += term;
if (Math.abs(term) < 1e-10) break;
// Use continued fraction approximation (Lentz's algorithm)
const fpmin = 1e-30;
const maxIter = 200;
const eps = 3e-7;
const bt = Math.exp(
gammaLn(a + b) - gammaLn(a) - gammaLn(b) +
a * Math.log(x) + b * Math.log(1 - x)
);
if (x < (a + 1) / (a + b + 2)) {
return bt * betaContinuedFraction(a, b, x) / a;
} else {
return 1 - bt * betaContinuedFraction(b, a, 1 - x) / b;
}
return result;
function betaContinuedFraction(a: number, b: number, x: number): number {
let c = 1;
let d = 1 - (a + b) * x / (a + 1);
if (Math.abs(d) < fpmin) d = fpmin;
d = 1 / d;
let h = d;
for (let m = 1; m <= maxIter; m++) {
const m2 = 2 * m;
const aa = m * (b - m) * x / ((a + m2 - 1) * (a + m2));
d = 1 + aa * d;
if (Math.abs(d) < fpmin) d = fpmin;
c = 1 + aa / c;
if (Math.abs(c) < fpmin) c = fpmin;
d = 1 / d;
h *= d * c;
const bb = -(a + m) * (a + b + m) * x / ((a + m2) * (a + m2 + 1));
d = 1 + bb * d;
if (Math.abs(d) < fpmin) d = fpmin;
c = 1 + bb / c;
if (Math.abs(c) < fpmin) c = fpmin;
d = 1 / d;
const del = d * c;
h *= del;
if (Math.abs(del - 1) < eps) break;
}
return h;
}
function gammaLn(xx: number): number {
const stp = 2.50662827465;
const coeffs = [
76.18009172947146,
-86.50532032941677,
24.01409824083091,
-1.231739572450155,
0.1208650973866179e-2,
-0.5395239384953e-5
];
let x = xx - 1;
let tmp = x + 5.5;
tmp -= (x + 0.5) * Math.log(tmp);
let ser = 1.000000000190015;
for (let j = 0; j < 6; j++) {
x += 1;
ser += coeffs[j] / x;
}
return -tmp + Math.log(stp * ser);
}
}
function eigenDecomposition(matrix: number[][]): { eigenvalues: number[]; eigenvectors: number[][] } {

View file

@ -208,14 +208,13 @@ export function identifyMarketRegime(
const recentData = ohlcv.slice(-lookbackPeriod);
const prices = recentData.map(candle => candle.close);
const volumes = recentData.map(candle => candle.volume);
// returns and volatility
// returns and volatility
const returns = [];
for (let i = 1; i < prices.length; i++) {
returns.push((prices[i] - prices[i - 1]) / prices[i - 1]);
}
const volatility = Volatility(returns);
const volatility = calculateVolatility(returns);
const averageVolume = volumes.reduce((sum, vol) => sum + vol, 0) / volumes.length;
// Trend analysis
@ -319,11 +318,10 @@ export function IntradayPatterns(
for (let hour = 0; hour < 24; hour++) {
const data = hourlyData[hour];
hourlyReturns[hour] = data.returns.length > 0 ?
hourlyReturns[hour] = data.returns.length > 0 ?
data.returns.reduce((sum, ret) => sum + ret, 0) / data.returns.length : 0;
hourlyVolatility[hour] = Volatility(data.returns);
hourlyVolatility[hour] = calculateVolatility(data.returns);
hourlyVolume[hour] = data.volumes.length > 0 ?
data.volumes.reduce((sum, vol) => sum + vol, 0) / data.volumes.length : 0;
@ -374,21 +372,20 @@ export function PriceDiscovery(
returns1.push((prices1[i] - prices1[i - 1]) / prices1[i - 1]);
returns2.push((prices2[i] - prices2[i - 1]) / prices2[i - 1]);
}
// correlations with lags
const correlation0 = Correlation(returns1, returns2);
// correlations with lags
const correlation0 = calculateCorrelation(returns1, returns2);
const correlation1 = returns1.length > 1 ?
Correlation(returns1.slice(1), returns2.slice(0, -1)) : 0;
calculateCorrelation(returns1.slice(1), returns2.slice(0, -1)) : 0;
const correlationMinus1 = returns1.length > 1 ?
Correlation(returns1.slice(0, -1), returns2.slice(1)) : 0;
calculateCorrelation(returns1.slice(0, -1), returns2.slice(1)) : 0;
// Price lead-lag (simplified)
const priceLeadLag = correlation1 - correlationMinus1;
// Information shares (simplified Hasbrouck methodology)
const variance1 = Variance(returns1);
const variance2 = Variance(returns2);
const covariance = Covariance(returns1, returns2);
const variance1 = calculateVariance(returns1);
const variance2 = calculateVariance(returns2);
const covariance = calculateCovariance(returns1, returns2);
const totalVariance = variance1 + variance2 + 2 * covariance;
const informationShare1 = totalVariance > 0 ? (variance1 + covariance) / totalVariance : 0.5;
@ -436,14 +433,13 @@ export function MarketStress(
returns.push((recentData[i].close - recentData[i - 1].close) / recentData[i - 1].close);
volumes.push(recentData[i].volume);
}
// Volatility stress
const volatility = Volatility(returns);
// Volatility stress
const volatility = calculateVolatility(returns);
const volatilityStress = Math.min(1, volatility / 0.05); // Normalize to 5% daily vol
// Liquidity stress (volume-based)
const averageVolume = volumes.reduce((sum, vol) => sum + vol, 0) / volumes.length;
const volumeVariability = Volatility(volumes.map(vol => vol / averageVolume));
const volumeVariability = calculateVolatility(volumes.map(vol => vol / averageVolume));
const liquidityStress = Math.min(1, volumeVariability);
// Correlation stress (simplified - would need multiple assets)
@ -542,7 +538,7 @@ export function ImplementationShortfall(
// Helper functions
function Volatility(returns: number[]): number {
function calculateVolatility(returns: number[]): number {
if (returns.length < 2) return 0;
const mean = returns.reduce((sum, ret) => sum + ret, 0) / returns.length;
@ -551,7 +547,7 @@ function Volatility(returns: number[]): number {
return Math.sqrt(variance);
}
function Correlation(x: number[], y: number[]): number {
function calculateCorrelation(x: number[], y: number[]): number {
if (x.length !== y.length || x.length < 2) return 0;
const n = x.length;

View file

@ -220,18 +220,14 @@ export function sharpeOptimizedPositionSize(
): number {
// Input validation
if (volatility <= 0 || accountSize <= 0 || expectedReturn <= riskFreeRate || maxLeverage <= 0) return 0;
// Kelly criterion with Sharpe ratio optimization
// Kelly criterion with Sharpe ratio optimization
const excessReturn = expectedReturn - riskFreeRate;
const kellyFraction = excessReturn / (volatility * volatility);
// Apply maximum leverage constraint and ensure reasonable bounds
// Apply maximum leverage constraint
const constrainedFraction = Math.max(0, Math.min(kellyFraction, maxLeverage));
// Further cap at 100% of account for safety
const finalFraction = Math.min(constrainedFraction, 1);
return accountSize * finalFraction;
return accountSize * constrainedFraction;
}
/**
@ -405,14 +401,12 @@ export function riskParityPositionSize(
return assets.map(asset => {
if (asset.volatility === 0 || asset.price === 0) return 0;
// Calculate weight based on inverse volatility
// Calculate weight based on inverse volatility
const weight = (1 / asset.volatility) / totalInverseVol;
// Scale by target risk
const riskAdjustedWeight = weight * (targetRisk / asset.volatility);
const positionValue = accountSize * riskAdjustedWeight;
// The weight itself already accounts for risk parity
// We just need to scale by target risk once
const positionValue = accountSize * weight * targetRisk;
return Math.floor(positionValue / asset.price);
});
}

View file

@ -359,9 +359,10 @@ export function calculateRiskMetrics(
/**
* Helper function to get Z-score for confidence level
* This implementation handles arbitrary confidence levels
*/
function getZScore(confidenceLevel: number): number {
// Approximate Z-scores for common confidence levels
// First check our lookup table for common values (more precise)
const zScores: { [key: string]: number } = {
'0.90': 1.282,
'0.95': 1.645,
@ -371,7 +372,17 @@ function getZScore(confidenceLevel: number): number {
};
const key = confidenceLevel.toString();
return zScores[key] || 1.645; // Default to 95% confidence
if (zScores[key]) return zScores[key];
// For arbitrary confidence levels, use approximation
if (confidenceLevel < 0.5) return -getZScore(1 - confidenceLevel);
if (confidenceLevel >= 0.999) return 3.09; // Cap at 99.9% for numerical stability
// Approximation of inverse normal CDF
const y = Math.sqrt(-2.0 * Math.log(1.0 - confidenceLevel));
return y - (2.515517 + 0.802853 * y + 0.010328 * y * y) /
(1.0 + 1.432788 * y + 0.189269 * y * y + 0.001308 * y * y * y);
}
/**

View file

@ -340,6 +340,38 @@ describe('Position Sizing Calculations', () => {
});
});
describe('sharpeOptimizedPositionSize', () => {
it('should calculate position size based on Sharpe optimization', () => {
const result = sharpeOptimizedPositionSize(100000, 0.15, 0.20, 0.02, 3);
// Kelly formula for continuous returns: f = (μ - r) / σ²
// Expected return: 0.15, Risk-free: 0.02, Volatility: 0.20
// f = (0.15 - 0.02) / (0.20)² = 0.13 / 0.04 = 3.25
// But capped at maxLeverage=3, so should be 3.0
// Final position: 100000 * 3 = 300000
expect(result).toBe(300000);
});
it('should return 0 for invalid inputs', () => {
// Invalid volatility
expect(sharpeOptimizedPositionSize(100000, 0.15, 0, 0.02)).toBe(0);
// Invalid account size
expect(sharpeOptimizedPositionSize(0, 0.15, 0.20, 0.02)).toBe(0);
// Expected return less than risk-free rate
expect(sharpeOptimizedPositionSize(100000, 0.01, 0.20, 0.02)).toBe(0);
});
it('should respect maximum leverage', () => {
const result = sharpeOptimizedPositionSize(100000, 0.30, 0.20, 0.02, 2);
// Kelly fraction would be (0.30 - 0.02) / (0.20)² = 7, but capped at 2
// Position: 100000 * 2 = 200000
expect(result).toBe(200000);
});
});
describe('validatePositionSize', () => {
it('should validate position size against limits', () => {
const result = validatePositionSize(500, 100, 100000, 10, 2);