/** * Makes an XMLHttpRequest object to get data from url and execute callback. * * @param {string} theUrl * URL to do a GET request too. * @param {object} callback * Function to execute once data arrives. * * @return * An XMLHttpRequest which user can call send() on to run ajax call. */ function makeHttpGetReq(theUrl, callback) { var xmlHttp = new XMLHttpRequest(); xmlHttp.open( "GET", theUrl, true ); // false for synchronous request xmlHttp.onreadystatechange = callback; //xmlHttp.setRequestHeader("Access-Control-Allow-Origin", "*"); return xmlHttp; } /** * Makes an XMLHttpRequest object to get data from IEX * * @param {mainInfo} mainInfo * The main info for the app as produced by makeMainInfo. * * @return * An XMLHttpRequest which user can call send() on to run ajax call to * get the data and then run handleIEXDataRequest. */ function makeGetIEXReq(mainInfo) { var baseURL = "https://api.iextrading.com/1.0/stock/"; var fullURL = baseURL + mainInfo['ticker'] + "/chart/5y"; var req = makeHttpGetReq(fullURL, handleIEXDataRequest); return req; } /** * Makes an XMLHttpRequest object to get data from pubdata * * @param {mainInfo} mainInfo * The main info for the app as produced by makeMainInfo. * * @return * An XMLHttpRequest which user can call send() on to run ajax call to * get the data and then run handlePubDataRequest. */ function makeGetPubData(mainInfo) { var baseURL = "https://aocks.github.io/pubdata/data/stocks/" var fullURL = baseURL + mainInfo['ticker'].charAt(0) + '/' + mainInfo[ 'ticker'] + ".json"; var req = makeHttpGetReq(fullURL, handlePubDataRequest); return req; } /** * Callback meant to handle processing response from IEX. * * This is a helper function meant to be used as a callback. When called * it checks readyState for this and tries to process IEX data. * Finally, it calls checkCallbacks to keep processing further callbacks. * **/ function handleIEXDataRequest() { if (this.readyState == 4) { var commentary = 'Problem occured handling IEX data'; if (this.status == 200) { debugLog(this.mainInfo, 'in handleIEXDataRequest status 200:\n' + this); this.mainInfo['iexData'] = JSON.parse(this.responseText); commentary = 'Got response of IEX data with status ' + ( this.status); } else { debugLog(this.mainInfo, 'in handleIEXDataRequest status ' + this.status + '\nthis: ' + this); commentary += ': status = ' + this.status; } this.mainInfo['notes'].push(commentary); checkCallbacks(this.mainInfo); } } /** * Callback meant to handle processing response from pubData. * * This is a helper function meant to be used as a callback. When called * it checks readyState for this and tries to process pubData results. * Finally, it calls checkCallbacks to keep processing further callbacks. * **/ function handlePubDataRequest() { this.mainInfo['pubData'] = []; if (this.readyState == 4) { if (this.status == 200) { console.log('in handlePubDataRequest status 200'); console.log(this); this.mainInfo['pubData'] = JSON.parse(this.responseText); this.mainInfo['notes'].push('Also found pubData for ' + this.mainInfo['ticker']); } else { this.mainInfo['notes'].push('No extra pubData for ' + this.mainInfo['ticker']); console.log('in handlePubDataRequest status ' + this.status); console.log(this); } checkCallbacks(this.mainInfo); } } /** * Make and return dictionary with main information for analysis. * **/ function makeMainInfo() { var mainInfo = { }; resetMainInfo(mainInfo); return mainInfo; } function resetMainInfo(mainInfo) { mainInfo['notes'] = []; // Array of string notes about calcualtions mainInfo['debug_log'] = 1; // If > 0, log debug messages to console mainInfo['results'] = []; // Array for results of computation mainInfo['callbacks'] = []; // Array of functions to call to do analysis } /** * Logs message to console if debug logging is turned on. * * @param{mainInfo} mainInfo * Object created by makeMainInfo. * @param{string} msg * Message to log. * **/ function debugLog(mainInfo, msg) { if (mainInfo['debug_log'] > 0) { console.log('debug: ' + msg); } } /** * Check and execute any callbacks in mainInfo. * * This function pulls the next callable out of mainInfo['callbacks'] * and executes it. Useful to chain things together. * * @param{mainInfo} mainInfo * Object created by makeMainInfo. * **/ function checkCallbacks(mainInfo) { if (mainInfo['callbacks'].length > 0) { var next_cb = mainInfo['callbacks'].shift(); debugLog(mainInfo, 'checkCallbacks --> ' + next_cb.toString()); next_cb(); } } function processData(output_id, ticker, mainInfo, dataFilters) { console.log('filters are: '); console.log(dataFilters); resetMainInfo(mainInfo); if (ticker == null) { mainInfo['ticker'] = document.getElementById('ticker_field').value; } var pubReq = makeGetPubData(mainInfo); pubReq.mainInfo = mainInfo; /* FIXME: iex busted var req = makeGetIEXReq(mainInfo); req.mainInfo = mainInfo; mainInfo['callbacks'].push(function(){req.send();}); */ mainInfo['callbacks'].push(function(){pubReq.send();}); mainInfo['callbacks'].push(function(){combineData(mainInfo)}); mainInfo['callbacks'].push(function(){applyFilters( mainInfo, dataFilters)}); mainInfo['callbacks'].push(function(){showResult(mainInfo)}); checkCallbacks(mainInfo); } /** * Try to splcie helper data onto main if possible. * * @param{array} main * Array of dictionaries repersenting main data. * @param{array} helper * Array of dictionaries representing helper data. * @param{string} matchField * Which field in main/helper to try and use to verify match. * @param{string} spliceName * Name of helper data we are trying to splice onto main. * @param{array} notes * We append notes about splice to the notes array. * * @return * Array of combined data. * * This function tries find a point in helper which matches * the *FIRST* date in main. If that is possible and if * main[matchField] is close to helper[matchField], then we * splice main onto helper (assuming helper has older historical data). * **/ function maybeSplice(main, helper, matchField, spliceName, notes) { var mainVal = main[0][matchField]; var spliceIdx = _.findIndex(helper, function(item) { var comp = (item['date'] == main[0]['date']); return comp; }); if (spliceIdx == -1) { notes.push('Did not splice data from ' + spliceName); return main; } var helpVal = helper[spliceIdx][matchField]; var diff = Math.abs(mainVal - helpVal); if (diff > 0.01) { diff = diff / Math.abs(mainVal + helpVal); } if (diff < .01) { notes.push('Splice data from ' + spliceName + ' at ' + helper[spliceIdx]['date']); console.log('Splicing at index ' + spliceIdx); return helper.slice(0, spliceIdx).concat(main); } else { notes.push('Data difference from ' + spliceName + ' on ' + helper[spliceIdx]['date'] + ' too large so no splice'); notes.push(spliceName + ': ' + helpVal + ' vs ' + mainVal); return main; } } /** * Combine data from multiple sources. * * @param{mainInfo} mainInfo * Object created by makeMainInfo. * * This function goes through various data sources starting from the one * with latest data going back to the one with the oldest data and tries * to splice them together. The result is put into mainInfo['data']. * This is helpful since some data sources like IEX have very up to date * recent data while others may have long history but don't get updated * as often. * **/ function combineData(mainInfo) { var data = []; var sources = ['pubData']; for (i=0; i