added calcs
This commit is contained in:
parent
ef12c9d308
commit
7886b7cfa5
10 changed files with 4331 additions and 0 deletions
346
libs/utils/src/calculations/position-sizing.ts
Normal file
346
libs/utils/src/calculations/position-sizing.ts
Normal file
|
|
@ -0,0 +1,346 @@
|
|||
/**
|
||||
* Position Sizing Calculations
|
||||
* Risk-based position sizing methods for trading strategies
|
||||
*/
|
||||
|
||||
export interface PositionSizeParams {
|
||||
accountSize: number;
|
||||
riskPercentage: number;
|
||||
entryPrice: number;
|
||||
stopLoss: number;
|
||||
leverage?: number;
|
||||
}
|
||||
|
||||
export interface KellyParams {
|
||||
winRate: number;
|
||||
averageWin: number;
|
||||
averageLoss: number;
|
||||
}
|
||||
|
||||
export interface VolatilityParams {
|
||||
price: number;
|
||||
volatility: number;
|
||||
targetVolatility: number;
|
||||
lookbackDays: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate position size based on fixed risk percentage
|
||||
*/
|
||||
export function fixedRiskPositionSize(params: PositionSizeParams): number {
|
||||
const { accountSize, riskPercentage, entryPrice, stopLoss, leverage = 1 } = params;
|
||||
|
||||
if (entryPrice === stopLoss) return 0;
|
||||
|
||||
const riskAmount = accountSize * (riskPercentage / 100);
|
||||
const riskPerShare = Math.abs(entryPrice - stopLoss);
|
||||
const basePositionSize = riskAmount / riskPerShare;
|
||||
|
||||
return basePositionSize * leverage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate position size using Kelly Criterion
|
||||
*/
|
||||
export function kellyPositionSize(params: KellyParams, accountSize: number): number {
|
||||
const { winRate, averageWin, averageLoss } = params;
|
||||
|
||||
if (averageLoss === 0 || winRate === 0 || winRate === 1) return 0;
|
||||
|
||||
const lossRate = 1 - winRate;
|
||||
const winLossRatio = averageWin / Math.abs(averageLoss);
|
||||
|
||||
// Kelly formula: f = (bp - q) / b
|
||||
// where: b = win/loss ratio, p = win rate, q = loss rate
|
||||
const kellyFraction = (winLossRatio * winRate - lossRate) / winLossRatio;
|
||||
|
||||
// Cap Kelly fraction to prevent over-leveraging
|
||||
const cappedKelly = Math.max(0, Math.min(kellyFraction, 0.25));
|
||||
|
||||
return accountSize * cappedKelly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate fractional Kelly position size (more conservative)
|
||||
*/
|
||||
export function fractionalKellyPositionSize(
|
||||
params: KellyParams,
|
||||
accountSize: number,
|
||||
fraction: number = 0.25
|
||||
): number {
|
||||
const fullKelly = kellyPositionSize(params, accountSize);
|
||||
return fullKelly * fraction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate position size based on volatility targeting
|
||||
*/
|
||||
export function volatilityTargetPositionSize(params: VolatilityParams, accountSize: number): number {
|
||||
const { price, volatility, targetVolatility } = params;
|
||||
|
||||
if (volatility === 0 || price === 0) return 0;
|
||||
|
||||
const volatilityRatio = targetVolatility / volatility;
|
||||
const basePositionValue = accountSize * volatilityRatio;
|
||||
|
||||
return basePositionValue / price;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate equal weight position size
|
||||
*/
|
||||
export function equalWeightPositionSize(
|
||||
accountSize: number,
|
||||
numberOfPositions: number,
|
||||
price: number
|
||||
): number {
|
||||
if (numberOfPositions === 0 || price === 0) return 0;
|
||||
|
||||
const positionValue = accountSize / numberOfPositions;
|
||||
return positionValue / price;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate position size based on Average True Range (ATR)
|
||||
*/
|
||||
export function atrBasedPositionSize(
|
||||
accountSize: number,
|
||||
riskPercentage: number,
|
||||
atrValue: number,
|
||||
atrMultiplier: number = 2,
|
||||
price: number
|
||||
): number {
|
||||
if (atrValue === 0 || price === 0) return 0;
|
||||
|
||||
const riskAmount = accountSize * (riskPercentage / 100);
|
||||
const stopDistance = atrValue * atrMultiplier;
|
||||
|
||||
return riskAmount / stopDistance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate position size using Van Tharp's expectancy
|
||||
*/
|
||||
export function expectancyPositionSize(
|
||||
accountSize: number,
|
||||
winRate: number,
|
||||
averageWin: number,
|
||||
averageLoss: number,
|
||||
maxRiskPercentage: number = 2
|
||||
): number {
|
||||
const expectancy = (winRate * averageWin) - ((1 - winRate) * Math.abs(averageLoss));
|
||||
|
||||
if (expectancy <= 0) return 0;
|
||||
|
||||
// Scale position size based on expectancy
|
||||
const expectancyRatio = expectancy / Math.abs(averageLoss);
|
||||
const riskPercentage = Math.min(expectancyRatio * 0.5, maxRiskPercentage);
|
||||
|
||||
return accountSize * (riskPercentage / 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate optimal position size using Monte Carlo simulation
|
||||
*/
|
||||
export function monteCarloPositionSize(
|
||||
accountSize: number,
|
||||
historicalReturns: number[],
|
||||
simulations: number = 1000,
|
||||
confidenceLevel: number = 0.95
|
||||
): number {
|
||||
if (historicalReturns.length === 0) return 0;
|
||||
|
||||
const outcomes: number[] = [];
|
||||
|
||||
for (let i = 0; i < simulations; i++) {
|
||||
let portfolioValue = accountSize;
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
outcomes.push(portfolioValue);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate position size based on Sharpe ratio optimization
|
||||
*/
|
||||
export function sharpeOptimizedPositionSize(
|
||||
accountSize: number,
|
||||
expectedReturn: number,
|
||||
volatility: number,
|
||||
riskFreeRate: number = 0.02,
|
||||
maxLeverage: number = 3
|
||||
): number {
|
||||
if (volatility === 0) return 0;
|
||||
|
||||
const excessReturn = expectedReturn - riskFreeRate;
|
||||
const sharpeRatio = excessReturn / volatility;
|
||||
|
||||
// Optimal leverage based on Sharpe ratio
|
||||
const optimalLeverage = Math.min(sharpeRatio / volatility, maxLeverage);
|
||||
|
||||
return accountSize * Math.max(0, optimalLeverage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate position size with correlation adjustment
|
||||
*/
|
||||
export function correlationAdjustedPositionSize(
|
||||
basePositionSize: number,
|
||||
existingPositions: Array<{ size: number; correlation: number }>,
|
||||
maxCorrelationRisk: number = 0.3
|
||||
): number {
|
||||
if (existingPositions.length === 0) return basePositionSize;
|
||||
|
||||
// Calculate total correlation risk
|
||||
const totalCorrelationRisk = existingPositions.reduce((total, position) => {
|
||||
return total + (position.size * Math.abs(position.correlation));
|
||||
}, 0);
|
||||
|
||||
// Adjust position size based on correlation risk
|
||||
const correlationAdjustment = Math.max(0, 1 - (totalCorrelationRisk / maxCorrelationRisk));
|
||||
|
||||
return basePositionSize * correlationAdjustment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate portfolio heat (total risk across all positions)
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamic position sizing based on market conditions
|
||||
*/
|
||||
export function dynamicPositionSize(
|
||||
basePositionSize: number,
|
||||
marketVolatility: number,
|
||||
normalVolatility: number,
|
||||
drawdownLevel: number,
|
||||
maxDrawdownThreshold: number = 0.1
|
||||
): number {
|
||||
// Volatility adjustment
|
||||
const volatilityAdjustment = normalVolatility / Math.max(marketVolatility, 0.01);
|
||||
|
||||
// Drawdown adjustment
|
||||
const drawdownAdjustment = Math.max(0.5, 1 - (drawdownLevel / maxDrawdownThreshold));
|
||||
|
||||
return basePositionSize * volatilityAdjustment * drawdownAdjustment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate maximum position size based on liquidity
|
||||
*/
|
||||
export function liquidityConstrainedPositionSize(
|
||||
desiredPositionSize: number,
|
||||
averageDailyVolume: number,
|
||||
maxVolumePercentage: number = 0.05,
|
||||
price: number
|
||||
): number {
|
||||
const maxShares = (averageDailyVolume * maxVolumePercentage);
|
||||
const maxPositionValue = maxShares * price;
|
||||
const desiredPositionValue = desiredPositionSize * price;
|
||||
|
||||
return Math.min(desiredPositionSize, maxPositionValue / price);
|
||||
}
|
||||
|
||||
/**
|
||||
* Multi-timeframe position sizing
|
||||
*/
|
||||
export function multiTimeframePositionSize(
|
||||
accountSize: number,
|
||||
shortTermSignal: number, // -1 to 1
|
||||
mediumTermSignal: number, // -1 to 1
|
||||
longTermSignal: number, // -1 to 1
|
||||
baseRiskPercentage: number = 1
|
||||
): number {
|
||||
// Weight the signals (long-term gets higher weight)
|
||||
const weightedSignal = (
|
||||
shortTermSignal * 0.2 +
|
||||
mediumTermSignal * 0.3 +
|
||||
longTermSignal * 0.5
|
||||
);
|
||||
|
||||
// Adjust risk based on signal strength
|
||||
const adjustedRisk = baseRiskPercentage * Math.abs(weightedSignal);
|
||||
|
||||
return accountSize * (adjustedRisk / 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* Risk parity position sizing
|
||||
*/
|
||||
export function riskParityPositionSize(
|
||||
assets: Array<{ volatility: number; price: number }>,
|
||||
targetRisk: number,
|
||||
accountSize: number
|
||||
): number[] {
|
||||
const totalInverseVol = assets.reduce((sum, asset) => sum + (1 / asset.volatility), 0);
|
||||
|
||||
return assets.map(asset => {
|
||||
const weight = (1 / asset.volatility) / totalInverseVol;
|
||||
const positionValue = accountSize * weight;
|
||||
return positionValue / asset.price;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate position size against risk limits
|
||||
*/
|
||||
export function validatePositionSize(
|
||||
positionSize: number,
|
||||
price: number,
|
||||
accountSize: number,
|
||||
maxPositionPercentage: number = 10,
|
||||
maxLeverage: number = 1
|
||||
): { isValid: boolean; adjustedSize: number; violations: string[] } {
|
||||
const violations: string[] = [];
|
||||
let adjustedSize = positionSize;
|
||||
|
||||
// Check maximum position percentage
|
||||
const positionValue = positionSize * price;
|
||||
const positionPercentage = (positionValue / accountSize) * 100;
|
||||
|
||||
if (positionPercentage > maxPositionPercentage) {
|
||||
violations.push(`Position exceeds maximum ${maxPositionPercentage}% of account`);
|
||||
adjustedSize = (accountSize * maxPositionPercentage / 100) / price;
|
||||
}
|
||||
|
||||
// Check leverage limits
|
||||
const leverage = positionValue / accountSize;
|
||||
if (leverage > maxLeverage) {
|
||||
violations.push(`Position exceeds maximum leverage of ${maxLeverage}x`);
|
||||
adjustedSize = Math.min(adjustedSize, (accountSize * maxLeverage) / price);
|
||||
}
|
||||
|
||||
// Check minimum position size
|
||||
if (adjustedSize < 1 && adjustedSize > 0) {
|
||||
violations.push('Position size too small (less than 1 share)');
|
||||
adjustedSize = 0;
|
||||
}
|
||||
|
||||
return {
|
||||
isValid: violations.length === 0,
|
||||
adjustedSize: Math.max(0, adjustedSize),
|
||||
violations
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue