// Get all possible states for a k length array function states(k) { if (k==1) return [0,1]; var ret = []; states(k-1).forEach(function(d) { ret.push([0].concat(d)); ret.push([1].concat(d)); }); return ret; } // Get all k-combinations from array function combinations(arr, k){ if (k==1) return arr; var ret = []; arr.forEach(function(d,i) { combinations(arr.slice(i+1, arr.length), k-1) .forEach(function(sub) { var next = [].concat(sub); next.unshift(d); ret.push( next ); }); }); return ret; } // Generate a LaTex fraction function frac(a,b) { if (!b || b == 1) return a; return '\\frac{'+(a || 1)+'}{'+b+'}'; } // Main calculation - callback to the JSONP request function main(input) { input = input.query.results.json; // Build rates matrix var rates = {}; Object.keys(input).forEach(function(key) { var numerator = key.slice(0,3), denominator = key.slice(4); if (!rates[numerator]) rates[numerator] = {}; rates[numerator][denominator] = input[key]; }); // List FX rates fx = Object.keys(rates); // Go through all single-pair arbs combinations(fx,2).forEach(function(d) { var ret = rates[d[0]][d[1]]*rates[d[1]][d[0]], first = frac(d[0],d[1])+' \\cdot '+frac(d[1],d[0]), second = rates[d[0]][d[1]]+'\\cdot '+rates[d[1]][d[0]]; // If return is below 1 the arb is in the inverse if (ret < 1) { first = frac(1,first); second = frac(1,second); ret = 1 / ret; } $("#dual").append('$$'+first+' = '+second+' = '+ret+ '$$'); }); // Go through all triangular combinations combinations(fx,3).forEach(function(c) { var best = {}; // Go through all possible selections of currency pairs for those three currencies states(3).forEach(function(states) { var d = {result: 1, above: [], below: []}; states.forEach(function(state,i) { var a = (state) ? c[i] : c[i+1] || c[0], b = (state) ? c[i+1] || c[0] : c[i], rate = rates[a][b]; if (state) d.above.push([a,b,rate]); else d.below.push([a,b,rate]); d.result *= state ? rate : 1/rate; }); // If the result is below 1 the arb is in the inverse if (d.result < 1) { var tmp = d.below; d.below = d.above; d.above = tmp; d.result = 1 / d.result; } // To eliminate duality-arb effect, we take the lowest triangular arb if (!best.result || d.result < best.result) best = d; }); $("#triangular").append('$$ '+frac( best.above.map(function(d) { return frac(d[0],d[1]);}).join(' \\cdot '), best.below.map(function(d) { return frac(d[0],d[1]);}).join(' \\cdot ') )+' = '+ frac( best.above.map(function(d) { return d[2];}).join(' \\cdot '), best.below.map(function(d) { return d[2];}).join(' \\cdot ') )+ ' = '+best.result +' $$' ); }); }