quantitivecalc
Version:
A TypeScript library providing advanced quantitative finance functions for risk analysis, performance metrics, and technical indicators. (Currently in development)
64 lines (63 loc) • 3.19 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.calculateSortinoRatio = calculateSortinoRatio;
/**
* Calculates the Sortino Ratio for a rolling window over a dataset.
*
* The Sortino Ratio measures risk-adjusted return, penalizing only downside volatility.
* It is computed as: (Mean Return - Target Return) / Downside Deviation (annualized).
*
* @param data - Array of data objects containing return values.
* @param returnsColumn - The key in each data object representing the return value.
* @param resultColumn - The key to store the calculated Sortino Ratio in each result object.
* @param windowSize - Number of periods in the rolling window (default: 252).
* @param riskFreeRate - Annual risk-free rate used as the default target return (default: 0.02).
* @param targetReturn - Optional annual target return. If null, uses risk-free rate.
* @returns Array of data objects with the Sortino Ratio added in the specified result column.
*/
function calculateSortinoRatio(data, returnsColumn, resultColumn, windowSize = 252, riskFreeRate = 0.02, targetReturn = null) {
if (!data || data.length === 0) {
return [];
}
const result = data.map(row => ({ ...row }));
const dailyRiskFreeRate = riskFreeRate / 252;
const dailyTargetReturn = targetReturn ? targetReturn / 252 : dailyRiskFreeRate;
for (let i = 0; i < result.length; i++) {
if (i < windowSize - 1) {
result[i][resultColumn] = null;
}
else {
const windowReturns = [];
const downsideReturns = [];
// Collect returns and downside returns
for (let j = i - windowSize + 1; j <= i; j++) {
const returnValue = result[j][returnsColumn];
if (typeof returnValue === 'number' && !isNaN(returnValue)) {
windowReturns.push(returnValue);
// Only include returns below target for downside deviation
if (returnValue < dailyTargetReturn) {
downsideReturns.push(returnValue - dailyTargetReturn);
}
}
}
if (windowReturns.length > 1) {
// Calculate mean return
const meanReturn = windowReturns.reduce((sum, val) => sum + val, 0) / windowReturns.length;
// Calculate downside deviation
let downsideDeviation = 0;
if (downsideReturns.length > 0) {
const downsideVariance = downsideReturns.reduce((sum, val) => sum + val * val, 0) / windowReturns.length;
downsideDeviation = Math.sqrt(downsideVariance);
}
// Sortino Ratio = (Mean Return - Target Return) / Downside Deviation (annualized)
const excessReturn = meanReturn - dailyTargetReturn;
const sortinoRatio = downsideDeviation > 0 ? (excessReturn / downsideDeviation) * Math.sqrt(252) : 0;
result[i][resultColumn] = sortinoRatio;
}
else {
result[i][resultColumn] = null;
}
}
}
return result;
}