PortfolioAnalytics v0.0.4 (Changelog)
In order to track my personal stock market investments performances, as well as to analyse trading strategies on my blog Le Quant 40, I wanted to use portfolio performances measures computed in JavaScript.
Why in JavaScript ? Because I am a fan of Google Sheets, which is easily extensible thanks to Google Apps Script, a JavaScript-based language.
After several fruitless hours of Googling (incomplete codes, incorrect codes, undocumented codes...), I decided to create my own JavaScript library of such portfolio performances measures, hoping that it could be useful to other people...
- Compatible with Google Sheets
- Compatible with any browser supporting ECMAScript 5 (i.e., front-end development)
- Compatible with Node.js (i.e., back-end development)
- (Performances) Automatically uses JavaScript Typed Arrays
- (Accuracy) Internally uses accurate numerical algorithms (e.g., corrected two pass algorithms for mean, variance, skewness and kurtosis, accurate algorithm for error function...)
- Code continuously tested and integrated by Travis CI
- Code heavily documented using JSDoc
If you would like to use PortfolioAnalytics in Google Sheets, you can either:
- (Recommended) Import the external Google Apps Script library with Script ID 1NXwj16pdgcJT-XG5LiWRJyRW604Dj26U4lqgGsJJfOKLum4y9grakXPI into your spreadsheet script
or:
- Import the JavaScript files from the dist/gs directory into your spreadsheet script
In both cases, providing data to the PortfolioAnalytics functions is then accomplished your preferred way:
- Using a wrapper function in your spreadsheet script, directly accessible from your spreadsheet, to which you can provide a standard data range (A1:A99...), e.g.:
function computeUlcerIndexWrapper(iEquityCurveRange) {
// Convert the input range coming from the spreadsheet into an array
var aInternalArray = [];
for (var i=0; i<iEquityCurveRange.length; ++i) {
aInternalArray.push(iEquityCurveRange[i][0]);
}
// Compute the index
var ulcerIndex = PortfolioAnalytics.ulcerIndex(aInternalArray);
// Return it to the spreadsheet
return ulcerIndex;
}
- Using pure Google Apps Script functions - typically the getRange(...) familly of functions -, optimized for speed, e.g.:
function computeUlcerIndex() {
// Adapted from https://developers.google.com/apps-script/reference/spreadsheet/sheet#getrangerow-column-numrows
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var range = sheet.getRange(1, 1, 100); // A1:A100
var values = range.getValues();
// Convert the above range into an array
var aInternalArray = [];
for (var row in values) {
for (var col in values[row]) {
aInternalArray.push(values[row][col]);
}
}
// Compute the index
var ulcerIndex = PortfolioAnalytics.ulcerIndex(aInternalArray);
// Do something with it (use it in a computation, write it back to the spreadsheet, etc.)
...
}
You can find examples of PortfolioAnalytics usage in this spreadsheet.
If you would like to use PortfolioAnalytics inside a browser you can download its source code and/or its minified source code.
You then just need to include this code in an HTML page, e.g.:
<script src="portfolio_analytics.dist.min.js" type="text/javascript"></script>
To be noted that if the browser is compatible with JavaScript Typed Arrays, you can provide such arrays in input to PortfolioAnalytics for better performances, e.g.:
PortfolioAnalytics.arithmeticReturns(new Float64Array([100.0, 109.75, 111.25]))
// Will output a Float64Array
If you would like to use PortfolioAnalytics with Node.js, you simply need to declare it as a dependency of your project
in your package.json
file.
Then, this is standard Node.js code, e.g.:
var PortfolioAnalytics = require('portfolio-analytics');
...
var ui = PortfolioAnalytics.ulcerIndex([100, 110, 105, 102, 95]);
// ui == 0.07204222820421435
PortfolioAnalytics.maxDrawdown([1, 2, 1]);
// The maximum drawdown
PortfolioAnalytics.drawdownFunction([1, 2, 1]);
// The drawdown function
PortfolioAnalytics.topDrawdowns([1, 2, 1], 1);
// The top 'n' drawdowns (second largest drawdown, etc.) with their start/end indexes
PortfolioAnalytics.ulcerIndex([1, 2, 1]);
// The Ulcer Index
PortfolioAnalytics.painIndex([1, 2, 1]);
// The Pain Index, also corresponding to the average of the drawdown function
PortfolioAnalytics.conditionalDrawdown([100, 90, 80], 0.5);
// The conditional drawdown
PortfolioAnalytics.cumulativeReturn([1, 2, 1]);
// The cumulative return from first to last period
PortfolioAnalytics.cagr([1, 2, 1], [new Date("2015-12-31"), new Date("2016-12-31"), new Date("2017-12-31")]);
// The compound annual growth rate (CAGR) from first to last date
PortfolioAnalytics.arithmeticReturns([1, 2, 1]);
// The arithmetic returns for all periods
PortfolioAnalytics.valueAtRisk([1, 2, 1], 0.7);
// The (percent) value at risk
PortfolioAnalytics.sharpeRatio([100, 110, 105, 107.5, 115], [100, 100, 100, 100, 100]);
// The Sharpe ratio
PortfolioAnalytics.biasAdjustedSharpeRatio([100, 110, 105, 107.5, 115], [100, 100, 100, 100, 100]);
// The Sharpe ratio adjusted for its bias
PortfolioAnalytics.doubleSharpeRatio([100, 110, 105, 107.5, 115], [100, 100, 100, 100, 100]);
// The double Sharpe ratio (i.e., the Sharpe ratio, adjusted for its estimation risk)
PortfolioAnalytics.sharpeRatioConfidenceInterval([100, 110, 105, 107.5, 115], [100, 100, 100, 100, 100], 0.05);
// The confidence interval for the Sharpe ratio (here, at 5% significance level)
PortfolioAnalytics.probabilisticSharpeRatio([100, 110, 105, 107.5, 115], [100, 100, 100, 100, 100], 0);
// The probabilistic Sharpe ratio (i.e., the probability that the Sharpe ratio is greater
// than a reference Sharpe ratio, here 0)
PortfolioAnalytics.minimumTrackRecordLength([100, 110, 105, 107.5, 115], [100, 100, 100, 100, 100], 0.05, 0);
// The minimum track record length (i.e., the minimal length of the track record of the performance
// to have statistical confidence, here at 95%, that the Sharpe ratio is greater than a reference Sharpe ratio, here 0)
PortfolioAnalytics.gainToPainRatio([1, 2, 1]);
// The gain to pain ratio
Fork the projet from Github...
Instal the Grunt dependencies
npm install
- The following command generates the files to be used inside a browser or with Node.js in the
dist
directory:
grunt deliver
- The following command generates the files to be used in Google Sheets in the
dist\gs
directory:
grunt deliver-gs
Any of the following two commands run the QUnit unit tests contained in the test
directory on the generated file dist\portfolio_analytics.dev.min.js
:
npm test
grunt test