@railpath/finance-toolkit
Version:
Production-ready finance library for portfolio construction, risk analytics, quantitative metrics, and ML-based regime detection
52 lines (51 loc) • 2.34 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.calculateSortinoRatio = calculateSortinoRatio;
const SortinoRatioOptionsSchema_1 = require("../schemas/SortinoRatioOptionsSchema");
const SortinoRatioResultSchema_1 = require("../schemas/SortinoRatioResultSchema");
/**
* Calculate Sortino Ratio
*
* Sortino = (Mean Return - Target Return) / Downside Deviation
*
* Unlike Sharpe, only penalizes downside volatility (returns < target).
*
* @param options - Returns, target return, risk-free rate, annualization
* @returns Sortino Ratio and downside deviation metrics
*/
function calculateSortinoRatio(options) {
const { returns, riskFreeRate, targetReturn, annualizationFactor } = SortinoRatioOptionsSchema_1.SortinoRatioOptionsSchema.parse(options);
// Mean return
const meanReturn = returns.reduce((sum, r) => sum + r, 0) / returns.length;
// Annualized return
const annualizedReturn = meanReturn * annualizationFactor;
// Downside deviation (only returns below target)
const downsideReturns = returns.filter((r) => r < targetReturn);
if (downsideReturns.length === 0) {
// No downside → very high Sortino (cap at high value)
return SortinoRatioResultSchema_1.SortinoRatioResultSchema.parse({
sortinoRatio: 999999, // Use large number instead of Infinity
annualizedReturn,
downsideDeviation: 0,
annualizedDownsideDeviation: 0,
excessReturn: annualizedReturn - riskFreeRate,
});
}
const downsideVariance = downsideReturns.reduce((sum, r) => sum + Math.pow(r - targetReturn, 2), 0) / returns.length; // Divide by total returns, not just downside
const downsideDeviation = Math.sqrt(downsideVariance);
// Annualized downside deviation
const annualizedDownsideDeviation = downsideDeviation * Math.sqrt(annualizationFactor);
// Excess return
const excessReturn = annualizedReturn - riskFreeRate;
// Sortino Ratio
const sortinoRatio = annualizedDownsideDeviation !== 0
? excessReturn / annualizedDownsideDeviation
: 0;
return SortinoRatioResultSchema_1.SortinoRatioResultSchema.parse({
sortinoRatio,
annualizedReturn,
downsideDeviation,
annualizedDownsideDeviation,
excessReturn,
});
}