This commit is contained in:
Boki 2025-06-11 10:38:05 -04:00
parent 597c6efc9b
commit 8b5e06954a
26 changed files with 532 additions and 186 deletions

View file

@ -31,8 +31,12 @@ export function fixedRiskPositionSize(params: PositionSizeParams): number {
const { accountSize, riskPercentage, entryPrice, stopLoss, leverage = 1 } = params;
// Input validation
if (accountSize <= 0 || riskPercentage <= 0 || entryPrice <= 0 || leverage <= 0) {return 0;}
if (entryPrice === stopLoss) {return 0;}
if (accountSize <= 0 || riskPercentage <= 0 || entryPrice <= 0 || leverage <= 0) {
return 0;
}
if (entryPrice === stopLoss) {
return 0;
}
const riskAmount = accountSize * (riskPercentage / 100);
const riskPerShare = Math.abs(entryPrice - stopLoss);
@ -48,7 +52,9 @@ export function kellyPositionSize(params: KellyParams, accountSize: number): num
const { winRate, averageWin, averageLoss } = params;
// Validate inputs
if (averageLoss === 0 || winRate <= 0 || winRate >= 1 || averageWin <= 0) {return 0;}
if (averageLoss === 0 || winRate <= 0 || winRate >= 1 || averageWin <= 0) {
return 0;
}
const lossRate = 1 - winRate;
const winLossRatio = averageWin / Math.abs(averageLoss);
@ -72,7 +78,9 @@ export function fractionalKellyPositionSize(
fraction: number = 0.25
): number {
// Input validation
if (fraction <= 0 || fraction > 1) {return 0;}
if (fraction <= 0 || fraction > 1) {
return 0;
}
const fullKelly = kellyPositionSize(params, accountSize);
return fullKelly * fraction;
@ -88,7 +96,9 @@ export function volatilityTargetPositionSize(
const { price, volatility, targetVolatility } = params;
// Input validation
if (volatility <= 0 || price <= 0 || targetVolatility <= 0 || accountSize <= 0) {return 0;}
if (volatility <= 0 || price <= 0 || targetVolatility <= 0 || accountSize <= 0) {
return 0;
}
const volatilityRatio = targetVolatility / volatility;
const basePositionValue = accountSize * Math.min(volatilityRatio, 2); // Cap at 2x leverage
@ -105,7 +115,9 @@ export function equalWeightPositionSize(
price: number
): number {
// Input validation
if (numberOfPositions <= 0 || price <= 0 || accountSize <= 0) {return 0;}
if (numberOfPositions <= 0 || price <= 0 || accountSize <= 0) {
return 0;
}
const positionValue = accountSize / numberOfPositions;
return Math.floor(positionValue / price);
@ -121,7 +133,9 @@ export function atrBasedPositionSize(
atrMultiplier: number = 2,
price: number
): number {
if (atrValue === 0 || price === 0) {return 0;}
if (atrValue === 0 || price === 0) {
return 0;
}
const riskAmount = accountSize * (riskPercentage / 100);
const stopDistance = atrValue * atrMultiplier;
@ -142,12 +156,15 @@ export function expectancyPositionSize(
maxRiskPercentage: number = 2
): number {
// Input validation
if (accountSize <= 0 || winRate <= 0 || winRate >= 1 || averageWin <= 0 || averageLoss === 0)
{return 0;}
if (accountSize <= 0 || winRate <= 0 || winRate >= 1 || averageWin <= 0 || averageLoss === 0) {
return 0;
}
const expectancy = winRate * averageWin - (1 - winRate) * Math.abs(averageLoss);
if (expectancy <= 0) {return 0;}
if (expectancy <= 0) {
return 0;
}
// Scale position size based on expectancy relative to average loss
// Higher expectancy relative to risk allows for larger position
@ -167,7 +184,9 @@ export function monteCarloPositionSize(
simulations: number = 1000,
confidenceLevel: number = 0.95
): number {
if (historicalReturns.length === 0) {return 0;}
if (historicalReturns.length === 0) {
return 0;
}
const outcomes: number[] = [];
const mean = historicalReturns.reduce((sum, ret) => sum + ret, 0) / historicalReturns.length;
@ -229,8 +248,9 @@ export function sharpeOptimizedPositionSize(
maxLeverage: number = 3
): number {
// Input validation
if (volatility <= 0 || accountSize <= 0 || expectedReturn <= riskFreeRate || maxLeverage <= 0)
{return 0;}
if (volatility <= 0 || accountSize <= 0 || expectedReturn <= riskFreeRate || maxLeverage <= 0) {
return 0;
}
// Kelly criterion with Sharpe ratio optimization
const excessReturn = expectedReturn - riskFreeRate;
const kellyFraction = excessReturn / (volatility * volatility);
@ -251,7 +271,9 @@ export function fixedFractionalPositionSize(
price: number
): number {
// Input validation
if (stopLossPercentage <= 0 || price <= 0 || riskPercentage <= 0 || accountSize <= 0) {return 0;}
if (stopLossPercentage <= 0 || price <= 0 || riskPercentage <= 0 || accountSize <= 0) {
return 0;
}
const riskAmount = accountSize * (riskPercentage / 100);
const stopLossAmount = price * (stopLossPercentage / 100);
@ -269,7 +291,9 @@ export function volatilityAdjustedPositionSize(
price: number
): number {
// Input validation
if (assetVolatility <= 0 || price <= 0 || targetVolatility <= 0 || accountSize <= 0) {return 0;}
if (assetVolatility <= 0 || price <= 0 || targetVolatility <= 0 || accountSize <= 0) {
return 0;
}
const volatilityRatio = targetVolatility / assetVolatility;
const cappedRatio = Math.min(volatilityRatio, 3); // Cap at 3x leverage
@ -286,7 +310,9 @@ export function correlationAdjustedPositionSize(
existingPositions: Array<{ size: number; correlation: number }>,
maxCorrelationRisk: number = 0.3
): number {
if (existingPositions.length === 0 || basePositionSize <= 0) {return basePositionSize;}
if (existingPositions.length === 0 || basePositionSize <= 0) {
return basePositionSize;
}
// Calculate portfolio correlation risk
// This should consider the correlation between the new position and existing ones
@ -310,7 +336,9 @@ export function calculatePortfolioHeat(
accountSize: number
): number {
// Input validation
if (accountSize <= 0 || positions.length === 0) {return 0;}
if (accountSize <= 0 || positions.length === 0) {
return 0;
}
const totalRisk = positions.reduce((sum, position) => {
// Ensure risk values are positive
@ -331,8 +359,12 @@ export function dynamicPositionSize(
maxDrawdownThreshold: number = 0.1
): number {
// Input validation
if (basePositionSize <= 0 || marketVolatility <= 0 || normalVolatility <= 0) {return 0;}
if (drawdownLevel < 0 || maxDrawdownThreshold <= 0) {return basePositionSize;}
if (basePositionSize <= 0 || marketVolatility <= 0 || normalVolatility <= 0) {
return 0;
}
if (drawdownLevel < 0 || maxDrawdownThreshold <= 0) {
return basePositionSize;
}
// Volatility adjustment - reduce size when volatility is high
const volatilityAdjustment = Math.min(normalVolatility / marketVolatility, 2); // Cap at 2x
@ -354,7 +386,9 @@ export function liquidityConstrainedPositionSize(
maxVolumePercentage: number = 0.05,
price: number
): number {
if (averageDailyVolume === 0 || price === 0) {return 0;}
if (averageDailyVolume === 0 || price === 0) {
return 0;
}
const maxShares = averageDailyVolume * maxVolumePercentage;
@ -372,7 +406,9 @@ export function multiTimeframePositionSize(
baseRiskPercentage: number = 1
): number {
// Input validation
if (accountSize <= 0 || baseRiskPercentage <= 0) {return 0;}
if (accountSize <= 0 || baseRiskPercentage <= 0) {
return 0;
}
// Clamp signals to valid range
const clampedShort = Math.max(-1, Math.min(1, shortTermSignal));
@ -396,18 +432,26 @@ export function riskParityPositionSize(
targetRisk: number,
accountSize: number
): number[] {
if (assets.length === 0) {return [];}
if (assets.length === 0) {
return [];
}
// Calculate inverse volatility weights
const totalInverseVol = assets.reduce((sum, asset) => {
if (asset.volatility === 0) {return sum;}
if (asset.volatility === 0) {
return sum;
}
return sum + 1 / asset.volatility;
}, 0);
if (totalInverseVol === 0) {return assets.map(() => 0);}
if (totalInverseVol === 0) {
return assets.map(() => 0);
}
return assets.map(asset => {
if (asset.volatility === 0 || asset.price === 0) {return 0;}
if (asset.volatility === 0 || asset.price === 0) {
return 0;
}
// Calculate weight based on inverse volatility
const weight = 1 / asset.volatility / totalInverseVol;
@ -468,7 +512,9 @@ export function optimalFPositionSize(
historicalReturns: number[],
maxIterations: number = 100
): number {
if (historicalReturns.length === 0 || accountSize <= 0) {return 0;}
if (historicalReturns.length === 0 || accountSize <= 0) {
return 0;
}
// Convert returns to P&L per unit
const pnlValues = historicalReturns.map(ret => ret * 1000); // Assuming $1000 per unit
@ -512,7 +558,9 @@ export function secureFPositionSize(
historicalReturns: number[],
confidenceLevel: number = 0.95
): number {
if (historicalReturns.length === 0 || accountSize <= 0) {return 0;}
if (historicalReturns.length === 0 || accountSize <= 0) {
return 0;
}
// Sort returns to find worst-case scenarios
const sortedReturns = [...historicalReturns].sort((a, b) => a - b);
@ -523,7 +571,9 @@ export function secureFPositionSize(
const maxLoss = Math.abs(worstCaseReturn);
const maxRiskPercentage = 0.02; // Never risk more than 2% on worst case
if (maxLoss === 0) {return accountSize * 0.1;} // Default to 10% if no historical losses
if (maxLoss === 0) {
return accountSize * 0.1;
} // Default to 10% if no historical losses
const secureF = Math.min(maxRiskPercentage / maxLoss, 0.25); // Cap at 25%