work on calculations
This commit is contained in:
parent
3d910a13e0
commit
ab7ef2b678
20 changed files with 1343 additions and 222 deletions
|
|
@ -30,13 +30,15 @@ export interface VolatilityParams {
|
|||
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;
|
||||
|
||||
const riskAmount = accountSize * (riskPercentage / 100);
|
||||
const riskPerShare = Math.abs(entryPrice - stopLoss);
|
||||
const basePositionSize = riskAmount / riskPerShare;
|
||||
|
||||
return basePositionSize * leverage;
|
||||
return Math.floor(basePositionSize * leverage);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -45,17 +47,18 @@ export function fixedRiskPositionSize(params: PositionSizeParams): number {
|
|||
export function kellyPositionSize(params: KellyParams, accountSize: number): number {
|
||||
const { winRate, averageWin, averageLoss } = params;
|
||||
|
||||
if (averageLoss === 0 || winRate === 0 || winRate === 1) return 0;
|
||||
// Validate inputs
|
||||
if (averageLoss === 0 || winRate <= 0 || winRate >= 1 || averageWin <= 0) return 0;
|
||||
|
||||
const lossRate = 1 - winRate;
|
||||
const winLossRatio = averageWin / Math.abs(averageLoss);
|
||||
|
||||
// Kelly formula: f = (bp - q) / b
|
||||
// Correct Kelly formula: f = (bp - q) / b
|
||||
// where: b = win/loss ratio, p = win rate, q = loss rate
|
||||
const kellyFraction = (winLossRatio * winRate - lossRate) / winLossRatio;
|
||||
const kellyFraction = (winRate * winLossRatio - lossRate) / winLossRatio;
|
||||
|
||||
// Cap Kelly fraction to prevent over-leveraging
|
||||
const cappedKelly = Math.max(0, Math.min(kellyFraction, 0.25));
|
||||
// Cap Kelly fraction to prevent over-leveraging (max 25% of Kelly recommendation)
|
||||
const cappedKelly = Math.max(0, Math.min(kellyFraction * 0.25, 0.25));
|
||||
|
||||
return accountSize * cappedKelly;
|
||||
}
|
||||
|
|
@ -68,6 +71,9 @@ export function fractionalKellyPositionSize(
|
|||
accountSize: number,
|
||||
fraction: number = 0.25
|
||||
): number {
|
||||
// Input validation
|
||||
if (fraction <= 0 || fraction > 1) return 0;
|
||||
|
||||
const fullKelly = kellyPositionSize(params, accountSize);
|
||||
return fullKelly * fraction;
|
||||
}
|
||||
|
|
@ -78,12 +84,13 @@ export function fractionalKellyPositionSize(
|
|||
export function volatilityTargetPositionSize(params: VolatilityParams, accountSize: number): number {
|
||||
const { price, volatility, targetVolatility } = params;
|
||||
|
||||
if (volatility === 0 || price === 0) return 0;
|
||||
// Input validation
|
||||
if (volatility <= 0 || price <= 0 || targetVolatility <= 0 || accountSize <= 0) return 0;
|
||||
|
||||
const volatilityRatio = targetVolatility / volatility;
|
||||
const basePositionValue = accountSize * volatilityRatio;
|
||||
const basePositionValue = accountSize * Math.min(volatilityRatio, 2); // Cap at 2x leverage
|
||||
|
||||
return basePositionValue / price;
|
||||
return Math.floor(basePositionValue / price);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -94,10 +101,11 @@ export function equalWeightPositionSize(
|
|||
numberOfPositions: number,
|
||||
price: number
|
||||
): number {
|
||||
if (numberOfPositions === 0 || price === 0) return 0;
|
||||
// Input validation
|
||||
if (numberOfPositions <= 0 || price <= 0 || accountSize <= 0) return 0;
|
||||
|
||||
const positionValue = accountSize / numberOfPositions;
|
||||
return positionValue / price;
|
||||
return Math.floor(positionValue / price);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -114,8 +122,10 @@ export function atrBasedPositionSize(
|
|||
|
||||
const riskAmount = accountSize * (riskPercentage / 100);
|
||||
const stopDistance = atrValue * atrMultiplier;
|
||||
const positionSize = riskAmount / stopDistance;
|
||||
|
||||
return riskAmount / stopDistance;
|
||||
// Return position size in shares, not dollars
|
||||
return Math.floor(positionSize);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -128,15 +138,20 @@ export function expectancyPositionSize(
|
|||
averageLoss: number,
|
||||
maxRiskPercentage: number = 2
|
||||
): number {
|
||||
// Input validation
|
||||
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;
|
||||
|
||||
// Scale position size based on expectancy
|
||||
// Scale position size based on expectancy relative to average loss
|
||||
// Higher expectancy relative to risk allows for larger position
|
||||
const expectancyRatio = expectancy / Math.abs(averageLoss);
|
||||
const riskPercentage = Math.min(expectancyRatio * 0.5, maxRiskPercentage);
|
||||
|
||||
return accountSize * (riskPercentage / 100);
|
||||
const positionValue = accountSize * (riskPercentage / 100);
|
||||
return positionValue;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -151,28 +166,46 @@ export function monteCarloPositionSize(
|
|||
if (historicalReturns.length === 0) return 0;
|
||||
|
||||
const outcomes: number[] = [];
|
||||
const mean = historicalReturns.reduce((sum, ret) => sum + ret, 0) / historicalReturns.length;
|
||||
const variance = historicalReturns.reduce((sum, ret) => sum + Math.pow(ret - mean, 2), 0) / historicalReturns.length;
|
||||
const stdDev = Math.sqrt(variance);
|
||||
|
||||
for (let i = 0; i < simulations; i++) {
|
||||
let portfolioValue = accountSize;
|
||||
// Test different position sizes (as fraction of account)
|
||||
const testFractions = [0.01, 0.025, 0.05, 0.075, 0.1, 0.15, 0.2, 0.25];
|
||||
let optimalFraction = 0;
|
||||
let bestSharpe = -Infinity;
|
||||
|
||||
for (const fraction of testFractions) {
|
||||
const simOutcomes: number[] = [];
|
||||
|
||||
// Simulate a series of trades
|
||||
for (let j = 0; j < 252; j++) { // One year of trading days
|
||||
const randomReturn = historicalReturns[Math.floor(Math.random() * historicalReturns.length)];
|
||||
portfolioValue *= (1 + randomReturn);
|
||||
for (let i = 0; i < simulations; i++) {
|
||||
let portfolioValue = accountSize;
|
||||
|
||||
// Simulate trades over a period
|
||||
for (let j = 0; j < 50; j++) { // 50 trades
|
||||
const randomReturn = historicalReturns[Math.floor(Math.random() * historicalReturns.length)];
|
||||
const positionReturn = randomReturn * fraction;
|
||||
portfolioValue = portfolioValue * (1 + positionReturn);
|
||||
}
|
||||
|
||||
simOutcomes.push(portfolioValue);
|
||||
}
|
||||
|
||||
outcomes.push(portfolioValue);
|
||||
// Calculate Sharpe ratio for this fraction
|
||||
const avgOutcome = simOutcomes.reduce((sum, val) => sum + val, 0) / simOutcomes.length;
|
||||
const returns = simOutcomes.map(val => (val - accountSize) / accountSize);
|
||||
const avgReturn = returns.reduce((sum, ret) => sum + ret, 0) / returns.length;
|
||||
const returnStdDev = Math.sqrt(returns.reduce((sum, ret) => sum + Math.pow(ret - avgReturn, 2), 0) / returns.length);
|
||||
|
||||
const sharpe = returnStdDev > 0 ? avgReturn / returnStdDev : -Infinity;
|
||||
|
||||
if (sharpe > bestSharpe) {
|
||||
bestSharpe = sharpe;
|
||||
optimalFraction = fraction;
|
||||
}
|
||||
}
|
||||
|
||||
outcomes.sort((a, b) => a - b);
|
||||
const worstCaseIndex = Math.floor((1 - confidenceLevel) * outcomes.length);
|
||||
const worstCaseValue = outcomes[worstCaseIndex];
|
||||
|
||||
// Calculate safe position size based on worst-case scenario
|
||||
const maxLoss = accountSize - worstCaseValue;
|
||||
const safePositionRatio = Math.min(0.1, accountSize / (maxLoss * 10));
|
||||
|
||||
return accountSize * safePositionRatio;
|
||||
return accountSize * optimalFraction;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -185,15 +218,57 @@ export function sharpeOptimizedPositionSize(
|
|||
riskFreeRate: number = 0.02,
|
||||
maxLeverage: number = 3
|
||||
): number {
|
||||
if (volatility === 0) return 0;
|
||||
// Input validation
|
||||
if (volatility <= 0 || accountSize <= 0 || expectedReturn <= riskFreeRate || maxLeverage <= 0) return 0;
|
||||
|
||||
// Kelly criterion with Sharpe ratio optimization
|
||||
const excessReturn = expectedReturn - riskFreeRate;
|
||||
const sharpeRatio = excessReturn / volatility;
|
||||
const kellyFraction = excessReturn / (volatility * volatility);
|
||||
|
||||
// Optimal leverage based on Sharpe ratio
|
||||
const optimalLeverage = Math.min(sharpeRatio / volatility, maxLeverage);
|
||||
// Apply maximum leverage constraint and ensure reasonable bounds
|
||||
const constrainedFraction = Math.max(0, Math.min(kellyFraction, maxLeverage));
|
||||
|
||||
return accountSize * Math.max(0, optimalLeverage);
|
||||
// Further cap at 100% of account for safety
|
||||
const finalFraction = Math.min(constrainedFraction, 1);
|
||||
|
||||
return accountSize * finalFraction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixed fractional position sizing
|
||||
*/
|
||||
export function fixedFractionalPositionSize(
|
||||
accountSize: number,
|
||||
riskPercentage: number,
|
||||
stopLossPercentage: number,
|
||||
price: number
|
||||
): number {
|
||||
// Input validation
|
||||
if (stopLossPercentage <= 0 || price <= 0 || riskPercentage <= 0 || accountSize <= 0) return 0;
|
||||
|
||||
const riskAmount = accountSize * (riskPercentage / 100);
|
||||
const stopLossAmount = price * (stopLossPercentage / 100);
|
||||
|
||||
return Math.floor(riskAmount / stopLossAmount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Volatility-adjusted position sizing
|
||||
*/
|
||||
export function volatilityAdjustedPositionSize(
|
||||
accountSize: number,
|
||||
targetVolatility: number,
|
||||
assetVolatility: number,
|
||||
price: number
|
||||
): number {
|
||||
// Input validation
|
||||
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
|
||||
const positionValue = accountSize * cappedRatio;
|
||||
|
||||
return Math.floor(positionValue / price);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -204,17 +279,20 @@ export function correlationAdjustedPositionSize(
|
|||
existingPositions: Array<{ size: number; correlation: number }>,
|
||||
maxCorrelationRisk: number = 0.3
|
||||
): number {
|
||||
if (existingPositions.length === 0) return basePositionSize;
|
||||
if (existingPositions.length === 0 || basePositionSize <= 0) return basePositionSize;
|
||||
|
||||
// Calculate total correlation risk
|
||||
// Calculate portfolio correlation risk
|
||||
// This should consider the correlation between the new position and existing ones
|
||||
const totalCorrelationRisk = existingPositions.reduce((total, position) => {
|
||||
return total + (position.size * Math.abs(position.correlation));
|
||||
// Weight correlation by position size relative to new position
|
||||
const relativeSize = position.size / (basePositionSize + position.size);
|
||||
return total + (relativeSize * Math.abs(position.correlation));
|
||||
}, 0);
|
||||
|
||||
// Adjust position size based on correlation risk
|
||||
const correlationAdjustment = Math.max(0, 1 - (totalCorrelationRisk / maxCorrelationRisk));
|
||||
const correlationAdjustment = Math.max(0.1, 1 - (totalCorrelationRisk / maxCorrelationRisk));
|
||||
|
||||
return basePositionSize * correlationAdjustment;
|
||||
return Math.floor(basePositionSize * correlationAdjustment);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -224,8 +302,15 @@ export function calculatePortfolioHeat(
|
|||
positions: Array<{ value: number; risk: number }>,
|
||||
accountSize: number
|
||||
): number {
|
||||
const totalRisk = positions.reduce((sum, position) => sum + position.risk, 0);
|
||||
return (totalRisk / accountSize) * 100;
|
||||
// Input validation
|
||||
if (accountSize <= 0 || positions.length === 0) return 0;
|
||||
|
||||
const totalRisk = positions.reduce((sum, position) => {
|
||||
// Ensure risk values are positive
|
||||
return sum + Math.max(0, position.risk);
|
||||
}, 0);
|
||||
|
||||
return Math.min((totalRisk / accountSize) * 100, 100); // Cap at 100%
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -238,13 +323,19 @@ export function dynamicPositionSize(
|
|||
drawdownLevel: number,
|
||||
maxDrawdownThreshold: number = 0.1
|
||||
): number {
|
||||
// Volatility adjustment
|
||||
const volatilityAdjustment = normalVolatility / Math.max(marketVolatility, 0.01);
|
||||
// Input validation
|
||||
if (basePositionSize <= 0 || marketVolatility <= 0 || normalVolatility <= 0) return 0;
|
||||
if (drawdownLevel < 0 || maxDrawdownThreshold <= 0) return basePositionSize;
|
||||
|
||||
// Drawdown adjustment
|
||||
const drawdownAdjustment = Math.max(0.5, 1 - (drawdownLevel / maxDrawdownThreshold));
|
||||
// Volatility adjustment - reduce size when volatility is high
|
||||
const volatilityAdjustment = Math.min(normalVolatility / marketVolatility, 2); // Cap at 2x
|
||||
|
||||
return basePositionSize * volatilityAdjustment * drawdownAdjustment;
|
||||
// Drawdown adjustment - reduce size as drawdown increases
|
||||
const normalizedDrawdown = Math.min(drawdownLevel / maxDrawdownThreshold, 1);
|
||||
const drawdownAdjustment = Math.max(0.1, 1 - normalizedDrawdown);
|
||||
|
||||
const adjustedSize = basePositionSize * volatilityAdjustment * drawdownAdjustment;
|
||||
return Math.floor(Math.max(0, adjustedSize));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -256,11 +347,11 @@ export function liquidityConstrainedPositionSize(
|
|||
maxVolumePercentage: number = 0.05,
|
||||
price: number
|
||||
): number {
|
||||
const maxShares = (averageDailyVolume * maxVolumePercentage);
|
||||
const maxPositionValue = maxShares * price;
|
||||
const desiredPositionValue = desiredPositionSize * price;
|
||||
if (averageDailyVolume === 0 || price === 0) return 0;
|
||||
|
||||
return Math.min(desiredPositionSize, maxPositionValue / price);
|
||||
const maxShares = averageDailyVolume * maxVolumePercentage;
|
||||
|
||||
return Math.min(desiredPositionSize, maxShares);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -273,11 +364,19 @@ export function multiTimeframePositionSize(
|
|||
longTermSignal: number, // -1 to 1
|
||||
baseRiskPercentage: number = 1
|
||||
): number {
|
||||
// Input validation
|
||||
if (accountSize <= 0 || baseRiskPercentage <= 0) return 0;
|
||||
|
||||
// Clamp signals to valid range
|
||||
const clampedShort = Math.max(-1, Math.min(1, shortTermSignal));
|
||||
const clampedMedium = Math.max(-1, Math.min(1, mediumTermSignal));
|
||||
const clampedLong = Math.max(-1, Math.min(1, longTermSignal));
|
||||
|
||||
// Weight the signals (long-term gets higher weight)
|
||||
const weightedSignal = (
|
||||
shortTermSignal * 0.2 +
|
||||
mediumTermSignal * 0.3 +
|
||||
longTermSignal * 0.5
|
||||
clampedShort * 0.2 +
|
||||
clampedMedium * 0.3 +
|
||||
clampedLong * 0.5
|
||||
);
|
||||
|
||||
// Adjust risk based on signal strength
|
||||
|
|
@ -294,12 +393,27 @@ export function riskParityPositionSize(
|
|||
targetRisk: number,
|
||||
accountSize: number
|
||||
): number[] {
|
||||
const totalInverseVol = assets.reduce((sum, asset) => sum + (1 / asset.volatility), 0);
|
||||
if (assets.length === 0) return [];
|
||||
|
||||
// Calculate inverse volatility weights
|
||||
const totalInverseVol = assets.reduce((sum, asset) => {
|
||||
if (asset.volatility === 0) return sum;
|
||||
return sum + (1 / asset.volatility);
|
||||
}, 0);
|
||||
|
||||
if (totalInverseVol === 0) return assets.map(() => 0);
|
||||
|
||||
return assets.map(asset => {
|
||||
if (asset.volatility === 0 || asset.price === 0) return 0;
|
||||
|
||||
// Calculate weight based on inverse volatility
|
||||
const weight = (1 / asset.volatility) / totalInverseVol;
|
||||
const positionValue = accountSize * weight;
|
||||
return positionValue / asset.price;
|
||||
|
||||
// Scale by target risk
|
||||
const riskAdjustedWeight = weight * (targetRisk / asset.volatility);
|
||||
|
||||
const positionValue = accountSize * riskAdjustedWeight;
|
||||
return Math.floor(positionValue / asset.price);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue