quantitivecalc
Version:
A TypeScript library providing advanced quantitative finance functions for risk analysis, performance metrics, and technical indicators. (Currently in development)
55 lines (54 loc) • 2.88 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.calculateDrawdown = calculateDrawdown;
/**
* Calculates the current drawdown for each point in a time series of values.
*
* The current drawdown represents the percentage decline from the previous peak at each point
* in time. This is particularly useful for creating underwater charts that visualize how far
* below the high-water mark an investment is at any given time. When a new peak is reached,
* the drawdown becomes 0.
*
* @param data - An array of objects representing the time series data.
* @param sourceColumn - The key in each object from which to read the numeric value for drawdown calculation.
* @param resultColumn - The key in which to store the calculated drawdown for each row (default: 'drawdown').
* @param asPercentage - Whether to return the drawdown as a percentage (default: true). If false, returns as decimal.
* @returns A new array of objects, each including the calculated current drawdown in the specified result column.
*
* @remarks
* - If the value in `sourceColumn` is not a valid number, the previous row's drawdown value is used.
* - Current drawdown is calculated as `(value - peak) / peak`, where `peak` is the highest value observed so far.
* - Drawdown values are always <= 0 (exactly 0 at peaks, negative during declines).
* - If the input data is empty or undefined, an empty array is returned.
* - The first valid data point is considered the initial peak with 0 drawdown.
* - This creates an "underwater chart" - the line stays at 0 at peaks and goes negative during drawdowns.
*/
function calculateDrawdown(data, sourceColumn, resultColumn = 'drawdown', asPercentage = true) {
if (!data || data.length === 0) {
return [];
}
// Create a copy of the data to avoid mutating the original
const result = data.map(row => ({ ...row }));
let peak = -Infinity;
for (let i = 0; i < result.length; i++) {
const value = result[i][sourceColumn];
if (typeof value === 'number' && !isNaN(value)) {
// Update peak if current value is higher
if (value > peak) {
peak = value;
}
// Calculate current drawdown from peak
// Should always be <= 0 (negative or zero at peak)
const currentDrawdown = peak > 0 ? (value - peak) / peak : 0;
// Convert to percentage if requested
const finalDrawdown = asPercentage ? currentDrawdown * 100 : currentDrawdown;
// Ensure drawdown is never positive (safety check)
result[i][resultColumn] = Math.min(0, finalDrawdown);
}
else {
// Invalid data - use previous value or 0 for first row
result[i][resultColumn] = i > 0 ? result[i - 1][resultColumn] : 0;
}
}
return result;
}