├── .gitignore ├── Strategies ├── 6 year mom pp.js ├── accent.js ├── angry mosquito.js ├── correlation.js ├── dash-of-vol-strategy.js ├── drop the mic w different horizons overlaid.js ├── hurst.0.1.js ├── hurst.js ├── investable vix.js ├── irx mo.js ├── jakarta hurst.js ├── logistic map.js ├── meh-momentum.js ├── mojito.js ├── mom autoc.js ├── momentum-pp.js ├── momentumpp.js ├── momersion lbt twist.js ├── momersion.js ├── oz real steady vol.js ├── pca.js ├── realisedVol.js ├── simplest mean reversion.js ├── simplest momentum strategy.js ├── slmo.js ├── smooth mojito.js ├── steady low vol.js ├── steady vol sp500.js └── stock correlation.js ├── addMarketDataWorker.js ├── clean ├── calendar.js ├── clean.html ├── d3.v3.min.js ├── lodash.min.js ├── moment.min.js ├── quandl-api-key.js └── testDrop.csv ├── formatWorker.js ├── graph.css ├── index.html └── lib ├── codemirror.css ├── codemirror.js ├── d3.min.js ├── dataHelpers.js ├── graphs.js ├── javascript.js ├── jstat.min.js ├── lazy.js ├── lodash.min.js ├── networking2.js ├── numeric-1.2.6.min.js ├── strategyHelpers.js └── utility.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled source # 2 | ################### 3 | *.com 4 | *.class 5 | *.dll 6 | *.exe 7 | *.o 8 | *.so 9 | 10 | # Packages # 11 | ############ 12 | # it's better to unpack these files and commit the raw source 13 | # git has its own built in compression methods 14 | *.7z 15 | *.dmg 16 | *.gz 17 | *.iso 18 | *.jar 19 | *.rar 20 | *.tar 21 | *.zip 22 | 23 | # Logs and databases # 24 | ###################### 25 | *.log 26 | *.sql 27 | *.sqlite 28 | 29 | # OS generated files # 30 | ###################### 31 | .DS_Store 32 | .DS_Store? 33 | ._* 34 | .Spotlight-V100 35 | .Trashes 36 | ehthumbs.db 37 | Thumbs.db 38 | 39 | *~ 40 | <<<<<<< HEAD 41 | ======= 42 | 43 | lib/listeners.js~ 44 | 45 | >>>>>>> addf4672b6b714b22df7b38bbfc9d3c3986500a1 46 | *.js~ 47 | -------------------------------------------------------------------------------- /Strategies/6 year mom pp.js: -------------------------------------------------------------------------------- 1 | var m = data.SPCPMTUP.weekly().lookback(53).returns(); 2 | 3 | var mean = jStat.mean(m); 4 | var sd = jStat.stdev(m,true); 5 | 6 | //if 'large' previous return 7 | m[0] >= sd+mean || m[0] <= -sd ? 8 | //then 'bet' on mean reversion 9 | //retain positive bias 10 | weight.SPCPMTUP = -m[0] 11 | : 12 | //else invest a 'constant relevant' amount 13 | weight.SPCPMTUP = 0; 14 | -------------------------------------------------------------------------------- /Strategies/accent.js: -------------------------------------------------------------------------------- 1 | //AMEX_SPY 2 | //MMM,AXP,AAPL,BA,CAT,CVX,CSCO,KO,DD,XOM,GE,GS,HD,INTC,IBM,JPM,JNJ,MCD,MRK,MSFT,NKE,PFE,PG,TRV,UTX,UNH,VZ,V,WMT,DIS 3 | 4 | weight.AMEX_SPY = 0; 5 | var is = data.AMEX_SPY.lookback(21).returns(); 6 | var var_i = jStat(is).variance(true); 7 | 8 | Object 9 | .keys(data) 10 | .filter( 11 | function(k){ 12 | return k != 'AMEX_SPY'; 13 | } 14 | ) 15 | .forEach( 16 | function(k){ 17 | var rs = data[k].lookback(21).returns(); 18 | var vol = jStat(rs).stdev(true); 19 | var cov = jStat.covariance(rs,is); 20 | var b = cov / var_i; 21 | weight[k] = 1/vol; 22 | weight.AMEX_SPY = weight.AMEX_SPY - b * weight[k]; 23 | } 24 | ); 25 | -------------------------------------------------------------------------------- /Strategies/angry mosquito.js: -------------------------------------------------------------------------------- 1 | var vxv = data.VXV.daily().lookback(1).prices()[0]; 2 | var vix = data.INDEX_VIX.daily().lookback(1).prices()[0]; 3 | 4 | var ivts = vix/vxv; 5 | 6 | if(ivts < 0.91){ 7 | weight.VXX = -0.7; 8 | weight.NYSEARCA_VXZ = 0.3; 9 | } 10 | else if (ivts < 0.94){ 11 | weight.VXX = -0.32; 12 | weight.NYSEARCA_VXZ = 0.68; 13 | } 14 | else if (ivts < 0.97){ 15 | weight.VXX = -0.32; 16 | weight.NYSEARCA_VXZ =0.68; 17 | } 18 | else if (ivts < 1.005){ 19 | weight.VXX = -0.28; 20 | weight.NYSEARCA_VXZ = 0.72; 21 | } 22 | else { 23 | weight.VXX = 0; 24 | weight.NYSEARCA_VXZ = 1; 25 | } 26 | 27 | weight.INDEX_GSPC = weight.NYSEARCA_VXZ+weight.VXX; 28 | 29 | //13 week t-bill yield index used to calculate Sharpe ratio. Scaled for a daily horizon 30 | weight.INDEX_IRX = -(weight.INDEX_GSPC+weight.VXX+weight.NYSEARCA_VXZ)/(5*13); 31 | 32 | -------------------------------------------------------------------------------- /Strategies/correlation.js: -------------------------------------------------------------------------------- 1 | var sp = data.INDEX_GSPC.daily().lookback(500).returns(); 2 | 3 | var corr = jStat.corrcoeff(Lazy(sp).rest().toArray(),Lazy(sp).initial().toArray()); 4 | 5 | var flag = null; 6 | 7 | sp[0] < 0 ? 8 | flag = -1 9 | : 10 | flag = 1; 11 | ; 12 | 13 | weight.INDEX_GSPC = flag * corr; 14 | 15 | //13 week t-bill yield index used to calculate Sharpe ratio. Scaled for a monthly horizon 16 | weight.INDEX_IRX = -weight.INDEX_GSPC*(4/13); 17 | -------------------------------------------------------------------------------- /Strategies/dash-of-vol-strategy.js: -------------------------------------------------------------------------------- 1 | var realisedVol = jStat( data.INDEX_GSPC.daily().lookback(5).returns() ).stdev(true)*100*Math.sqrt(252); 2 | 3 | weight.INDEX_VIX = 1/realisedVol; 4 | 5 | weight.INDEX_GSPC = 1; 6 | 7 | //13 week t-bill yield index used to calculate Sharpe ratio. Scaled for a monthly horizon 8 | weight.INDEX_IRX = -(weight.INDEX_GSPC+weight.INDEX_VIX)*(4/13); 9 | -------------------------------------------------------------------------------- /Strategies/drop the mic w different horizons overlaid.js: -------------------------------------------------------------------------------- 1 | //Two years of history 2 | var d = data.INDEX_GSPC.daily().lookback(500).returns(); 3 | var w = data.INDEX_GSPC.weekly().lookback(100).returns(); 4 | var m = data.INDEX_GSPC.monthly().lookback(25).returns(); 5 | var q = data.INDEX_GSPC.quarterly().lookback(9).returns(); 6 | 7 | //Correlations 8 | var cd = jStat.corrcoeff( Lazy(d).initial().toArray(), Lazy(d).rest().toArray() ); 9 | var cw = jStat.corrcoeff( Lazy(w).initial().toArray(), Lazy(w).rest().toArray() ); 10 | var cm = jStat.corrcoeff( Lazy(m).initial().toArray(), Lazy(m).rest().toArray() ); 11 | var cq = jStat.corrcoeff( Lazy(q).initial().toArray(), Lazy(q).rest().toArray() ); 12 | 13 | //Results 14 | var result = cd * d[0] / Math.abs(d[0] + 0.00000000001); 15 | result = result + cw * w[0] / Math.abs(w[0] + 0.00000000001)/5; 16 | result = result + cm * m[0] / Math.abs(m[0] + 0.00000000001)/21; 17 | result = result + cq * q[0] / Math.abs(q[0] + 0.00000000001)/63; 18 | 19 | weight.INDEX_GSPC = result; -------------------------------------------------------------------------------- /Strategies/hurst.0.1.js: -------------------------------------------------------------------------------- 1 | var lb = 300; 2 | 3 | var v = data.INDEX_GSPC.daily().lookback(lb+1).returns(); 4 | 5 | var xx = Lazy 6 | .range(1,Math.floor(Math.log2(lb/3))+1) 7 | .map( 8 | function(i){ 9 | return Math.pow(2,i); 10 | } 11 | ) 12 | ; 13 | 14 | var yy = xx 15 | .map( 16 | function(n){ 17 | var rs = Lazy(v) 18 | .chunk(Math.floor(lb/n)) 19 | .filter( 20 | function(i){ 21 | return i.length >= Math.floor(lb/n); 22 | } 23 | ) 24 | .map( 25 | function(t){ 26 | var y = numeric.subVS(t,jStat.mean(t)); 27 | var s = jStat.stdev(y,true); 28 | var z = y.reduce( 29 | function(a,t){ 30 | if (a.length === 0) { 31 | return [t]; 32 | } 33 | else { 34 | return a.concat(t+a[a.length-1]); 35 | } 36 | } 37 | , [] 38 | ); 39 | var r = Lazy(z).max() - Lazy(z).min(); 40 | return r/s; 41 | } 42 | ) 43 | .toArray(); 44 | return Math.log(jStat.mean(rs)); 45 | } 46 | ) 47 | .toArray() 48 | ; 49 | 50 | var ln_xx = xx.map( 51 | function(i){ 52 | return Math.log(i); 53 | } 54 | ) 55 | .toArray(); 56 | 57 | var h = 58 | jStat.covariance( 59 | yy, 60 | ln_xx 61 | ) 62 | / 63 | jStat.variance( 64 | ln_xx,true 65 | ); 66 | 67 | var signal = (Math.abs(h)-0.5)/0.5; 68 | 69 | //var r2 = Math.pow(jStat.corrcoeff(yy,ln_xx),2); 70 | 71 | var flag = 0; 72 | 73 | v[0] < 0 ? flag = -1 : flag = 1; 74 | 75 | weight.INDEX_GSPC = signal * flag; 76 | -------------------------------------------------------------------------------- /Strategies/hurst.js: -------------------------------------------------------------------------------- 1 | var lb = 300; 2 | 3 | var v = data.INDEX_GSPC.daily().lookback(lb+1).returns(); 4 | 5 | //find max poss range per lookback size 6 | //ensure bulletproofness 7 | var xx = Lazy.range(2,12); 8 | var yy = xx 9 | .map( 10 | function(n){ 11 | var rs = Lazy(v) 12 | .chunk(lb/n) 13 | .map( 14 | function(t){ 15 | var y = numeric.subVS(t,jStat.mean(t)); 16 | var s = jStat.stdev(y,true); 17 | var z = y.reduce( 18 | function(a,t){ 19 | if (a.length === 0) { 20 | return [t]; 21 | } 22 | else { 23 | return a.concat(t+a[a.length-1]); 24 | } 25 | } 26 | , [] 27 | ); 28 | var r = Lazy(z).max() - Lazy(z).min(); 29 | return r/s; 30 | } 31 | ) 32 | .toArray(); 33 | return Math.log(jStat.mean(rs)); 34 | } 35 | ) 36 | .toArray() 37 | ; 38 | 39 | var ln_xx = xx.map( 40 | function(i){ 41 | return Math.log(i); 42 | } 43 | ) 44 | .toArray(); 45 | 46 | var h = 47 | jStat.covariance( 48 | yy, 49 | ln_xx 50 | ) 51 | / 52 | jStat.variance( 53 | ln_xx 54 | ); 55 | 56 | //h.print(); 57 | 58 | var signal = (Math.abs(h)-0.5)/0.5; 59 | 60 | //var r2 = Math.pow(jStat.corrcoeff(yy,ln_xx),2); 61 | 62 | var flag = 0; 63 | 64 | v[0] < 0 ? flag = -1 : flag = 1; 65 | 66 | weight.INDEX_GSPC = signal * flag 67 | 68 | -------------------------------------------------------------------------------- /Strategies/investable vix.js: -------------------------------------------------------------------------------- 1 | var v = data.SPVXTSTR.daily().lookback(1).prices()[0]; 2 | weight.SPVXTSTR = 360000/v; 3 | weight.INDEX_GSPC = 1; 4 | weight.INDEX_IRX = -(weight.INDEX_GSPC+weight.SPVXTSTR)*(4/13); -------------------------------------------------------------------------------- /Strategies/irx mo.js: -------------------------------------------------------------------------------- 1 | var i = data.INDEX_IRX.daily().lookback(2).returns()[0]; 2 | weight.INDEX_IRX = Math.abs(i)/(i+0.00000000001); -------------------------------------------------------------------------------- /Strategies/jakarta hurst.js: -------------------------------------------------------------------------------- 1 | var lb = 30; 2 | 3 | var v = data.INDEX_JKSE.daily().lookback(lb+1).returns(); 4 | 5 | var xx = Lazy 6 | .range(1,Math.floor(Math.log2(lb/3))+1) 7 | .map( 8 | function(i){ 9 | return Math.pow(2,i); 10 | } 11 | ) 12 | ; 13 | 14 | var yy = xx 15 | .map( 16 | function(n){ 17 | var rs = Lazy(v) 18 | .chunk(Math.floor(lb/n)) 19 | .filter( 20 | function(i){ 21 | return i.length >= Math.floor(lb/n); 22 | } 23 | ) 24 | .map( 25 | function(t){ 26 | var y = numeric.subVS(t,jStat.mean(t)); 27 | var s = jStat.stdev(y,true); 28 | var z = y.reduce( 29 | function(a,t){ 30 | if (a.length === 0) { 31 | return [t]; 32 | } 33 | else { 34 | return a.concat(t+a[a.length-1]); 35 | } 36 | } 37 | , [] 38 | ); 39 | var r = Lazy(z).max() - Lazy(z).min(); 40 | return r/s; 41 | } 42 | ) 43 | .toArray(); 44 | return Math.log(jStat.mean(rs)); 45 | } 46 | ) 47 | .toArray() 48 | ; 49 | 50 | var ln_xx = xx.map( 51 | function(i){ 52 | return Math.log(i); 53 | } 54 | ) 55 | .toArray(); 56 | 57 | var h = 58 | jStat.covariance( 59 | yy, 60 | ln_xx 61 | ) 62 | / 63 | jStat.variance( 64 | ln_xx,true 65 | ); 66 | 67 | var signal = (Math.abs(h)-0.5)/0.5; 68 | 69 | //var r2 = Math.pow(jStat.corrcoeff(yy,ln_xx),2); 70 | 71 | var flag = 0; 72 | 73 | v[0] < 0 ? flag = -1 : flag = 1; 74 | 75 | weight.INDEX_JKSE = signal * flag; -------------------------------------------------------------------------------- /Strategies/logistic map.js: -------------------------------------------------------------------------------- 1 | var w = null; 2 | 3 | history.INDEX_GSPC_weight != null ? w = history.INDEX_GSPC_weight[0] : w = 0.511; 4 | 5 | weight.INDEX_GSPC = 4 * w * (1 - w); 6 | -------------------------------------------------------------------------------- /Strategies/meh-momentum.js: -------------------------------------------------------------------------------- 1 | //loop keys 2 | //find max 3 | 4 | var maxAsset = Object 5 | .keys(data) 6 | .reduce( 7 | function(a,k){ 8 | var m = data[k].period(252).lookback(2).returns()[0]; 9 | a.currentMax < m ? r = {key:k, currentMax: m} : r = a; 10 | return r; 11 | } 12 | ,{key:"",currentMax:-Infinity} 13 | ) 14 | .key; 15 | 16 | weight[maxAsset] = 1; 17 | 18 | weight.INDEX_IRX = -1*(4/13); 19 | 20 | -------------------------------------------------------------------------------- /Strategies/mojito.js: -------------------------------------------------------------------------------- 1 | var vxv = data.VXV.daily().lookback(1).prices()[0]; 2 | var vix = data.INDEX_VIX.daily().lookback(1).prices()[0]; 3 | 4 | var ivts = vix/vxv; 5 | 6 | ivts.print(); 7 | 8 | if(ivts < 0.91){ 9 | weight.VXX = -0.7; 10 | weight.NYSEARCA_VXZ = 0.3; 11 | } 12 | else if (ivts < 0.94){ 13 | weight.VXX = -0.32; 14 | weight.NYSEARCA_VXZ = 0.68; 15 | } 16 | else if (ivts < 0.97){ 17 | weight.VXX = -0.32; 18 | weight.NYSEARCA_VXZ =0.68; 19 | } 20 | else if (ivts < 1.005){ 21 | weight.VXX = -0.28; 22 | weight.NYSEARCA_VXZ = 0.72; 23 | } 24 | else { 25 | weight.VXX = 0; 26 | weight.NYSEARCA_VXZ = 1; 27 | } 28 | 29 | //13 week t-bill yield index used to calculate Sharpe ratio. Scaled for a monthly horizon 30 | weight.INDEX_IRX = -(weight.VXX+weight.NYSEARCA_VXZ)/(5*13); 31 | 32 | -------------------------------------------------------------------------------- /Strategies/mom autoc.js: -------------------------------------------------------------------------------- 1 | var m = data.SPCPMTUP.daily().lookback(90).returns(); 2 | 3 | var c = jStat.corrcoeff( Lazy(m).initial().toArray(), Lazy(m).rest().toArray() ); 4 | 5 | var f = 0; 6 | m[0] < 0 ? f = -1 : f = 0; 7 | 8 | weight.SPCPMTUP = c*f; 9 | 10 | -------------------------------------------------------------------------------- /Strategies/momentum-pp.js: -------------------------------------------------------------------------------- 1 | var m = data.SPCPMTUP.monthly().lookback(2).returns()[0]; 2 | 3 | m >= 0.025 || m <= -0.025 ? 4 | weight.SPCPMTUP = -m 5 | : 6 | weight.SPCPMTUP = 0.2; 7 | 8 | // 9 | 10 | var m = data.NYSEARCA_MTUM.monthly().lookback(2).returns()[0]; 11 | 12 | m >= 0.025 || m <= -0.025 ? 13 | weight.NYSEARCA_MTUM = -m 14 | : 15 | weight.NYSEARCA_MTUM = 0.2; 16 | 17 | -------------------------------------------------------------------------------- /Strategies/momentumpp.js: -------------------------------------------------------------------------------- 1 | 2 | var m = data.NYSEARCA_MTUM.monthly().lookback(2).returns()[0]; 3 | 4 | var mean = 0.013; 5 | var sd = 0.03; 6 | 7 | m >= 2*sd || m <= -2*sd+mean ? 8 | weight.NYSEARCA_MTUM = -m 9 | : 10 | weight.NYSEARCA_MTUM = mean; 11 | 12 | -------------------------------------------------------------------------------- /Strategies/momersion lbt twist.js: -------------------------------------------------------------------------------- 1 | var Mc = data 2 | .AMEX_SPY 3 | .daily() 4 | .lookback(21) 5 | .returns() 6 | .reduce( 7 | function(a,c){ 8 | //increment in the prescence of momentum 9 | a[0]*c > 0 ? a[1] = a[1]+1 : null; 10 | //update 'prev' return 11 | a[0] = c; 12 | return a; 13 | } 14 | //[prev return, mo count] 15 | ,[0,0] 16 | ) 17 | [1]; 18 | 19 | weight.AMEX_SPY = Mc/19; 20 | -------------------------------------------------------------------------------- /Strategies/momersion.js: -------------------------------------------------------------------------------- 1 | var Mc = data 2 | .AMEX_SPY 3 | .daily() 4 | .lookback(20) 5 | .returns() 6 | .reduce( 7 | function(a,c){ 8 | //increment in the prescence of momentum 9 | a[0]*c > 0 ? a[1] = a[1]+1 : null; 10 | //update 'prev' return 11 | a[0] = c; 12 | return a; 13 | } 14 | //[prev return, mo count] 15 | ,[0,0] 16 | ) 17 | [1]; 18 | 19 | //copy over previous weight 20 | if (history.AMEX_SPY_weight != null) { 21 | weight.AMEX_SPY = history.AMEX_SPY_weight[0] 22 | } 23 | else { 24 | //default 25 | weight.AMEX_SPY = 0; 26 | } 27 | 28 | //buy 29 | Mc/19 < 0.3 ? weight.AMEX_SPY = 1 : null; 30 | 31 | //sell 32 | Mc/19 > 0.7 ? weight.AMEX_SPY = 0 : null; -------------------------------------------------------------------------------- /Strategies/oz real steady vol.js: -------------------------------------------------------------------------------- 1 | var a = data.INDEX_AORD.daily().lookback(6).returns(); 2 | weight.INDEX_AORD = 0.005/jStat.stdev(a,true); -------------------------------------------------------------------------------- /Strategies/pca.js: -------------------------------------------------------------------------------- 1 | var lb = 63; 2 | 3 | var bb = data.AMEX_TLT.daily().lookback(lb).returns(); 4 | var cc = data.AMEX_SPY.daily().lookback(lb).returns(); 5 | var dd = data.AMEX_GLD.daily().lookback(lb).returns(); 6 | 7 | var pss = [bb,cc,dd]; 8 | 9 | //demean 10 | var pss_dm = pss.map( 11 | function(i){ 12 | var m = jStat(i).mean(); 13 | return i.map(function(j){return j-m;}); 14 | } 15 | ) 16 | 17 | //********************************************* 18 | //find r2, and return 'goodness of spread/diversification' resul 19 | var diversificationMeasure = function(ps){ 20 | var r = numeric.svd( numeric.transpose(ps) ) 21 | .S 22 | .map( 23 | function(i){ 24 | return Math.pow(i,2) 25 | } 26 | ) 27 | ; 28 | var sum = r.reduce(function(a,c){return a+c;}); 29 | 30 | return r.map(function(i){return i/sum;}).reduce(function(a,c){return a * c;},1); 31 | } 32 | 33 | //********************************************* 34 | //optimise 35 | var objective = function(x){ 36 | //shock 37 | return - diversificationMeasure( numeric.dot(numeric.diag(x), pss_dm) ); 38 | } 39 | 40 | //start at equal weighting 41 | var sol = numeric.uncmin(objective,numeric.rep([pss.length],1/pss.length)).solution; 42 | sol.print(); 43 | //********************************************* 44 | 45 | weight.AMEX_TLT = sol[0]; 46 | weight.AMEX_SPY = sol[1]; 47 | weight.AMEX_GLD = sol[2]; 48 | //netting, in order to calculate sharpe 49 | weight.INDEX_IRX = -sol.reduce(function(a,c){return a+c;},0)*(4/13); 50 | -------------------------------------------------------------------------------- /Strategies/realisedVol.js: -------------------------------------------------------------------------------- 1 | var realisedVol = jStat( data.INDEX_GSPC.daily().lookback(11).returns() ).stdev(true)*100*Math.sqrt(21); 2 | //var vix = data.INDEX_GSPC.daily().lookback(1).prices(); 3 | 4 | weight.INDEX_GSPC = 1/realisedVol; 5 | //weight.INDEX_GSPC = 1/vix; 6 | 7 | //13 week t-bill yield index used to calculate Sharpe ratio. Scaled for a monthly horizon 8 | weight.INDEX_IRX = -weight.INDEX_GSPC*(4/13); 9 | 10 | -------------------------------------------------------------------------------- /Strategies/simplest mean reversion.js: -------------------------------------------------------------------------------- 1 | 2 | var f = data.NYSEARCA_FXI.daily().lookback(2).returns()[0]; 3 | weight.NYSEARCA_FXI = -f/Math.abs(f+0.0000000001); 4 | 5 | <<<<<<< HEAD 6 | //13 week t-bill yield index used to calculate Sharpe ratio. Scaled for a monthly horizon 7 | weight.INDEX_IRX = -weight.NYSEARCA_FXI*(4/13); 8 | ======= 9 | //13 week t-bill yield index used to calculate Sharpe ratio. Scaled for a daily horizon 10 | weight.INDEX_IRX = -weight.NYSEARCA_FXI/(5*13); 11 | >>>>>>> addf4672b6b714b22df7b38bbfc9d3c3986500a1 12 | 13 | -------------------------------------------------------------------------------- /Strategies/simplest momentum strategy.js: -------------------------------------------------------------------------------- 1 | 2 | var s = data.INDEX_GSPC.daily().lookback(2).returns()[0]; 3 | weight.INDEX_GSPC = Math.abs(s)/(s+0.00000001); 4 | weight.INDEX_IRX = -weight.INDEX_GSPC/(5*13); 5 | 6 | -------------------------------------------------------------------------------- /Strategies/slmo.js: -------------------------------------------------------------------------------- 1 | 2 | var r = data.INDEX_GSPC.daily().lookback(500).returns(); 3 | 4 | var c = jStat.corrcoeff( Lazy(r).initial().toArray(), Lazy(r).rest().toArray() ); 5 | 6 | weight.INDEX_GSPC = 0; 7 | 8 | //mo 9 | r[0] > 0 && c > 0 ? weight.INDEX_GSPC = 1 : null; 10 | 11 | //mr 12 | r[0] < 0 && c < 0 ? weight.INDEX_GSPC = 1 : null; 13 | -------------------------------------------------------------------------------- /Strategies/smooth mojito.js: -------------------------------------------------------------------------------- 1 | var vxv = data.VXV.daily().lookback(1).prices()[0]; 2 | var vix = data.INDEX_VIX.daily().lookback(1).prices()[0]; 3 | 4 | var ivts = vix/vxv; 5 | 6 | weight.VXX = 11*Math.pow(ivts,2)-17*ivts+5.8; 7 | weight.NYSEARCA_VXZ = 11*Math.pow(ivts,2)-17*ivts+6.8; 8 | 9 | //13 week t-bill yield index used to calculate Sharpe ratio. Scaled for a monthly horizon 10 | weight.INDEX_IRX = -(weight.VXX+weight.NYSEARCA_VXZ)*4/13; -------------------------------------------------------------------------------- /Strategies/steady low vol.js: -------------------------------------------------------------------------------- 1 | var a = data.SP5LVI.daily().lookback(6).returns(); 2 | weight.SP5LVI = 0.0057/jStat.stdev(a,true); -------------------------------------------------------------------------------- /Strategies/steady vol sp500.js: -------------------------------------------------------------------------------- 1 | var v = data.INDEX_GSPC.daily().lookback(43).returns(); 2 | weight.INDEX_GSPC = 1/jStat.stdev(v,true); 3 | weight.INDEX_IRX = -weight.INDEX_GSPC * (4/13); -------------------------------------------------------------------------------- /Strategies/stock correlation.js: -------------------------------------------------------------------------------- 1 | var sp = data.INDEX_GSPC.daily().lookback(500).returns(); 2 | 3 | var corr = jStat.corrcoeff(Lazy(sp).rest().toArray(),Lazy(sp).initial().toArray()); 4 | 5 | var flag = null; 6 | 7 | sp[0] < 0 ? 8 | flag = -1 9 | : 10 | flag = 1; 11 | ; 12 | 13 | weight.INDEX_GSPC = flag * corr; 14 | 15 | //13 week t-bill yield index used to calculate Sharpe ratio. Scaled for a monthly horizon 16 | weight.INDEX_IRX = -weight.INDEX_GSPC*(4/13); 17 | -------------------------------------------------------------------------------- /addMarketDataWorker.js: -------------------------------------------------------------------------------- 1 | importScripts('lib/lazy.js'); 2 | importScripts('lib/strategyHelpers.js'); 3 | importScripts('lib/dataHelpers.js'); 4 | importScripts('lib/networking2.js'); 5 | importScripts('lib/utility.js'); 6 | 7 | 8 | 9 | 10 | //***************************************************** 11 | 12 | self.addEventListener( 13 | 'message', 14 | function(e) { 15 | var d = e.data; 16 | 17 | var prices = 18 | //map data from quandl 19 | getQuandlCall( 20 | d.source, 21 | d.ticker, 22 | d.key 23 | ) 24 | .xhr() 25 | .map( 26 | function(i){ 27 | var r = null; 28 | d.y == true ? 29 | r = {ticker: d.ticker, date: new Date(i[0]), datum:i[1].yieldToDsft()} 30 | : 31 | r = {ticker: d.ticker, date: new Date(i[0]), datum:i[1]}; 32 | return r; 33 | } 34 | ) 35 | ; 36 | 37 | //console.log(prices); 38 | 39 | self.postMessage({ticker:d.ticker,prices:prices,horizon:d.horizon}); 40 | }, 41 | false 42 | ); 43 | 44 | 45 | //***************************************************** 46 | 47 | -------------------------------------------------------------------------------- /clean/calendar.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | //need moment.js library 4 | 5 | const easterMonday = y => 6 | { 7 | const emDaysByYear = 8 | [ 9 | 98, 90, 103, 95, 114, 106, 91, 111, 102, // 1901-1909 10 | 87, 107, 99, 83, 103, 95, 115, 99, 91, 111, // 1910-1919 11 | 96, 87, 107, 92, 112, 103, 95, 108, 100, 91, // 1920-1929 12 | 111, 96, 88, 107, 92, 112, 104, 88, 108, 100, // 1930-1939 13 | 85, 104, 96, 116, 101, 92, 112, 97, 89, 108, // 1940-1949 14 | 100, 85, 105, 96, 109, 101, 93, 112, 97, 89, // 1950-1959 15 | 109, 93, 113, 105, 90, 109, 101, 86, 106, 97, // 1960-1969 16 | 89, 102, 94, 113, 105, 90, 110, 101, 86, 106, // 1970-1979 17 | 98, 110, 102, 94, 114, 98, 90, 110, 95, 86, // 1980-1989 18 | 106, 91, 111, 102, 94, 107, 99, 90, 103, 95, // 1990-1999 19 | 115, 106, 91, 111, 103, 87, 107, 99, 84, 103, // 2000-2009 20 | 95, 115, 100, 91, 111, 96, 88, 107, 92, 112, // 2010-2019 21 | 104, 95, 108, 100, 92, 111, 96, 88, 108, 92, // 2020-2029 22 | 112, 104, 89, 108, 100, 85, 105, 96, 116, 101, // 2030-2039 23 | 93, 112, 97, 89, 109, 100, 85, 105, 97, 109, // 2040-2049 24 | 101, 93, 113, 97, 89, 109, 94, 113, 105, 90, // 2050-2059 25 | 110, 101, 86, 106, 98, 89, 102, 94, 114, 105, // 2060-2069 26 | 90, 110, 102, 86, 106, 98, 111, 102, 94, 114, // 2070-2079 27 | 99, 90, 110, 95, 87, 106, 91, 111, 103, 94, // 2080-2089 28 | 107, 99, 91, 103, 95, 115, 107, 91, 111, 103, // 2090-2099 29 | 88, 108, 100, 85, 105, 96, 109, 101, 93, 112, // 2100-2109 30 | 97, 89, 109, 93, 113, 105, 90, 109, 101, 86, // 2110-2119 31 | 106, 97, 89, 102, 94, 113, 105, 90, 110, 101, // 2120-2129 32 | 86, 106, 98, 110, 102, 94, 114, 98, 90, 110, // 2130-2139 33 | 95, 86, 106, 91, 111, 102, 94, 107, 99, 90, // 2140-2149 34 | 103, 95, 115, 106, 91, 111, 103, 87, 107, 99, // 2150-2159 35 | 84, 103, 95, 115, 100, 91, 111, 96, 88, 107, // 2160-2169 36 | 92, 112, 104, 95, 108, 100, 92, 111, 96, 88, // 2170-2179 37 | 108, 92, 112, 104, 89, 108, 100, 85, 105, 96, // 2180-2189 38 | 116, 101, 93, 112, 97, 89, 109, 100, 85, 105 // 2190-2199 39 | ]; 40 | 41 | return emDaysByYear[ y - 1901 ]; 42 | } 43 | 44 | const isWeekend = 45 | w => 46 | (w === "Saturday" || w === "Sunday") 47 | ? true 48 | : false; 49 | 50 | 51 | const isBusinessDayNYSE = 52 | input => { 53 | const date = moment(input); 54 | 55 | const w = date.format('dddd'); 56 | const d = date.format('D'); 57 | const dd = date.format('DDD'); 58 | const m = date.format('MMMM'); 59 | const y = date.format('YYYY'); 60 | 61 | const em = easterMonday( date.format('YYYY') ); 62 | 63 | if ( 64 | isWeekend(w) 65 | // New Year's Day (possibly moved to Monday if on Sunday) 66 | || ((d == 1 || (d == 2 && w == "Monday")) && m == "January") 67 | // Washington's birthday (third Monday in February) 68 | || ((d >= 15 && d <= 21) && w == "Monday" && m == "February") 69 | // Good Friday 70 | || (dd == em-3) 71 | // Memorial Day (last Monday in May) 72 | || (d >= 25 && w == "Monday" && m == "May") 73 | // Independence Day (Monday if Sunday or Friday if Saturday) 74 | || ((d == 4 || (d == 5 && w == "Monday") || 75 | (d == 3 && w == "Friday")) && m == "July") 76 | // Labor Day (first Monday in September) 77 | || (d <= 7 && w == "Monday" && m == "September") 78 | // Thanksgiving Day (fourth Thursday in November) 79 | || ((d >= 22 && d <= 28) && w == "Thursday" && m == "November") 80 | // Christmas (Monday if Sunday or Friday if Saturday) 81 | || ((d == 25 || (d == 26 && w == "Monday") || 82 | (d == 24 && w == "Friday")) && m == "December") 83 | ){ 84 | return false; 85 | } 86 | else if (y >= 1998 && (d >= 15 && d <= 21) && w == "Monday" && m == "January") 87 | // Martin Luther King's birthday (third Monday in January) 88 | {return false;} 89 | else if ((y <= 1968 || (y <= 1980 && y % 4 == 0)) && m == "November" 90 | && d <= 7 && w == "Tuesday") 91 | // Presidential election days 92 | {return false;} 93 | // Special closings 94 | else if (// Hurricane Sandy 95 | (y == 2012 && m == "October" && (d == 29 || d == 30)) 96 | // President Ford's funeral 97 | || (y == 2007 && m == "January" && d == 2) 98 | // President Reagan's funeral 99 | || (y == 2004 && m == "June" && d == 11) 100 | // September 11-14, 2001 101 | || (y == 2001 && m == "September" && (11 <= d && d <= 14)) 102 | // President Nixon's funeral 103 | || (y == 1994 && m == "April" && d == 27) 104 | // Hurricane Gloria 105 | || (y == 1985 && m == "September" && d == 27) 106 | // 1977 Blackout 107 | || (y == 1977 && m == "July" && d == 14) 108 | // Funeral of former President Lyndon B. Johnson. 109 | || (y == 1973 && m == "January" && d == 25) 110 | // Funeral of former President Harry S. Truman 111 | || (y == 1972 && m == "December" && d == 28) 112 | // National Day of Participation for the lunar exploration. 113 | || (y == 1969 && m == "July" && d == 21) 114 | // Funeral of former President Eisenhower. 115 | || (y == 1969 && m == "March" && d == 31) 116 | // Closed all day - heavy snow. 117 | || (y == 1969 && m == "February" && d == 10) 118 | // Day after Independence Day. 119 | || (y == 1968 && m == "July" && d == 5) 120 | // June 12-Dec. 31, 1968 121 | // Four day week (closed on Wednesdays) - Paperwork Crisis 122 | || (y == 1968 && dd >= 163 && w == "Wednesday") 123 | // Day of mourning for Martin Luther King Jr. 124 | || (y == 1968 && m == "April" && d == 9) 125 | // Funeral of President Kennedy 126 | || (y == 1963 && m == "November" && d == 25) 127 | // Day before Decoration Day 128 | || (y == 1961 && m == "May" && d == 29) 129 | // Day after Christmas 130 | || (y == 1958 && m == "December" && d == 26) 131 | // Christmas Eve 132 | || ((y == 1954 || y == 1956 || y == 1965) 133 | && m == "December" && d == 24) 134 | ) {return false;} 135 | else { 136 | return true; 137 | }; 138 | } 139 | -------------------------------------------------------------------------------- /clean/clean.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 18 | 19 | 88 | 89 | -------------------------------------------------------------------------------- /clean/moment.min.js: -------------------------------------------------------------------------------- 1 | //! moment.js 2 | //! version : 2.11.0 3 | //! authors : Tim Wood, Iskren Chernev, Moment.js contributors 4 | //! license : MIT 5 | //! momentjs.com 6 | !function(a,b){"object"==typeof exports&&"undefined"!=typeof module?module.exports=b():"function"==typeof define&&define.amd?define(b):a.moment=b()}(this,function(){"use strict";function a(){return Qc.apply(null,arguments)}function b(a){Qc=a}function c(a){return"[object Array]"===Object.prototype.toString.call(a)}function d(a){return a instanceof Date||"[object Date]"===Object.prototype.toString.call(a)}function e(a,b){var c,d=[];for(c=0;c0)for(c in Sc)d=Sc[c],e=b[d],m(e)||(a[d]=e);return a}function o(b){n(this,b),this._d=new Date(null!=b._d?b._d.getTime():NaN),Tc===!1&&(Tc=!0,a.updateOffset(this),Tc=!1)}function p(a){return a instanceof o||null!=a&&null!=a._isAMomentObject}function q(a){return 0>a?Math.ceil(a):Math.floor(a)}function r(a){var b=+a,c=0;return 0!==b&&isFinite(b)&&(c=q(b)),c}function s(a,b,c){var d,e=Math.min(a.length,b.length),f=Math.abs(a.length-b.length),g=0;for(d=0;e>d;d++)(c&&a[d]!==b[d]||!c&&r(a[d])!==r(b[d]))&&g++;return g+f}function t(){}function u(a){return a?a.toLowerCase().replace("_","-"):a}function v(a){for(var b,c,d,e,f=0;f0;){if(d=w(e.slice(0,b).join("-")))return d;if(c&&c.length>=b&&s(e,c,!0)>=b-1)break;b--}f++}return null}function w(a){var b=null;if(!Uc[a]&&!m(module)&&module&&module.exports)try{b=Rc._abbr,require("./locale/"+a),x(b)}catch(c){}return Uc[a]}function x(a,b){var c;return a&&(c=m(b)?z(a):y(a,b),c&&(Rc=c)),Rc._abbr}function y(a,b){return null!==b?(b.abbr=a,Uc[a]=Uc[a]||new t,Uc[a].set(b),x(a),Uc[a]):(delete Uc[a],null)}function z(a){var b;if(a&&a._locale&&a._locale._abbr&&(a=a._locale._abbr),!a)return Rc;if(!c(a)){if(b=w(a))return b;a=[a]}return v(a)}function A(a,b){var c=a.toLowerCase();Vc[c]=Vc[c+"s"]=Vc[b]=a}function B(a){return"string"==typeof a?Vc[a]||Vc[a.toLowerCase()]:void 0}function C(a){var b,c,d={};for(c in a)f(a,c)&&(b=B(c),b&&(d[b]=a[c]));return d}function D(a){return a instanceof Function||"[object Function]"===Object.prototype.toString.call(a)}function E(b,c){return function(d){return null!=d?(G(this,b,d),a.updateOffset(this,c),this):F(this,b)}}function F(a,b){return a.isValid()?a._d["get"+(a._isUTC?"UTC":"")+b]():NaN}function G(a,b,c){a.isValid()&&a._d["set"+(a._isUTC?"UTC":"")+b](c)}function H(a,b){var c;if("object"==typeof a)for(c in a)this.set(c,a[c]);else if(a=B(a),D(this[a]))return this[a](b);return this}function I(a,b,c){var d=""+Math.abs(a),e=b-d.length,f=a>=0;return(f?c?"+":"":"-")+Math.pow(10,Math.max(0,e)).toString().substr(1)+d}function J(a,b,c,d){var e=d;"string"==typeof d&&(e=function(){return this[d]()}),a&&(Zc[a]=e),b&&(Zc[b[0]]=function(){return I(e.apply(this,arguments),b[1],b[2])}),c&&(Zc[c]=function(){return this.localeData().ordinal(e.apply(this,arguments),a)})}function K(a){return a.match(/\[[\s\S]/)?a.replace(/^\[|\]$/g,""):a.replace(/\\/g,"")}function L(a){var b,c,d=a.match(Wc);for(b=0,c=d.length;c>b;b++)Zc[d[b]]?d[b]=Zc[d[b]]:d[b]=K(d[b]);return function(e){var f="";for(b=0;c>b;b++)f+=d[b]instanceof Function?d[b].call(e,a):d[b];return f}}function M(a,b){return a.isValid()?(b=N(b,a.localeData()),Yc[b]=Yc[b]||L(b),Yc[b](a)):a.localeData().invalidDate()}function N(a,b){function c(a){return b.longDateFormat(a)||a}var d=5;for(Xc.lastIndex=0;d>=0&&Xc.test(a);)a=a.replace(Xc,c),Xc.lastIndex=0,d-=1;return a}function O(a,b,c){pd[a]=D(b)?b:function(a){return a&&c?c:b}}function P(a,b){return f(pd,a)?pd[a](b._strict,b._locale):new RegExp(Q(a))}function Q(a){return a.replace("\\","").replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(a,b,c,d,e){return b||c||d||e}).replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function R(a,b){var c,d=b;for("string"==typeof a&&(a=[a]),"number"==typeof b&&(d=function(a,c){c[b]=r(a)}),c=0;cd;d++){if(e=h([2e3,d]),c&&!this._longMonthsParse[d]&&(this._longMonthsParse[d]=new RegExp("^"+this.months(e,"").replace(".","")+"$","i"),this._shortMonthsParse[d]=new RegExp("^"+this.monthsShort(e,"").replace(".","")+"$","i")),c||this._monthsParse[d]||(f="^"+this.months(e,"")+"|^"+this.monthsShort(e,""),this._monthsParse[d]=new RegExp(f.replace(".",""),"i")),c&&"MMMM"===b&&this._longMonthsParse[d].test(a))return d;if(c&&"MMM"===b&&this._shortMonthsParse[d].test(a))return d;if(!c&&this._monthsParse[d].test(a))return d}}function Y(a,b){var c;return a.isValid()?"string"==typeof b&&(b=a.localeData().monthsParse(b),"number"!=typeof b)?a:(c=Math.min(a.date(),U(a.year(),b)),a._d["set"+(a._isUTC?"UTC":"")+"Month"](b,c),a):a}function Z(b){return null!=b?(Y(this,b),a.updateOffset(this,!0),this):F(this,"Month")}function $(){return U(this.year(),this.month())}function _(a){var b,c=a._a;return c&&-2===j(a).overflow&&(b=c[sd]<0||c[sd]>11?sd:c[td]<1||c[td]>U(c[rd],c[sd])?td:c[ud]<0||c[ud]>24||24===c[ud]&&(0!==c[vd]||0!==c[wd]||0!==c[xd])?ud:c[vd]<0||c[vd]>59?vd:c[wd]<0||c[wd]>59?wd:c[xd]<0||c[xd]>999?xd:-1,j(a)._overflowDayOfYear&&(rd>b||b>td)&&(b=td),j(a)._overflowWeeks&&-1===b&&(b=yd),j(a)._overflowWeekday&&-1===b&&(b=zd),j(a).overflow=b),a}function aa(b){a.suppressDeprecationWarnings===!1&&!m(console)&&console.warn&&console.warn("Deprecation warning: "+b)}function ba(a,b){var c=!0;return g(function(){return c&&(aa(a+"\nArguments: "+Array.prototype.slice.call(arguments).join(", ")+"\n"+(new Error).stack),c=!1),b.apply(this,arguments)},b)}function ca(a,b){Dd[a]||(aa(b),Dd[a]=!0)}function da(a){var b,c,d,e,f,g,h=a._i,i=Ed.exec(h)||Fd.exec(h);if(i){for(j(a).iso=!0,b=0,c=Hd.length;c>b;b++)if(Hd[b][1].exec(i[1])){e=Hd[b][0],d=Hd[b][2]!==!1;break}if(null==e)return void(a._isValid=!1);if(i[3]){for(b=0,c=Id.length;c>b;b++)if(Id[b][1].exec(i[3])){f=(i[2]||" ")+Id[b][0];break}if(null==f)return void(a._isValid=!1)}if(!d&&null!=f)return void(a._isValid=!1);if(i[4]){if(!Gd.exec(i[4]))return void(a._isValid=!1);g="Z"}a._f=e+(f||"")+(g||""),sa(a)}else a._isValid=!1}function ea(b){var c=Jd.exec(b._i);return null!==c?void(b._d=new Date(+c[1])):(da(b),void(b._isValid===!1&&(delete b._isValid,a.createFromInputFallback(b))))}function fa(a,b,c,d,e,f,g){var h=new Date(a,b,c,d,e,f,g);return 100>a&&a>=0&&isFinite(h.getFullYear())&&h.setFullYear(a),h}function ga(a){var b=new Date(Date.UTC.apply(null,arguments));return 100>a&&a>=0&&isFinite(b.getUTCFullYear())&&b.setUTCFullYear(a),b}function ha(a){return ia(a)?366:365}function ia(a){return a%4===0&&a%100!==0||a%400===0}function ja(){return ia(this.year())}function ka(a,b,c){var d=7+b-c,e=(7+ga(a,0,d).getUTCDay()-b)%7;return-e+d-1}function la(a,b,c,d,e){var f,g,h=(7+c-d)%7,i=ka(a,d,e),j=1+7*(b-1)+h+i;return 0>=j?(f=a-1,g=ha(f)+j):j>ha(a)?(f=a+1,g=j-ha(a)):(f=a,g=j),{year:f,dayOfYear:g}}function ma(a,b,c){var d,e,f=ka(a.year(),b,c),g=Math.floor((a.dayOfYear()-f-1)/7)+1;return 1>g?(e=a.year()-1,d=g+na(e,b,c)):g>na(a.year(),b,c)?(d=g-na(a.year(),b,c),e=a.year()+1):(e=a.year(),d=g),{week:d,year:e}}function na(a,b,c){var d=ka(a,b,c),e=ka(a+1,b,c);return(ha(a)-d+e)/7}function oa(a,b,c){return null!=a?a:null!=b?b:c}function pa(b){var c=new Date(a.now());return b._useUTC?[c.getUTCFullYear(),c.getUTCMonth(),c.getUTCDate()]:[c.getFullYear(),c.getMonth(),c.getDate()]}function qa(a){var b,c,d,e,f=[];if(!a._d){for(d=pa(a),a._w&&null==a._a[td]&&null==a._a[sd]&&ra(a),a._dayOfYear&&(e=oa(a._a[rd],d[rd]),a._dayOfYear>ha(e)&&(j(a)._overflowDayOfYear=!0),c=ga(e,0,a._dayOfYear),a._a[sd]=c.getUTCMonth(),a._a[td]=c.getUTCDate()),b=0;3>b&&null==a._a[b];++b)a._a[b]=f[b]=d[b];for(;7>b;b++)a._a[b]=f[b]=null==a._a[b]?2===b?1:0:a._a[b];24===a._a[ud]&&0===a._a[vd]&&0===a._a[wd]&&0===a._a[xd]&&(a._nextDay=!0,a._a[ud]=0),a._d=(a._useUTC?ga:fa).apply(null,f),null!=a._tzm&&a._d.setUTCMinutes(a._d.getUTCMinutes()-a._tzm),a._nextDay&&(a._a[ud]=24)}}function ra(a){var b,c,d,e,f,g,h,i;b=a._w,null!=b.GG||null!=b.W||null!=b.E?(f=1,g=4,c=oa(b.GG,a._a[rd],ma(Aa(),1,4).year),d=oa(b.W,1),e=oa(b.E,1),(1>e||e>7)&&(i=!0)):(f=a._locale._week.dow,g=a._locale._week.doy,c=oa(b.gg,a._a[rd],ma(Aa(),f,g).year),d=oa(b.w,1),null!=b.d?(e=b.d,(0>e||e>6)&&(i=!0)):null!=b.e?(e=b.e+f,(b.e<0||b.e>6)&&(i=!0)):e=f),1>d||d>na(c,f,g)?j(a)._overflowWeeks=!0:null!=i?j(a)._overflowWeekday=!0:(h=la(c,d,e,f,g),a._a[rd]=h.year,a._dayOfYear=h.dayOfYear)}function sa(b){if(b._f===a.ISO_8601)return void da(b);b._a=[],j(b).empty=!0;var c,d,e,f,g,h=""+b._i,i=h.length,k=0;for(e=N(b._f,b._locale).match(Wc)||[],c=0;c0&&j(b).unusedInput.push(g),h=h.slice(h.indexOf(d)+d.length),k+=d.length),Zc[f]?(d?j(b).empty=!1:j(b).unusedTokens.push(f),T(f,d,b)):b._strict&&!d&&j(b).unusedTokens.push(f);j(b).charsLeftOver=i-k,h.length>0&&j(b).unusedInput.push(h),j(b).bigHour===!0&&b._a[ud]<=12&&b._a[ud]>0&&(j(b).bigHour=void 0),b._a[ud]=ta(b._locale,b._a[ud],b._meridiem),qa(b),_(b)}function ta(a,b,c){var d;return null==c?b:null!=a.meridiemHour?a.meridiemHour(b,c):null!=a.isPM?(d=a.isPM(c),d&&12>b&&(b+=12),d||12!==b||(b=0),b):b}function ua(a){var b,c,d,e,f;if(0===a._f.length)return j(a).invalidFormat=!0,void(a._d=new Date(NaN));for(e=0;ef)&&(d=f,c=b));g(a,c||b)}function va(a){if(!a._d){var b=C(a._i);a._a=e([b.year,b.month,b.day||b.date,b.hour,b.minute,b.second,b.millisecond],function(a){return a&&parseInt(a,10)}),qa(a)}}function wa(a){var b=new o(_(xa(a)));return b._nextDay&&(b.add(1,"d"),b._nextDay=void 0),b}function xa(a){var b=a._i,e=a._f;return a._locale=a._locale||z(a._l),null===b||void 0===e&&""===b?l({nullInput:!0}):("string"==typeof b&&(a._i=b=a._locale.preparse(b)),p(b)?new o(_(b)):(c(e)?ua(a):e?sa(a):d(b)?a._d=b:ya(a),k(a)||(a._d=null),a))}function ya(b){var f=b._i;void 0===f?b._d=new Date(a.now()):d(f)?b._d=new Date(+f):"string"==typeof f?ea(b):c(f)?(b._a=e(f.slice(0),function(a){return parseInt(a,10)}),qa(b)):"object"==typeof f?va(b):"number"==typeof f?b._d=new Date(f):a.createFromInputFallback(b)}function za(a,b,c,d,e){var f={};return"boolean"==typeof c&&(d=c,c=void 0),f._isAMomentObject=!0,f._useUTC=f._isUTC=e,f._l=c,f._i=a,f._f=b,f._strict=d,wa(f)}function Aa(a,b,c,d){return za(a,b,c,d,!1)}function Ba(a,b){var d,e;if(1===b.length&&c(b[0])&&(b=b[0]),!b.length)return Aa();for(d=b[0],e=1;ea&&(a=-a,c="-"),c+I(~~(a/60),2)+b+I(~~a%60,2)})}function Ha(a,b){var c=(b||"").match(a)||[],d=c[c.length-1]||[],e=(d+"").match(Od)||["-",0,0],f=+(60*e[1])+r(e[2]);return"+"===e[0]?f:-f}function Ia(b,c){var e,f;return c._isUTC?(e=c.clone(),f=(p(b)||d(b)?+b:+Aa(b))-+e,e._d.setTime(+e._d+f),a.updateOffset(e,!1),e):Aa(b).local()}function Ja(a){return 15*-Math.round(a._d.getTimezoneOffset()/15)}function Ka(b,c){var d,e=this._offset||0;return this.isValid()?null!=b?("string"==typeof b?b=Ha(md,b):Math.abs(b)<16&&(b=60*b),!this._isUTC&&c&&(d=Ja(this)),this._offset=b,this._isUTC=!0,null!=d&&this.add(d,"m"),e!==b&&(!c||this._changeInProgress?$a(this,Va(b-e,"m"),1,!1):this._changeInProgress||(this._changeInProgress=!0,a.updateOffset(this,!0),this._changeInProgress=null)),this):this._isUTC?e:Ja(this):null!=b?this:NaN}function La(a,b){return null!=a?("string"!=typeof a&&(a=-a),this.utcOffset(a,b),this):-this.utcOffset()}function Ma(a){return this.utcOffset(0,a)}function Na(a){return this._isUTC&&(this.utcOffset(0,a),this._isUTC=!1,a&&this.subtract(Ja(this),"m")),this}function Oa(){return this._tzm?this.utcOffset(this._tzm):"string"==typeof this._i&&this.utcOffset(Ha(ld,this._i)),this}function Pa(a){return this.isValid()?(a=a?Aa(a).utcOffset():0,(this.utcOffset()-a)%60===0):!1}function Qa(){return this.utcOffset()>this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()}function Ra(){if(!m(this._isDSTShifted))return this._isDSTShifted;var a={};if(n(a,this),a=xa(a),a._a){var b=a._isUTC?h(a._a):Aa(a._a);this._isDSTShifted=this.isValid()&&s(a._a,b.toArray())>0}else this._isDSTShifted=!1;return this._isDSTShifted}function Sa(){return this.isValid()?!this._isUTC:!1}function Ta(){return this.isValid()?this._isUTC:!1}function Ua(){return this.isValid()?this._isUTC&&0===this._offset:!1}function Va(a,b){var c,d,e,g=a,h=null;return Fa(a)?g={ms:a._milliseconds,d:a._days,M:a._months}:"number"==typeof a?(g={},b?g[b]=a:g.milliseconds=a):(h=Pd.exec(a))?(c="-"===h[1]?-1:1,g={y:0,d:r(h[td])*c,h:r(h[ud])*c,m:r(h[vd])*c,s:r(h[wd])*c,ms:r(h[xd])*c}):(h=Qd.exec(a))?(c="-"===h[1]?-1:1,g={y:Wa(h[2],c),M:Wa(h[3],c),d:Wa(h[4],c),h:Wa(h[5],c),m:Wa(h[6],c),s:Wa(h[7],c),w:Wa(h[8],c)}):null==g?g={}:"object"==typeof g&&("from"in g||"to"in g)&&(e=Ya(Aa(g.from),Aa(g.to)),g={},g.ms=e.milliseconds,g.M=e.months),d=new Ea(g),Fa(a)&&f(a,"_locale")&&(d._locale=a._locale),d}function Wa(a,b){var c=a&&parseFloat(a.replace(",","."));return(isNaN(c)?0:c)*b}function Xa(a,b){var c={milliseconds:0,months:0};return c.months=b.month()-a.month()+12*(b.year()-a.year()),a.clone().add(c.months,"M").isAfter(b)&&--c.months,c.milliseconds=+b-+a.clone().add(c.months,"M"),c}function Ya(a,b){var c;return a.isValid()&&b.isValid()?(b=Ia(b,a),a.isBefore(b)?c=Xa(a,b):(c=Xa(b,a),c.milliseconds=-c.milliseconds,c.months=-c.months),c):{milliseconds:0,months:0}}function Za(a,b){return function(c,d){var e,f;return null===d||isNaN(+d)||(ca(b,"moment()."+b+"(period, number) is deprecated. Please use moment()."+b+"(number, period)."),f=c,c=d,d=f),c="string"==typeof c?+c:c,e=Va(c,d),$a(this,e,a),this}}function $a(b,c,d,e){var f=c._milliseconds,g=c._days,h=c._months;b.isValid()&&(e=null==e?!0:e,f&&b._d.setTime(+b._d+f*d),g&&G(b,"Date",F(b,"Date")+g*d),h&&Y(b,F(b,"Month")+h*d),e&&a.updateOffset(b,g||h))}function _a(a,b){var c=a||Aa(),d=Ia(c,this).startOf("day"),e=this.diff(d,"days",!0),f=-6>e?"sameElse":-1>e?"lastWeek":0>e?"lastDay":1>e?"sameDay":2>e?"nextDay":7>e?"nextWeek":"sameElse",g=b&&(D(b[f])?b[f]():b[f]);return this.format(g||this.localeData().calendar(f,this,Aa(c)))}function ab(){return new o(this)}function bb(a,b){var c=p(a)?a:Aa(a);return this.isValid()&&c.isValid()?(b=B(m(b)?"millisecond":b),"millisecond"===b?+this>+c:+c<+this.clone().startOf(b)):!1}function cb(a,b){var c=p(a)?a:Aa(a);return this.isValid()&&c.isValid()?(b=B(m(b)?"millisecond":b),"millisecond"===b?+c>+this:+this.clone().endOf(b)<+c):!1}function db(a,b,c){return this.isAfter(a,c)&&this.isBefore(b,c)}function eb(a,b){var c,d=p(a)?a:Aa(a);return this.isValid()&&d.isValid()?(b=B(b||"millisecond"),"millisecond"===b?+this===+d:(c=+d,+this.clone().startOf(b)<=c&&c<=+this.clone().endOf(b))):!1}function fb(a,b){return this.isSame(a,b)||this.isAfter(a,b)}function gb(a,b){return this.isSame(a,b)||this.isBefore(a,b)}function hb(a,b,c){var d,e,f,g;return this.isValid()?(d=Ia(a,this),d.isValid()?(e=6e4*(d.utcOffset()-this.utcOffset()),b=B(b),"year"===b||"month"===b||"quarter"===b?(g=ib(this,d),"quarter"===b?g/=3:"year"===b&&(g/=12)):(f=this-d,g="second"===b?f/1e3:"minute"===b?f/6e4:"hour"===b?f/36e5:"day"===b?(f-e)/864e5:"week"===b?(f-e)/6048e5:f),c?g:q(g)):NaN):NaN}function ib(a,b){var c,d,e=12*(b.year()-a.year())+(b.month()-a.month()),f=a.clone().add(e,"months");return 0>b-f?(c=a.clone().add(e-1,"months"),d=(b-f)/(f-c)):(c=a.clone().add(e+1,"months"),d=(b-f)/(c-f)),-(e+d)}function jb(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")}function kb(){var a=this.clone().utc();return 0f&&(b=f),Kb.call(this,a,b,c,d,e))}function Kb(a,b,c,d,e){var f=la(a,b,c,d,e),g=ga(f.year,0,f.dayOfYear);return this.year(g.getUTCFullYear()),this.month(g.getUTCMonth()),this.date(g.getUTCDate()),this}function Lb(a){return null==a?Math.ceil((this.month()+1)/3):this.month(3*(a-1)+this.month()%3)}function Mb(a){return ma(a,this._week.dow,this._week.doy).week}function Nb(){return this._week.dow}function Ob(){return this._week.doy}function Pb(a){var b=this.localeData().week(this);return null==a?b:this.add(7*(a-b),"d")}function Qb(a){var b=ma(this,1,4).week;return null==a?b:this.add(7*(a-b),"d")}function Rb(a,b){return"string"!=typeof a?a:isNaN(a)?(a=b.weekdaysParse(a),"number"==typeof a?a:null):parseInt(a,10)}function Sb(a,b){return c(this._weekdays)?this._weekdays[a.day()]:this._weekdays[this._weekdays.isFormat.test(b)?"format":"standalone"][a.day()]}function Tb(a){return this._weekdaysShort[a.day()]}function Ub(a){return this._weekdaysMin[a.day()]}function Vb(a,b,c){var d,e,f;for(this._weekdaysParse||(this._weekdaysParse=[],this._minWeekdaysParse=[],this._shortWeekdaysParse=[],this._fullWeekdaysParse=[]),d=0;7>d;d++){if(e=Aa([2e3,1]).day(d),c&&!this._fullWeekdaysParse[d]&&(this._fullWeekdaysParse[d]=new RegExp("^"+this.weekdays(e,"").replace(".",".?")+"$","i"),this._shortWeekdaysParse[d]=new RegExp("^"+this.weekdaysShort(e,"").replace(".",".?")+"$","i"),this._minWeekdaysParse[d]=new RegExp("^"+this.weekdaysMin(e,"").replace(".",".?")+"$","i")),this._weekdaysParse[d]||(f="^"+this.weekdays(e,"")+"|^"+this.weekdaysShort(e,"")+"|^"+this.weekdaysMin(e,""),this._weekdaysParse[d]=new RegExp(f.replace(".",""),"i")),c&&"dddd"===b&&this._fullWeekdaysParse[d].test(a))return d;if(c&&"ddd"===b&&this._shortWeekdaysParse[d].test(a))return d;if(c&&"dd"===b&&this._minWeekdaysParse[d].test(a))return d;if(!c&&this._weekdaysParse[d].test(a))return d}}function Wb(a){if(!this.isValid())return null!=a?this:NaN;var b=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=a?(a=Rb(a,this.localeData()),this.add(a-b,"d")):b}function Xb(a){if(!this.isValid())return null!=a?this:NaN;var b=(this.day()+7-this.localeData()._week.dow)%7;return null==a?b:this.add(a-b,"d")}function Yb(a){return this.isValid()?null==a?this.day()||7:this.day(this.day()%7?a:a-7):null!=a?this:NaN}function Zb(a){var b=Math.round((this.clone().startOf("day")-this.clone().startOf("year"))/864e5)+1;return null==a?b:this.add(a-b,"d")}function $b(){return this.hours()%12||12}function _b(a,b){J(a,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),b)})}function ac(a,b){return b._meridiemParse}function bc(a){return"p"===(a+"").toLowerCase().charAt(0)}function cc(a,b,c){return a>11?c?"pm":"PM":c?"am":"AM"}function dc(a,b){b[xd]=r(1e3*("0."+a))}function ec(){return this._isUTC?"UTC":""}function fc(){return this._isUTC?"Coordinated Universal Time":""}function gc(a){return Aa(1e3*a)}function hc(){return Aa.apply(null,arguments).parseZone()}function ic(a,b,c){var d=this._calendar[a];return D(d)?d.call(b,c):d}function jc(a){var b=this._longDateFormat[a],c=this._longDateFormat[a.toUpperCase()];return b||!c?b:(this._longDateFormat[a]=c.replace(/MMMM|MM|DD|dddd/g,function(a){return a.slice(1)}),this._longDateFormat[a])}function kc(){return this._invalidDate}function lc(a){return this._ordinal.replace("%d",a)}function mc(a){return a}function nc(a,b,c,d){var e=this._relativeTime[c];return D(e)?e(a,b,c,d):e.replace(/%d/i,a)}function oc(a,b){var c=this._relativeTime[a>0?"future":"past"];return D(c)?c(b):c.replace(/%s/i,b)}function pc(a){var b,c;for(c in a)b=a[c],D(b)?this[c]=b:this["_"+c]=b;this._ordinalParseLenient=new RegExp(this._ordinalParse.source+"|"+/\d{1,2}/.source)}function qc(a,b,c,d){var e=z(),f=h().set(d,b);return e[c](f,a)}function rc(a,b,c,d,e){if("number"==typeof a&&(b=a,a=void 0),a=a||"",null!=b)return qc(a,b,c,e);var f,g=[];for(f=0;d>f;f++)g[f]=qc(a,f,c,e);return g}function sc(a,b){return rc(a,b,"months",12,"month")}function tc(a,b){return rc(a,b,"monthsShort",12,"month")}function uc(a,b){return rc(a,b,"weekdays",7,"day")}function vc(a,b){return rc(a,b,"weekdaysShort",7,"day")}function wc(a,b){return rc(a,b,"weekdaysMin",7,"day")}function xc(){var a=this._data;return this._milliseconds=me(this._milliseconds),this._days=me(this._days),this._months=me(this._months),a.milliseconds=me(a.milliseconds),a.seconds=me(a.seconds),a.minutes=me(a.minutes),a.hours=me(a.hours),a.months=me(a.months),a.years=me(a.years),this}function yc(a,b,c,d){var e=Va(b,c);return a._milliseconds+=d*e._milliseconds,a._days+=d*e._days,a._months+=d*e._months,a._bubble()}function zc(a,b){return yc(this,a,b,1)}function Ac(a,b){return yc(this,a,b,-1)}function Bc(a){return 0>a?Math.floor(a):Math.ceil(a)}function Cc(){var a,b,c,d,e,f=this._milliseconds,g=this._days,h=this._months,i=this._data;return f>=0&&g>=0&&h>=0||0>=f&&0>=g&&0>=h||(f+=864e5*Bc(Ec(h)+g),g=0,h=0),i.milliseconds=f%1e3,a=q(f/1e3),i.seconds=a%60,b=q(a/60),i.minutes=b%60,c=q(b/60),i.hours=c%24,g+=q(c/24),e=q(Dc(g)),h+=e,g-=Bc(Ec(e)),d=q(h/12),h%=12,i.days=g,i.months=h,i.years=d,this}function Dc(a){return 4800*a/146097}function Ec(a){return 146097*a/4800}function Fc(a){var b,c,d=this._milliseconds;if(a=B(a),"month"===a||"year"===a)return b=this._days+d/864e5,c=this._months+Dc(b),"month"===a?c:c/12;switch(b=this._days+Math.round(Ec(this._months)),a){case"week":return b/7+d/6048e5;case"day":return b+d/864e5;case"hour":return 24*b+d/36e5;case"minute":return 1440*b+d/6e4;case"second":return 86400*b+d/1e3;case"millisecond":return Math.floor(864e5*b)+d;default:throw new Error("Unknown unit "+a)}}function Gc(){return this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*r(this._months/12)}function Hc(a){return function(){return this.as(a)}}function Ic(a){return a=B(a),this[a+"s"]()}function Jc(a){return function(){return this._data[a]}}function Kc(){return q(this.days()/7)}function Lc(a,b,c,d,e){return e.relativeTime(b||1,!!c,a,d)}function Mc(a,b,c){var d=Va(a).abs(),e=Ce(d.as("s")),f=Ce(d.as("m")),g=Ce(d.as("h")),h=Ce(d.as("d")),i=Ce(d.as("M")),j=Ce(d.as("y")),k=e=f&&["m"]||f=g&&["h"]||g=h&&["d"]||h=i&&["M"]||i=j&&["y"]||["yy",j];return k[2]=b,k[3]=+a>0,k[4]=c,Lc.apply(null,k)}function Nc(a,b){return void 0===De[a]?!1:void 0===b?De[a]:(De[a]=b,!0)}function Oc(a){var b=this.localeData(),c=Mc(this,!a,b);return a&&(c=b.pastFuture(+this,c)),b.postformat(c)}function Pc(){var a,b,c,d=Ee(this._milliseconds)/1e3,e=Ee(this._days),f=Ee(this._months);a=q(d/60),b=q(a/60),d%=60,a%=60,c=q(f/12),f%=12;var g=c,h=f,i=e,j=b,k=a,l=d,m=this.asSeconds();return m?(0>m?"-":"")+"P"+(g?g+"Y":"")+(h?h+"M":"")+(i?i+"D":"")+(j||k||l?"T":"")+(j?j+"H":"")+(k?k+"M":"")+(l?l+"S":""):"P0D"}var Qc,Rc,Sc=a.momentProperties=[],Tc=!1,Uc={},Vc={},Wc=/(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g,Xc=/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,Yc={},Zc={},$c=/\d/,_c=/\d\d/,ad=/\d{3}/,bd=/\d{4}/,cd=/[+-]?\d{6}/,dd=/\d\d?/,ed=/\d\d\d\d?/,fd=/\d\d\d\d\d\d?/,gd=/\d{1,3}/,hd=/\d{1,4}/,id=/[+-]?\d{1,6}/,jd=/\d+/,kd=/[+-]?\d+/,ld=/Z|[+-]\d\d:?\d\d/gi,md=/Z|[+-]\d\d(?::?\d\d)?/gi,nd=/[+-]?\d+(\.\d{1,3})?/,od=/[0-9]*(a[mn]\s?)?['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF\-]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i,pd={},qd={},rd=0,sd=1,td=2,ud=3,vd=4,wd=5,xd=6,yd=7,zd=8;J("M",["MM",2],"Mo",function(){return this.month()+1}),J("MMM",0,0,function(a){return this.localeData().monthsShort(this,a)}),J("MMMM",0,0,function(a){return this.localeData().months(this,a)}),A("month","M"),O("M",dd),O("MM",dd,_c),O("MMM",od),O("MMMM",od),R(["M","MM"],function(a,b){b[sd]=r(a)-1}),R(["MMM","MMMM"],function(a,b,c,d){var e=c._locale.monthsParse(a,d,c._strict);null!=e?b[sd]=e:j(c).invalidMonth=a});var Ad=/D[oD]?(\[[^\[\]]*\]|\s+)+MMMM?/,Bd="January_February_March_April_May_June_July_August_September_October_November_December".split("_"),Cd="Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sept_Oct_Nov_Dec".split("_"),Dd={};a.suppressDeprecationWarnings=!1;var Ed=/^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?/,Fd=/^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?/,Gd=/Z|[+-]\d\d(?::?\d\d)?/,Hd=[["YYYYYY-MM-DD",/[+-]\d{6}-\d\d-\d\d/],["YYYY-MM-DD",/\d{4}-\d\d-\d\d/],["GGGG-[W]WW-E",/\d{4}-W\d\d-\d/],["GGGG-[W]WW",/\d{4}-W\d\d/,!1],["YYYY-DDD",/\d{4}-\d{3}/],["YYYY-MM",/\d{4}-\d\d/,!1],["YYYYYYMMDD",/[+-]\d{10}/],["YYYYMMDD",/\d{8}/],["GGGG[W]WWE",/\d{4}W\d{3}/],["GGGG[W]WW",/\d{4}W\d{2}/,!1],["YYYYDDD",/\d{7}/]],Id=[["HH:mm:ss.SSSS",/\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss,SSSS",/\d\d:\d\d:\d\d,\d+/],["HH:mm:ss",/\d\d:\d\d:\d\d/],["HH:mm",/\d\d:\d\d/],["HHmmss.SSSS",/\d\d\d\d\d\d\.\d+/],["HHmmss,SSSS",/\d\d\d\d\d\d,\d+/],["HHmmss",/\d\d\d\d\d\d/],["HHmm",/\d\d\d\d/],["HH",/\d\d/]],Jd=/^\/?Date\((\-?\d+)/i;a.createFromInputFallback=ba("moment construction falls back to js Date. This is discouraged and will be removed in upcoming major release. Please refer to https://github.com/moment/moment/issues/1407 for more info.",function(a){a._d=new Date(a._i+(a._useUTC?" UTC":""))}),J(0,["YY",2],0,function(){return this.year()%100}),J(0,["YYYY",4],0,"year"),J(0,["YYYYY",5],0,"year"),J(0,["YYYYYY",6,!0],0,"year"),A("year","y"),O("Y",kd),O("YY",dd,_c),O("YYYY",hd,bd),O("YYYYY",id,cd),O("YYYYYY",id,cd),R(["YYYYY","YYYYYY"],rd),R("YYYY",function(b,c){c[rd]=2===b.length?a.parseTwoDigitYear(b):r(b)}),R("YY",function(b,c){c[rd]=a.parseTwoDigitYear(b)}),a.parseTwoDigitYear=function(a){return r(a)+(r(a)>68?1900:2e3)};var Kd=E("FullYear",!1);a.ISO_8601=function(){};var Ld=ba("moment().min is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548",function(){var a=Aa.apply(null,arguments);return this.isValid()&&a.isValid()?this>a?this:a:l()}),Md=ba("moment().max is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548",function(){var a=Aa.apply(null,arguments);return this.isValid()&&a.isValid()?a>this?this:a:l()}),Nd=Date.now||function(){return+new Date};Ga("Z",":"),Ga("ZZ",""),O("Z",md),O("ZZ",md),R(["Z","ZZ"],function(a,b,c){c._useUTC=!0,c._tzm=Ha(md,a)});var Od=/([\+\-]|\d\d)/gi;a.updateOffset=function(){};var Pd=/(\-)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/,Qd=/^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/;Va.fn=Ea.prototype;var Rd=Za(1,"add"),Sd=Za(-1,"subtract");a.defaultFormat="YYYY-MM-DDTHH:mm:ssZ";var Td=ba("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.",function(a){return void 0===a?this.localeData():this.locale(a)});J(0,["gg",2],0,function(){return this.weekYear()%100}),J(0,["GG",2],0,function(){return this.isoWeekYear()%100}),Eb("gggg","weekYear"),Eb("ggggg","weekYear"),Eb("GGGG","isoWeekYear"),Eb("GGGGG","isoWeekYear"),A("weekYear","gg"),A("isoWeekYear","GG"),O("G",kd),O("g",kd),O("GG",dd,_c),O("gg",dd,_c),O("GGGG",hd,bd),O("gggg",hd,bd),O("GGGGG",id,cd),O("ggggg",id,cd),S(["gggg","ggggg","GGGG","GGGGG"],function(a,b,c,d){b[d.substr(0,2)]=r(a)}),S(["gg","GG"],function(b,c,d,e){c[e]=a.parseTwoDigitYear(b)}),J("Q",0,"Qo","quarter"),A("quarter","Q"),O("Q",$c),R("Q",function(a,b){b[sd]=3*(r(a)-1)}),J("w",["ww",2],"wo","week"),J("W",["WW",2],"Wo","isoWeek"),A("week","w"),A("isoWeek","W"),O("w",dd),O("ww",dd,_c),O("W",dd),O("WW",dd,_c),S(["w","ww","W","WW"],function(a,b,c,d){b[d.substr(0,1)]=r(a)});var Ud={dow:0,doy:6};J("D",["DD",2],"Do","date"),A("date","D"),O("D",dd),O("DD",dd,_c),O("Do",function(a,b){ 7 | return a?b._ordinalParse:b._ordinalParseLenient}),R(["D","DD"],td),R("Do",function(a,b){b[td]=r(a.match(dd)[0],10)});var Vd=E("Date",!0);J("d",0,"do","day"),J("dd",0,0,function(a){return this.localeData().weekdaysMin(this,a)}),J("ddd",0,0,function(a){return this.localeData().weekdaysShort(this,a)}),J("dddd",0,0,function(a){return this.localeData().weekdays(this,a)}),J("e",0,0,"weekday"),J("E",0,0,"isoWeekday"),A("day","d"),A("weekday","e"),A("isoWeekday","E"),O("d",dd),O("e",dd),O("E",dd),O("dd",od),O("ddd",od),O("dddd",od),S(["dd","ddd","dddd"],function(a,b,c,d){var e=c._locale.weekdaysParse(a,d,c._strict);null!=e?b.d=e:j(c).invalidWeekday=a}),S(["d","e","E"],function(a,b,c,d){b[d]=r(a)});var Wd="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),Xd="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),Yd="Su_Mo_Tu_We_Th_Fr_Sa".split("_");J("DDD",["DDDD",3],"DDDo","dayOfYear"),A("dayOfYear","DDD"),O("DDD",gd),O("DDDD",ad),R(["DDD","DDDD"],function(a,b,c){c._dayOfYear=r(a)}),J("H",["HH",2],0,"hour"),J("h",["hh",2],0,$b),J("hmm",0,0,function(){return""+$b.apply(this)+I(this.minutes(),2)}),J("hmmss",0,0,function(){return""+$b.apply(this)+I(this.minutes(),2)+I(this.seconds(),2)}),J("Hmm",0,0,function(){return""+this.hours()+I(this.minutes(),2)}),J("Hmmss",0,0,function(){return""+this.hours()+I(this.minutes(),2)+I(this.seconds(),2)}),_b("a",!0),_b("A",!1),A("hour","h"),O("a",ac),O("A",ac),O("H",dd),O("h",dd),O("HH",dd,_c),O("hh",dd,_c),O("hmm",ed),O("hmmss",fd),O("Hmm",ed),O("Hmmss",fd),R(["H","HH"],ud),R(["a","A"],function(a,b,c){c._isPm=c._locale.isPM(a),c._meridiem=a}),R(["h","hh"],function(a,b,c){b[ud]=r(a),j(c).bigHour=!0}),R("hmm",function(a,b,c){var d=a.length-2;b[ud]=r(a.substr(0,d)),b[vd]=r(a.substr(d)),j(c).bigHour=!0}),R("hmmss",function(a,b,c){var d=a.length-4,e=a.length-2;b[ud]=r(a.substr(0,d)),b[vd]=r(a.substr(d,2)),b[wd]=r(a.substr(e)),j(c).bigHour=!0}),R("Hmm",function(a,b,c){var d=a.length-2;b[ud]=r(a.substr(0,d)),b[vd]=r(a.substr(d))}),R("Hmmss",function(a,b,c){var d=a.length-4,e=a.length-2;b[ud]=r(a.substr(0,d)),b[vd]=r(a.substr(d,2)),b[wd]=r(a.substr(e))});var Zd=/[ap]\.?m?\.?/i,$d=E("Hours",!0);J("m",["mm",2],0,"minute"),A("minute","m"),O("m",dd),O("mm",dd,_c),R(["m","mm"],vd);var _d=E("Minutes",!1);J("s",["ss",2],0,"second"),A("second","s"),O("s",dd),O("ss",dd,_c),R(["s","ss"],wd);var ae=E("Seconds",!1);J("S",0,0,function(){return~~(this.millisecond()/100)}),J(0,["SS",2],0,function(){return~~(this.millisecond()/10)}),J(0,["SSS",3],0,"millisecond"),J(0,["SSSS",4],0,function(){return 10*this.millisecond()}),J(0,["SSSSS",5],0,function(){return 100*this.millisecond()}),J(0,["SSSSSS",6],0,function(){return 1e3*this.millisecond()}),J(0,["SSSSSSS",7],0,function(){return 1e4*this.millisecond()}),J(0,["SSSSSSSS",8],0,function(){return 1e5*this.millisecond()}),J(0,["SSSSSSSSS",9],0,function(){return 1e6*this.millisecond()}),A("millisecond","ms"),O("S",gd,$c),O("SS",gd,_c),O("SSS",gd,ad);var be;for(be="SSSS";be.length<=9;be+="S")O(be,jd);for(be="S";be.length<=9;be+="S")R(be,dc);var ce=E("Milliseconds",!1);J("z",0,0,"zoneAbbr"),J("zz",0,0,"zoneName");var de=o.prototype;de.add=Rd,de.calendar=_a,de.clone=ab,de.diff=hb,de.endOf=tb,de.format=lb,de.from=mb,de.fromNow=nb,de.to=ob,de.toNow=pb,de.get=H,de.invalidAt=Cb,de.isAfter=bb,de.isBefore=cb,de.isBetween=db,de.isSame=eb,de.isSameOrAfter=fb,de.isSameOrBefore=gb,de.isValid=Ab,de.lang=Td,de.locale=qb,de.localeData=rb,de.max=Md,de.min=Ld,de.parsingFlags=Bb,de.set=H,de.startOf=sb,de.subtract=Sd,de.toArray=xb,de.toObject=yb,de.toDate=wb,de.toISOString=kb,de.toJSON=zb,de.toString=jb,de.unix=vb,de.valueOf=ub,de.creationData=Db,de.year=Kd,de.isLeapYear=ja,de.weekYear=Fb,de.isoWeekYear=Gb,de.quarter=de.quarters=Lb,de.month=Z,de.daysInMonth=$,de.week=de.weeks=Pb,de.isoWeek=de.isoWeeks=Qb,de.weeksInYear=Ib,de.isoWeeksInYear=Hb,de.date=Vd,de.day=de.days=Wb,de.weekday=Xb,de.isoWeekday=Yb,de.dayOfYear=Zb,de.hour=de.hours=$d,de.minute=de.minutes=_d,de.second=de.seconds=ae,de.millisecond=de.milliseconds=ce,de.utcOffset=Ka,de.utc=Ma,de.local=Na,de.parseZone=Oa,de.hasAlignedHourOffset=Pa,de.isDST=Qa,de.isDSTShifted=Ra,de.isLocal=Sa,de.isUtcOffset=Ta,de.isUtc=Ua,de.isUTC=Ua,de.zoneAbbr=ec,de.zoneName=fc,de.dates=ba("dates accessor is deprecated. Use date instead.",Vd),de.months=ba("months accessor is deprecated. Use month instead",Z),de.years=ba("years accessor is deprecated. Use year instead",Kd),de.zone=ba("moment().zone is deprecated, use moment().utcOffset instead. https://github.com/moment/moment/issues/1779",La);var ee=de,fe={sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},ge={LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},he="Invalid date",ie="%d",je=/\d{1,2}/,ke={future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},le=t.prototype;le._calendar=fe,le.calendar=ic,le._longDateFormat=ge,le.longDateFormat=jc,le._invalidDate=he,le.invalidDate=kc,le._ordinal=ie,le.ordinal=lc,le._ordinalParse=je,le.preparse=mc,le.postformat=mc,le._relativeTime=ke,le.relativeTime=nc,le.pastFuture=oc,le.set=pc,le.months=V,le._months=Bd,le.monthsShort=W,le._monthsShort=Cd,le.monthsParse=X,le.week=Mb,le._week=Ud,le.firstDayOfYear=Ob,le.firstDayOfWeek=Nb,le.weekdays=Sb,le._weekdays=Wd,le.weekdaysMin=Ub,le._weekdaysMin=Yd,le.weekdaysShort=Tb,le._weekdaysShort=Xd,le.weekdaysParse=Vb,le.isPM=bc,le._meridiemParse=Zd,le.meridiem=cc,x("en",{monthsParse:[/^jan/i,/^feb/i,/^mar/i,/^apr/i,/^may/i,/^jun/i,/^jul/i,/^aug/i,/^sep/i,/^oct/i,/^nov/i,/^dec/i],longMonthsParse:[/^january$/i,/^february$/i,/^march$/i,/^april$/i,/^may$/i,/^june$/i,/^july$/i,/^august$/i,/^september$/i,/^october$/i,/^november$/i,/^december$/i],shortMonthsParse:[/^jan$/i,/^feb$/i,/^mar$/i,/^apr$/i,/^may$/i,/^jun$/i,/^jul$/i,/^aug/i,/^sept?$/i,/^oct$/i,/^nov$/i,/^dec$/i],ordinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(a){var b=a%10,c=1===r(a%100/10)?"th":1===b?"st":2===b?"nd":3===b?"rd":"th";return a+c}}),a.lang=ba("moment.lang is deprecated. Use moment.locale instead.",x),a.langData=ba("moment.langData is deprecated. Use moment.localeData instead.",z);var me=Math.abs,ne=Hc("ms"),oe=Hc("s"),pe=Hc("m"),qe=Hc("h"),re=Hc("d"),se=Hc("w"),te=Hc("M"),ue=Hc("y"),ve=Jc("milliseconds"),we=Jc("seconds"),xe=Jc("minutes"),ye=Jc("hours"),ze=Jc("days"),Ae=Jc("months"),Be=Jc("years"),Ce=Math.round,De={s:45,m:45,h:22,d:26,M:11},Ee=Math.abs,Fe=Ea.prototype;Fe.abs=xc,Fe.add=zc,Fe.subtract=Ac,Fe.as=Fc,Fe.asMilliseconds=ne,Fe.asSeconds=oe,Fe.asMinutes=pe,Fe.asHours=qe,Fe.asDays=re,Fe.asWeeks=se,Fe.asMonths=te,Fe.asYears=ue,Fe.valueOf=Gc,Fe._bubble=Cc,Fe.get=Ic,Fe.milliseconds=ve,Fe.seconds=we,Fe.minutes=xe,Fe.hours=ye,Fe.days=ze,Fe.weeks=Kc,Fe.months=Ae,Fe.years=Be,Fe.humanize=Oc,Fe.toISOString=Pc,Fe.toString=Pc,Fe.toJSON=Pc,Fe.locale=qb,Fe.localeData=rb,Fe.toIsoString=ba("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",Pc),Fe.lang=Td,J("X",0,0,"unix"),J("x",0,0,"valueOf"),O("x",kd),O("X",nd),R("X",function(a,b,c){c._d=new Date(1e3*parseFloat(a,10))}),R("x",function(a,b,c){c._d=new Date(r(a))}),a.version="2.11.0",b(Aa),a.fn=ee,a.min=Ca,a.max=Da,a.now=Nd,a.utc=h,a.unix=gc,a.months=sc,a.isDate=d,a.locale=x,a.invalid=l,a.duration=Va,a.isMoment=p,a.weekdays=uc,a.parseZone=hc,a.localeData=z,a.isDuration=Fa,a.monthsShort=tc,a.weekdaysMin=wc,a.defineLocale=y,a.weekdaysShort=vc,a.normalizeUnits=B,a.relativeTimeThreshold=Nc,a.prototype=ee;var Ge=a;return Ge}); -------------------------------------------------------------------------------- /clean/quandl-api-key.js: -------------------------------------------------------------------------------- 1 | var quandlApiKey = 'Fp6cFhibc5xvL2pN3dnu'; -------------------------------------------------------------------------------- /formatWorker.js: -------------------------------------------------------------------------------- 1 | importScripts('lib/lazy.js'); 2 | importScripts('lib/strategyHelpers.js'); 3 | importScripts('lib/dataHelpers.js'); 4 | 5 | //***************************************************** 6 | 7 | self.addEventListener( 8 | 'message', 9 | function(e) { 10 | var data = e.data[0]; 11 | var horizon = e.data[1]; 12 | 13 | //clean 14 | cd = data.cleanData(); 15 | //format 16 | self.postMessage(cd); 17 | }, 18 | false 19 | ); 20 | 21 | 22 | //***************************************************** 23 | 24 | -------------------------------------------------------------------------------- /graph.css: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | #graph{ 4 | padding-left: 1em; 5 | } 6 | */ 7 | .axis path, 8 | .axis line { 9 | fill: none; 10 | stroke: #000; 11 | shape-rendering: crispEdges; 12 | } 13 | .x.axis path { 14 | display: none; 15 | } 16 | .y.axis path { 17 | display: none; 18 | } 19 | .overlay { 20 | fill: none; 21 | pointer-events: all; 22 | } 23 | .line { 24 | fill: none; 25 | stroke-width: 0.1em; 26 | } 27 | .dateLine { 28 | fill: black; 29 | stroke: black; 30 | stroke-width: 0.5; 31 | } 32 | .focus rect { 33 | fill: white; 34 | fill-opacity: 0.75; 35 | stroke:white; 36 | stroke-opacity: 1; 37 | } 38 | 39 | /* 40 | #graph { 41 | margin-left: 1em; 42 | } 43 | */ 44 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 21 | 22 | 23 | 24 | 25 | Lazy Backtest IDE 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 45 | 90 | 91 | 92 | 93 | 94 | 95 | 96 |
97 | 98 | 99 | 100 |
Lazy Backtest IDE
101 | 102 |
103 |

1) Set the strategy's holding period

104 | 105 |
106 | 112 |
113 | 114 |
115 | 116 |

2) Pull required data down from Quandl by specifying sources and tickers

117 | 118 | 132 |
133 | 140 | 141 | Yield 142 | 143 | ADD 144 |
145 | 146 |
147 | 153 | 154 | Yield 155 | 156 | ADD 157 |
158 | 159 |
160 | 161 |
162 | 163 | 164 | 165 |
166 | 167 |

3) Code your strategy in the browser

168 | 169 |
170 | 171 |
172 | 173 |

4) Hit the back test link

174 | 175 |

Back Test

176 | 177 | 178 |
179 | 180 | 181 | 182 | 183 | Download CSV file 184 | 185 |
186 | 187 |

(Results are "Big O", i.e. conservative)

188 |
189 | 190 | 191 | 192 | 193 | 200 |
201 | 202 | 209 | 210 |
211 | 212 |

Optional: input a Quandl key for unlimited free data

213 |
214 | 215 |
216 | 217 |
218 | 219 |

API Details

220 | 221 |

Need help? Watch the screencast:

222 | 223 | 224 | 225 |
226 | 227 | 228 | 848 | 849 | -------------------------------------------------------------------------------- /lib/codemirror.css: -------------------------------------------------------------------------------- 1 | /* BASICS */ 2 | 3 | .CodeMirror { 4 | /* Set height, width, borders, and global font properties here */ 5 | font-family: monospace; 6 | height: 300px; 7 | color: black; 8 | } 9 | 10 | /* PADDING */ 11 | 12 | .CodeMirror-lines { 13 | padding: 4px 0; /* Vertical padding around content */ 14 | } 15 | .CodeMirror pre { 16 | padding: 0 4px; /* Horizontal padding of content */ 17 | } 18 | 19 | .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { 20 | background-color: white; /* The little square between H and V scrollbars */ 21 | } 22 | 23 | /* GUTTER */ 24 | 25 | .CodeMirror-gutters { 26 | border-right: 1px solid #ddd; 27 | background-color: #f7f7f7; 28 | white-space: nowrap; 29 | } 30 | .CodeMirror-linenumbers {} 31 | .CodeMirror-linenumber { 32 | padding: 0 3px 0 5px; 33 | min-width: 20px; 34 | text-align: right; 35 | color: #999; 36 | white-space: nowrap; 37 | } 38 | 39 | .CodeMirror-guttermarker { color: black; } 40 | .CodeMirror-guttermarker-subtle { color: #999; } 41 | 42 | /* CURSOR */ 43 | 44 | .CodeMirror div.CodeMirror-cursor { 45 | border-left: 1px solid black; 46 | } 47 | /* Shown when moving in bi-directional text */ 48 | .CodeMirror div.CodeMirror-secondarycursor { 49 | border-left: 1px solid silver; 50 | } 51 | .CodeMirror.cm-fat-cursor div.CodeMirror-cursor { 52 | width: auto; 53 | border: 0; 54 | background: #7e7; 55 | } 56 | .CodeMirror.cm-fat-cursor div.CodeMirror-cursors { 57 | z-index: 1; 58 | } 59 | 60 | .cm-animate-fat-cursor { 61 | width: auto; 62 | border: 0; 63 | -webkit-animation: blink 1.06s steps(1) infinite; 64 | -moz-animation: blink 1.06s steps(1) infinite; 65 | animation: blink 1.06s steps(1) infinite; 66 | } 67 | @-moz-keyframes blink { 68 | 0% { background: #7e7; } 69 | 50% { background: none; } 70 | 100% { background: #7e7; } 71 | } 72 | @-webkit-keyframes blink { 73 | 0% { background: #7e7; } 74 | 50% { background: none; } 75 | 100% { background: #7e7; } 76 | } 77 | @keyframes blink { 78 | 0% { background: #7e7; } 79 | 50% { background: none; } 80 | 100% { background: #7e7; } 81 | } 82 | 83 | /* Can style cursor different in overwrite (non-insert) mode */ 84 | div.CodeMirror-overwrite div.CodeMirror-cursor {} 85 | 86 | .cm-tab { display: inline-block; text-decoration: inherit; } 87 | 88 | .CodeMirror-ruler { 89 | border-left: 1px solid #ccc; 90 | position: absolute; 91 | } 92 | 93 | /* DEFAULT THEME */ 94 | 95 | .cm-s-default .cm-keyword {color: #708;} 96 | .cm-s-default .cm-atom {color: #219;} 97 | .cm-s-default .cm-number {color: #164;} 98 | .cm-s-default .cm-def {color: #00f;} 99 | .cm-s-default .cm-variable, 100 | .cm-s-default .cm-punctuation, 101 | .cm-s-default .cm-property, 102 | .cm-s-default .cm-operator {} 103 | .cm-s-default .cm-variable-2 {color: #05a;} 104 | .cm-s-default .cm-variable-3 {color: #085;} 105 | .cm-s-default .cm-comment {color: #a50;} 106 | .cm-s-default .cm-string {color: #a11;} 107 | .cm-s-default .cm-string-2 {color: #f50;} 108 | .cm-s-default .cm-meta {color: #555;} 109 | .cm-s-default .cm-qualifier {color: #555;} 110 | .cm-s-default .cm-builtin {color: #30a;} 111 | .cm-s-default .cm-bracket {color: #997;} 112 | .cm-s-default .cm-tag {color: #170;} 113 | .cm-s-default .cm-attribute {color: #00c;} 114 | .cm-s-default .cm-header {color: blue;} 115 | .cm-s-default .cm-quote {color: #090;} 116 | .cm-s-default .cm-hr {color: #999;} 117 | .cm-s-default .cm-link {color: #00c;} 118 | 119 | .cm-negative {color: #d44;} 120 | .cm-positive {color: #292;} 121 | .cm-header, .cm-strong {font-weight: bold;} 122 | .cm-em {font-style: italic;} 123 | .cm-link {text-decoration: underline;} 124 | .cm-strikethrough {text-decoration: line-through;} 125 | 126 | .cm-s-default .cm-error {color: #f00;} 127 | .cm-invalidchar {color: #f00;} 128 | 129 | .CodeMirror-composing { border-bottom: 2px solid; } 130 | 131 | /* Default styles for common addons */ 132 | 133 | div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} 134 | div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} 135 | .CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); } 136 | .CodeMirror-activeline-background {background: #e8f2ff;} 137 | 138 | /* STOP */ 139 | 140 | /* The rest of this file contains styles related to the mechanics of 141 | the editor. You probably shouldn't touch them. */ 142 | 143 | .CodeMirror { 144 | position: relative; 145 | overflow: hidden; 146 | background: white; 147 | } 148 | 149 | .CodeMirror-scroll { 150 | overflow: scroll !important; /* Things will break if this is overridden */ 151 | /* 30px is the magic margin used to hide the element's real scrollbars */ 152 | /* See overflow: hidden in .CodeMirror */ 153 | margin-bottom: -30px; margin-right: -30px; 154 | padding-bottom: 30px; 155 | height: 100%; 156 | outline: none; /* Prevent dragging from highlighting the element */ 157 | position: relative; 158 | } 159 | .CodeMirror-sizer { 160 | position: relative; 161 | border-right: 30px solid transparent; 162 | } 163 | 164 | /* The fake, visible scrollbars. Used to force redraw during scrolling 165 | before actuall scrolling happens, thus preventing shaking and 166 | flickering artifacts. */ 167 | .CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { 168 | position: absolute; 169 | z-index: 6; 170 | display: none; 171 | } 172 | .CodeMirror-vscrollbar { 173 | right: 0; top: 0; 174 | overflow-x: hidden; 175 | overflow-y: scroll; 176 | } 177 | .CodeMirror-hscrollbar { 178 | bottom: 0; left: 0; 179 | overflow-y: hidden; 180 | overflow-x: scroll; 181 | } 182 | .CodeMirror-scrollbar-filler { 183 | right: 0; bottom: 0; 184 | } 185 | .CodeMirror-gutter-filler { 186 | left: 0; bottom: 0; 187 | } 188 | 189 | .CodeMirror-gutters { 190 | position: absolute; left: 0; top: 0; 191 | z-index: 3; 192 | } 193 | .CodeMirror-gutter { 194 | white-space: normal; 195 | height: 100%; 196 | display: inline-block; 197 | margin-bottom: -30px; 198 | /* Hack to make IE7 behave */ 199 | *zoom:1; 200 | *display:inline; 201 | } 202 | .CodeMirror-gutter-wrapper { 203 | position: absolute; 204 | z-index: 4; 205 | height: 100%; 206 | } 207 | .CodeMirror-gutter-elt { 208 | position: absolute; 209 | cursor: default; 210 | z-index: 4; 211 | } 212 | .CodeMirror-gutter-wrapper { 213 | -webkit-user-select: none; 214 | -moz-user-select: none; 215 | user-select: none; 216 | } 217 | 218 | .CodeMirror-lines { 219 | cursor: text; 220 | min-height: 1px; /* prevents collapsing before first draw */ 221 | } 222 | .CodeMirror pre { 223 | /* Reset some styles that the rest of the page might have set */ 224 | -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; 225 | border-width: 0; 226 | background: transparent; 227 | font-family: inherit; 228 | font-size: inherit; 229 | margin: 0; 230 | white-space: pre; 231 | word-wrap: normal; 232 | line-height: inherit; 233 | color: inherit; 234 | z-index: 2; 235 | position: relative; 236 | overflow: visible; 237 | -webkit-tap-highlight-color: transparent; 238 | } 239 | .CodeMirror-wrap pre { 240 | word-wrap: break-word; 241 | white-space: pre-wrap; 242 | word-break: normal; 243 | } 244 | 245 | .CodeMirror-linebackground { 246 | position: absolute; 247 | left: 0; right: 0; top: 0; bottom: 0; 248 | z-index: 0; 249 | } 250 | 251 | .CodeMirror-linewidget { 252 | position: relative; 253 | z-index: 2; 254 | overflow: auto; 255 | } 256 | 257 | .CodeMirror-widget {} 258 | 259 | .CodeMirror-code { 260 | outline: none; 261 | } 262 | 263 | /* Force content-box sizing for the elements where we expect it */ 264 | .CodeMirror-scroll, 265 | .CodeMirror-sizer, 266 | .CodeMirror-gutter, 267 | .CodeMirror-gutters, 268 | .CodeMirror-linenumber { 269 | -moz-box-sizing: content-box; 270 | box-sizing: content-box; 271 | } 272 | 273 | .CodeMirror-measure { 274 | position: absolute; 275 | width: 100%; 276 | height: 0; 277 | overflow: hidden; 278 | visibility: hidden; 279 | } 280 | .CodeMirror-measure pre { position: static; } 281 | 282 | .CodeMirror div.CodeMirror-cursor { 283 | position: absolute; 284 | border-right: none; 285 | width: 0; 286 | } 287 | 288 | div.CodeMirror-cursors { 289 | visibility: hidden; 290 | position: relative; 291 | z-index: 3; 292 | } 293 | .CodeMirror-focused div.CodeMirror-cursors { 294 | visibility: visible; 295 | } 296 | 297 | .CodeMirror-selected { background: #d9d9d9; } 298 | .CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; } 299 | .CodeMirror-crosshair { cursor: crosshair; } 300 | .CodeMirror ::selection { background: #d7d4f0; } 301 | .CodeMirror ::-moz-selection { background: #d7d4f0; } 302 | 303 | .cm-searching { 304 | background: #ffa; 305 | background: rgba(255, 255, 0, .4); 306 | } 307 | 308 | /* IE7 hack to prevent it from returning funny offsetTops on the spans */ 309 | .CodeMirror span { *vertical-align: text-bottom; } 310 | 311 | /* Used to force a border model for a node */ 312 | .cm-force-border { padding-right: .1px; } 313 | 314 | @media print { 315 | /* Hide the cursor when printing */ 316 | .CodeMirror div.CodeMirror-cursors { 317 | visibility: hidden; 318 | } 319 | } 320 | 321 | /* See issue #2901 */ 322 | .cm-tab-wrap-hack:after { content: ''; } 323 | 324 | /* Help users use markselection to safely style text background */ 325 | span.CodeMirror-selectedtext { background: none; } 326 | -------------------------------------------------------------------------------- /lib/dataHelpers.js: -------------------------------------------------------------------------------- 1 | 2 | //***************************************************** 3 | //ensure all data has a matching date 4 | var cleanData = function(){ 5 | var _this = this; 6 | return Lazy(this) 7 | .reduce(function(acc,c){return acc.concat(c.prices);},Lazy([])) 8 | //ensure dates are matched for every assetall the way down the line 9 | .groupBy(function(i){return i.date }) 10 | .filter(function(i){return i.length === _this.length;}) 11 | .toArray() 12 | .map( 13 | function(i){ 14 | return i[1]; 15 | } 16 | ) 17 | .reverse() 18 | ; 19 | } 20 | 21 | Object.defineProperty( 22 | Object.prototype, 23 | 'cleanData', 24 | { 25 | value: cleanData, 26 | writable: true, 27 | configurable: true, 28 | enumerable: false 29 | } 30 | ); 31 | 32 | -------------------------------------------------------------------------------- /lib/graphs.js: -------------------------------------------------------------------------------- 1 | /* 2 | function resize(){ 3 | window.onresize = function(){ 4 | document.getElementsByTagName("svg")[0].style.width = window.innerWidth * 0.9; 5 | document.getElementsByTagName("svg")[0].style.height = window.innerHeight * 0.9; 6 | }; 7 | } 8 | */ 9 | 10 | function shim(ddd,gts){ 11 | var d=new Graph(); 12 | d.draw(ddd,false,gts); 13 | } 14 | 15 | function Graph(){ 16 | 17 | //filter return series or not 18 | //var filter = null; 19 | 20 | //!use check boxes to store this data! 21 | var graphTimeSeries = ["STRATEGY_return"]; 22 | datax = null; 23 | 24 | /* 25 | orig data is needed to load up 26 | orig data is global 27 | update/change graphTimeSeries each time 28 | include optional graphTimeSeries input in draw function 29 | */ 30 | 31 | //********************************************************************************************** 32 | //setup 33 | function appendCheckBox(timeSeries, allReturns){ 34 | var p = document.createElement('p'); 35 | var text = document.createTextNode(timeSeries); 36 | 37 | document.getElementById('graph').appendChild(p); 38 | 39 | var input = document.createElement('input'); 40 | input.type="checkbox"; 41 | 42 | input .addEventListener( 43 | "change", 44 | function(){ 45 | //find whether to add or remove 46 | var index = graphTimeSeries.indexOf(timeSeries); 47 | //add or remove 48 | index === -1 ? graphTimeSeries.push(timeSeries) : graphTimeSeries.splice(index,1); 49 | 50 | update(allReturns.slice()); 51 | } 52 | ); 53 | //check strategy checkbox by default 54 | timeSeries === "STRATEGY_return" ? input.checked = true : input.checked = false; 55 | 56 | p.appendChild(input); 57 | p.appendChild(text); 58 | } 59 | 60 | 61 | 62 | function update(allReturns){ 63 | 64 | var _this = this; 65 | 66 | returns = allReturns.filter( 67 | function(i){ 68 | return graphTimeSeries.indexOf(i.name) != -1; 69 | } 70 | ); 71 | 72 | //need to get relevant data 73 | //add relevant data to tag string 74 | var importString = '
'; 75 | 76 | 77 | var datay = datax 78 | .slice() 79 | .map( 80 | function(i){ 81 | var j = _.pick(i,graphTimeSeries); 82 | j.date = i.date.getFullYear() + "-" + (i.date.getMonth()+1) + "-" + (i.date.getDate()); 83 | return j; 84 | } 85 | ); 86 | 87 | //console.log(datay); 88 | 89 | var s1 = ''; 90 | var s2 = ''; 91 | 92 | document.getElementById('codex') != null ? document.getElementById('codex').value = (s1+importString+s2) : null; 93 | 94 | //******************************************* 95 | //update axes 96 | x.domain(d3.extent(returns[0].values, function(d) { return d.date; })); 97 | 98 | y.domain([ 99 | d3.min(returns, function(c) { return d3.min(c.values, function(v) { return v.r; }); }), 100 | d3.max(returns, function(c) { return d3.max(c.values, function(v) { return v.r; }); }) 101 | ]); 102 | 103 | var xAxis = d3 104 | .svg 105 | .axis() 106 | .scale(x) 107 | .orient("bottom") 108 | .ticks(2); 109 | 110 | var yAxis = d3 111 | .svg 112 | .axis() 113 | .scale(y) 114 | .orient("left") 115 | .ticks(2); 116 | 117 | d3 .select(".x.axis") 118 | .transition() 119 | .duration(750) 120 | .call(xAxis); 121 | 122 | d3 .select(".y.axis") 123 | .transition() 124 | .duration(750) 125 | .call(yAxis.tickFormat( formatAxisLabel )); 126 | 127 | //******************************************* 128 | //return path 129 | 130 | //remove all 131 | d3 .select('#base') 132 | .selectAll(".returnPath") 133 | .remove(); 134 | 135 | //append 136 | d3 .select('#base') 137 | .selectAll(".returnPath") 138 | .data( returns ) 139 | .enter() 140 | .append("g") 141 | .attr("class", "returnPath") 142 | .attr("id", function(d){return d.name;}) 143 | .append("path") 144 | .attr("class", "line") 145 | .style("stroke", function(d) { return color(d.name); }) 146 | .attr("d", function(d) { return line(d.values); }) 147 | .transition() 148 | .duration(750); 149 | } 150 | 151 | //****************** 152 | //choose smallest dimension between parent's width and window height 153 | 154 | var worh = document.querySelector("#graph").parentNode.offsetWidth; 155 | 156 | worh > window.innerHeight ? 157 | worh = window.innerHeight 158 | : 159 | null; 160 | 161 | var margin = { 162 | bottom: worh*0.05, 163 | top: worh*0.05, 164 | right: worh*0, 165 | left: worh*0.1 166 | }; 167 | 168 | var width = worh - margin.left - margin.right; 169 | 170 | var height = worh - margin.top - margin.bottom; 171 | 172 | //****************** 173 | 174 | var x = d3 175 | .time 176 | .scale() 177 | .range([0, width]); 178 | 179 | var y = d3 180 | .scale 181 | .linear() 182 | .range([height, 0]); 183 | 184 | //****************** 185 | 186 | 187 | var color = d3 188 | .scale 189 | .category10(); 190 | 191 | var line = d3 192 | .svg 193 | .line() 194 | .interpolate("basis") 195 | .x(function(d) { return x(d.date); }) 196 | .y(function(d) { return y(d.r); }); 197 | 198 | //****************** 199 | 200 | 201 | var formatAxisLabel = function(d){ 202 | return (d * 100).toLocaleString()+"%"; 203 | } 204 | 205 | 206 | //graph object 207 | //change domain, lines 208 | //update lines and axes 209 | 210 | this.draw = function(data, controlsFlag, gts){ 211 | gts != null ? graphTimeSeries = gts : null; 212 | 213 | datax = data.slice(); 214 | 215 | //gts != null ? graphTimeSeries = gts : null; 216 | 217 | //if controls then filter 218 | controlsFlag ? filter = true : filter = false; 219 | 220 | //****************** 221 | 222 | var xAxis = d3 223 | .svg 224 | .axis() 225 | .scale(x) 226 | .orient("bottom") 227 | .ticks(2); 228 | 229 | var yAxis = d3 230 | .svg 231 | .axis() 232 | .scale(y) 233 | .orient("left") 234 | .ticks(2); 235 | 236 | //****************** 237 | 238 | var parseDate = d3.time.format("%Y-%m-%d").parse; 239 | 240 | 241 | 242 | //array with element per time series 243 | var bisectDate = d3 244 | .bisector(function(d) { return d.date; }) 245 | .left; 246 | 247 | //********************************************************************************************** 248 | //setup 249 | 250 | 251 | //remove anything pre-existing 252 | d3 .select("svg") 253 | .remove(); 254 | 255 | d3 .select("#graph") 256 | .selectAll('p') 257 | .remove(); 258 | 259 | //********************************************************************************************** 260 | 261 | var svg = d3 262 | .select("#graph") 263 | .append("svg") 264 | //.attr("viewBox","0 0 "+(width + margin.left + margin.right)+" "+(height + margin.top + margin.bottom)) 265 | //.attr("preserveAspectRatio","xMidYMid") 266 | .attr("width", width + margin.left + margin.right) 267 | .attr("height", height + margin.top + margin.bottom) 268 | .append("g") 269 | //.attr('id','base') 270 | .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); 271 | 272 | svg .append("g") 273 | .attr('id','base'); 274 | 275 | //sets colour(?) 276 | color .domain( 277 | d3 .keys(data[0]) 278 | .filter( 279 | function(key) { 280 | return key != "date" && key.indexOf("weight") === -1; 281 | } 282 | ) 283 | ); 284 | 285 | //convert date format 286 | data .forEach( 287 | function(d) { 288 | d.date = parseDate(d.date); 289 | } 290 | ); 291 | 292 | console.log("c"); 293 | 294 | //generate cumulative demeaned returns 295 | var allReturns = color 296 | .domain() 297 | .reduce( 298 | function(aa, name) { 299 | //find mean 300 | var av = jStat( 301 | _.pluck(data,name) 302 | .map(function(i){return +i;}) 303 | ).mean(); 304 | 305 | var factor = null; 306 | var origAv = null; 307 | 308 | aa.length > 0 ? factor = av/aa[0].av : factor = 1; 309 | aa.length > 0 ? origAv = aa[0].av : origAv = av; 310 | 311 | var data2 = data.slice(); 312 | 313 | return aa.concat( 314 | { 315 | name: name, 316 | av: av, 317 | values: data2.reverse().reduce(function(a,d) { 318 | var rr = null; 319 | if (a.length>0){ 320 | //cumulative and demean 321 | rr = +d[name] / factor + a[a.length-1].r - origAv; 322 | } 323 | else{ 324 | rr = +d[name] / factor - origAv; 325 | } 326 | return a.concat({date: d.date, r: rr }); 327 | } 328 | ,[]) 329 | } 330 | ); 331 | } 332 | , [] 333 | ); 334 | 335 | 336 | 337 | 338 | //************************************************************************************ 339 | //************************************************************************************ 340 | //************************************************************************************ 341 | //create object 342 | //two modes: simple & advanced 343 | 344 | 345 | //initially just show strat returns 346 | /* 347 | var returns = allReturns 348 | .filter( 349 | function(i){ 350 | return graphTimeSeries.indexOf(i.name) != -1; 351 | } 352 | ) 353 | ; 354 | */ 355 | 356 | if (controlsFlag === true){ 357 | //append check boxes 358 | color .domain() 359 | .reverse() 360 | .forEach( 361 | function(i){ 362 | appendCheckBox(i, allReturns.slice()); 363 | } 364 | ); 365 | } 366 | 367 | //************************************************************************************ 368 | //************************************************************************************ 369 | //************************************************************************************ 370 | 371 | 372 | 373 | 374 | //initial axis setup 375 | 376 | //xaxis 377 | svg .append("g") 378 | .attr("class", "x axis") 379 | .attr("transform", "translate(0," + height + ")") 380 | .call(xAxis); 381 | //yaxis 382 | svg .append("g") 383 | .attr("class", "y axis") 384 | .call( yAxis.tickFormat( formatAxisLabel ) ) 385 | .append("text") 386 | .attr("transform", "rotate(-90)") 387 | .attr("x", 0) 388 | .attr("y", 6) 389 | .attr("dy", "1em") 390 | .style("text-anchor", "end") 391 | .text("Cumulative Demeaned"); 392 | 393 | 394 | //************************************************************************************ 395 | //setup check boxes 396 | 397 | update( allReturns.slice() ); 398 | 399 | 400 | //************************************************************************************ 401 | //overlay for mouse movement tracking 402 | var dateBox = svg 403 | .append("g") 404 | .attr("class", "focus") 405 | .style("display", "none"); 406 | dateBox 407 | .append("rect") 408 | .attr("width", "8em") 409 | .attr("height", "1.1em"); 410 | dateBox 411 | .append("text") 412 | .attr("x", 9) 413 | .attr("dy", "1em"); 414 | 415 | var toolTips = svg 416 | .append("g") 417 | .attr("class", "toolTips"); 418 | 419 | //append time series tool tips 420 | var f = color .domain() 421 | .reduce( 422 | function(a,i){ 423 | a[i] = toolTips 424 | .append("g") 425 | .attr("class", "focus "+i) 426 | .style("display", "none") 427 | ; 428 | 429 | a[i] .append("rect") 430 | .attr("width", "10em") 431 | .attr("height", "1.1em"); 432 | a[i] .append("text") 433 | .attr("x", 9) 434 | .attr("dy", "1em"); 435 | 436 | return a; 437 | } 438 | , [] 439 | ); 440 | 441 | //vertical date line 442 | var dateLine = svg 443 | .append("g") 444 | .attr("class", "dateLine") 445 | .style("display", "none"); 446 | dateLine 447 | .append("line") 448 | .attr("x1",0) 449 | .attr("y1",0) 450 | .attr("x2",0) 451 | .attr("y2",height); 452 | 453 | 454 | 455 | //append overlay for mouse movement 456 | var overlay = svg 457 | .append("rect") 458 | .attr("class", "overlay"); 459 | overlay 460 | .attr("width", width) 461 | .attr("height", height); 462 | overlay 463 | .on("mouseover", 464 | function() { 465 | 466 | //scrub out tool tips 467 | allReturns.forEach( 468 | function(i){ 469 | f[i.name].style("display", "none"); 470 | } 471 | ); 472 | //show relvant tool tips 473 | graphTimeSeries.forEach( 474 | function(i){ 475 | f[i].style("display", null); 476 | } 477 | ); 478 | 479 | dateBox.style("display", null); 480 | dateLine.style("display", null); 481 | } 482 | ) 483 | .on("mousemove", mousemove); 484 | 485 | 486 | 487 | 488 | //************************************************************************************ 489 | 490 | 491 | 492 | function mousemove() { 493 | var currentDate = x.invert(d3.mouse(this)[0]); 494 | 495 | //strategy return boxes 496 | allReturns.forEach( 497 | function(i){ 498 | var index = bisectDate(i.values, currentDate); 499 | var d = i.values[index]; 500 | f[i.name].attr("transform", "translate(" + x(d.date) + "," + y(d.r) + ")"); 501 | 502 | //remove returns and exchanges 503 | var cleanedTickers = (i.name).substring(0,i.name.length-7); 504 | 505 | var indexof = cleanedTickers.indexOf("_"); 506 | indexof === -1 ? null : cleanedTickers = cleanedTickers.substring(indexof+1);; 507 | 508 | //find first underscore 509 | f[i.name] 510 | .select("text").text(cleanedTickers+": "+Math.round(d.r * 100).toLocaleString()+"%") 511 | .style("stroke", color(i.name) ); 512 | } 513 | ); 514 | 515 | dateLine.attr("transform", "translate(" + x(currentDate) + "," + 0 + ")"); 516 | 517 | dateBox .attr("transform", "translate(" + x(currentDate) + "," + 0 + ")") 518 | .select("text") 519 | .text(currentDate.toLocaleDateString()); 520 | } 521 | } 522 | } 523 | -------------------------------------------------------------------------------- /lib/javascript.js: -------------------------------------------------------------------------------- 1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others 2 | // Distributed under an MIT license: http://codemirror.net/LICENSE 3 | 4 | // TODO actually recognize syntax of TypeScript constructs 5 | 6 | (function(mod) { 7 | if (typeof exports == "object" && typeof module == "object") // CommonJS 8 | mod(require("../../lib/codemirror")); 9 | else if (typeof define == "function" && define.amd) // AMD 10 | define(["../../lib/codemirror"], mod); 11 | else // Plain browser env 12 | mod(CodeMirror); 13 | })(function(CodeMirror) { 14 | "use strict"; 15 | 16 | CodeMirror.defineMode("javascript", function(config, parserConfig) { 17 | var indentUnit = config.indentUnit; 18 | var statementIndent = parserConfig.statementIndent; 19 | var jsonldMode = parserConfig.jsonld; 20 | var jsonMode = parserConfig.json || jsonldMode; 21 | var isTS = parserConfig.typescript; 22 | var wordRE = parserConfig.wordCharacters || /[\w$\xa1-\uffff]/; 23 | 24 | // Tokenizer 25 | 26 | var keywords = function(){ 27 | function kw(type) {return {type: type, style: "keyword"};} 28 | var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c"); 29 | var operator = kw("operator"), atom = {type: "atom", style: "atom"}; 30 | 31 | var jsKeywords = { 32 | "if": kw("if"), "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B, 33 | "return": C, "break": C, "continue": C, "new": C, "delete": C, "throw": C, "debugger": C, 34 | "var": kw("var"), "const": kw("var"), "let": kw("var"), 35 | "function": kw("function"), "catch": kw("catch"), 36 | "for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"), 37 | "in": operator, "typeof": operator, "instanceof": operator, 38 | "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom, 39 | "this": kw("this"), "module": kw("module"), "class": kw("class"), "super": kw("atom"), 40 | "yield": C, "export": kw("export"), "import": kw("import"), "extends": C 41 | }; 42 | 43 | // Extend the 'normal' keywords with the TypeScript language extensions 44 | if (isTS) { 45 | var type = {type: "variable", style: "variable-3"}; 46 | var tsKeywords = { 47 | // object-like things 48 | "interface": kw("interface"), 49 | "extends": kw("extends"), 50 | "constructor": kw("constructor"), 51 | 52 | // scope modifiers 53 | "public": kw("public"), 54 | "private": kw("private"), 55 | "protected": kw("protected"), 56 | "static": kw("static"), 57 | 58 | // types 59 | "string": type, "number": type, "bool": type, "any": type 60 | }; 61 | 62 | for (var attr in tsKeywords) { 63 | jsKeywords[attr] = tsKeywords[attr]; 64 | } 65 | } 66 | 67 | return jsKeywords; 68 | }(); 69 | 70 | var isOperatorChar = /[+\-*&%=<>!?|~^]/; 71 | var isJsonldKeyword = /^@(context|id|value|language|type|container|list|set|reverse|index|base|vocab|graph)"/; 72 | 73 | function readRegexp(stream) { 74 | var escaped = false, next, inSet = false; 75 | while ((next = stream.next()) != null) { 76 | if (!escaped) { 77 | if (next == "/" && !inSet) return; 78 | if (next == "[") inSet = true; 79 | else if (inSet && next == "]") inSet = false; 80 | } 81 | escaped = !escaped && next == "\\"; 82 | } 83 | } 84 | 85 | // Used as scratch variables to communicate multiple values without 86 | // consing up tons of objects. 87 | var type, content; 88 | function ret(tp, style, cont) { 89 | type = tp; content = cont; 90 | return style; 91 | } 92 | function tokenBase(stream, state) { 93 | var ch = stream.next(); 94 | if (ch == '"' || ch == "'") { 95 | state.tokenize = tokenString(ch); 96 | return state.tokenize(stream, state); 97 | } else if (ch == "." && stream.match(/^\d+(?:[eE][+\-]?\d+)?/)) { 98 | return ret("number", "number"); 99 | } else if (ch == "." && stream.match("..")) { 100 | return ret("spread", "meta"); 101 | } else if (/[\[\]{}\(\),;\:\.]/.test(ch)) { 102 | return ret(ch); 103 | } else if (ch == "=" && stream.eat(">")) { 104 | return ret("=>", "operator"); 105 | } else if (ch == "0" && stream.eat(/x/i)) { 106 | stream.eatWhile(/[\da-f]/i); 107 | return ret("number", "number"); 108 | } else if (/\d/.test(ch)) { 109 | stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/); 110 | return ret("number", "number"); 111 | } else if (ch == "/") { 112 | if (stream.eat("*")) { 113 | state.tokenize = tokenComment; 114 | return tokenComment(stream, state); 115 | } else if (stream.eat("/")) { 116 | stream.skipToEnd(); 117 | return ret("comment", "comment"); 118 | } else if (state.lastType == "operator" || state.lastType == "keyword c" || 119 | state.lastType == "sof" || /^[\[{}\(,;:]$/.test(state.lastType)) { 120 | readRegexp(stream); 121 | stream.match(/^\b(([gimyu])(?![gimyu]*\2))+\b/); 122 | return ret("regexp", "string-2"); 123 | } else { 124 | stream.eatWhile(isOperatorChar); 125 | return ret("operator", "operator", stream.current()); 126 | } 127 | } else if (ch == "`") { 128 | state.tokenize = tokenQuasi; 129 | return tokenQuasi(stream, state); 130 | } else if (ch == "#") { 131 | stream.skipToEnd(); 132 | return ret("error", "error"); 133 | } else if (isOperatorChar.test(ch)) { 134 | stream.eatWhile(isOperatorChar); 135 | return ret("operator", "operator", stream.current()); 136 | } else if (wordRE.test(ch)) { 137 | stream.eatWhile(wordRE); 138 | var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word]; 139 | return (known && state.lastType != ".") ? ret(known.type, known.style, word) : 140 | ret("variable", "variable", word); 141 | } 142 | } 143 | 144 | function tokenString(quote) { 145 | return function(stream, state) { 146 | var escaped = false, next; 147 | if (jsonldMode && stream.peek() == "@" && stream.match(isJsonldKeyword)){ 148 | state.tokenize = tokenBase; 149 | return ret("jsonld-keyword", "meta"); 150 | } 151 | while ((next = stream.next()) != null) { 152 | if (next == quote && !escaped) break; 153 | escaped = !escaped && next == "\\"; 154 | } 155 | if (!escaped) state.tokenize = tokenBase; 156 | return ret("string", "string"); 157 | }; 158 | } 159 | 160 | function tokenComment(stream, state) { 161 | var maybeEnd = false, ch; 162 | while (ch = stream.next()) { 163 | if (ch == "/" && maybeEnd) { 164 | state.tokenize = tokenBase; 165 | break; 166 | } 167 | maybeEnd = (ch == "*"); 168 | } 169 | return ret("comment", "comment"); 170 | } 171 | 172 | function tokenQuasi(stream, state) { 173 | var escaped = false, next; 174 | while ((next = stream.next()) != null) { 175 | if (!escaped && (next == "`" || next == "$" && stream.eat("{"))) { 176 | state.tokenize = tokenBase; 177 | break; 178 | } 179 | escaped = !escaped && next == "\\"; 180 | } 181 | return ret("quasi", "string-2", stream.current()); 182 | } 183 | 184 | var brackets = "([{}])"; 185 | // This is a crude lookahead trick to try and notice that we're 186 | // parsing the argument patterns for a fat-arrow function before we 187 | // actually hit the arrow token. It only works if the arrow is on 188 | // the same line as the arguments and there's no strange noise 189 | // (comments) in between. Fallback is to only notice when we hit the 190 | // arrow, and not declare the arguments as locals for the arrow 191 | // body. 192 | function findFatArrow(stream, state) { 193 | if (state.fatArrowAt) state.fatArrowAt = null; 194 | var arrow = stream.string.indexOf("=>", stream.start); 195 | if (arrow < 0) return; 196 | 197 | var depth = 0, sawSomething = false; 198 | for (var pos = arrow - 1; pos >= 0; --pos) { 199 | var ch = stream.string.charAt(pos); 200 | var bracket = brackets.indexOf(ch); 201 | if (bracket >= 0 && bracket < 3) { 202 | if (!depth) { ++pos; break; } 203 | if (--depth == 0) break; 204 | } else if (bracket >= 3 && bracket < 6) { 205 | ++depth; 206 | } else if (wordRE.test(ch)) { 207 | sawSomething = true; 208 | } else if (/["'\/]/.test(ch)) { 209 | return; 210 | } else if (sawSomething && !depth) { 211 | ++pos; 212 | break; 213 | } 214 | } 215 | if (sawSomething && !depth) state.fatArrowAt = pos; 216 | } 217 | 218 | // Parser 219 | 220 | var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true, "this": true, "jsonld-keyword": true}; 221 | 222 | function JSLexical(indented, column, type, align, prev, info) { 223 | this.indented = indented; 224 | this.column = column; 225 | this.type = type; 226 | this.prev = prev; 227 | this.info = info; 228 | if (align != null) this.align = align; 229 | } 230 | 231 | function inScope(state, varname) { 232 | for (var v = state.localVars; v; v = v.next) 233 | if (v.name == varname) return true; 234 | for (var cx = state.context; cx; cx = cx.prev) { 235 | for (var v = cx.vars; v; v = v.next) 236 | if (v.name == varname) return true; 237 | } 238 | } 239 | 240 | function parseJS(state, style, type, content, stream) { 241 | var cc = state.cc; 242 | // Communicate our context to the combinators. 243 | // (Less wasteful than consing up a hundred closures on every call.) 244 | cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc; cx.style = style; 245 | 246 | if (!state.lexical.hasOwnProperty("align")) 247 | state.lexical.align = true; 248 | 249 | while(true) { 250 | var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement; 251 | if (combinator(type, content)) { 252 | while(cc.length && cc[cc.length - 1].lex) 253 | cc.pop()(); 254 | if (cx.marked) return cx.marked; 255 | if (type == "variable" && inScope(state, content)) return "variable-2"; 256 | return style; 257 | } 258 | } 259 | } 260 | 261 | // Combinator utils 262 | 263 | var cx = {state: null, column: null, marked: null, cc: null}; 264 | function pass() { 265 | for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]); 266 | } 267 | function cont() { 268 | pass.apply(null, arguments); 269 | return true; 270 | } 271 | function register(varname) { 272 | function inList(list) { 273 | for (var v = list; v; v = v.next) 274 | if (v.name == varname) return true; 275 | return false; 276 | } 277 | var state = cx.state; 278 | if (state.context) { 279 | cx.marked = "def"; 280 | if (inList(state.localVars)) return; 281 | state.localVars = {name: varname, next: state.localVars}; 282 | } else { 283 | if (inList(state.globalVars)) return; 284 | if (parserConfig.globalVars) 285 | state.globalVars = {name: varname, next: state.globalVars}; 286 | } 287 | } 288 | 289 | // Combinators 290 | 291 | var defaultVars = {name: "this", next: {name: "arguments"}}; 292 | function pushcontext() { 293 | cx.state.context = {prev: cx.state.context, vars: cx.state.localVars}; 294 | cx.state.localVars = defaultVars; 295 | } 296 | function popcontext() { 297 | cx.state.localVars = cx.state.context.vars; 298 | cx.state.context = cx.state.context.prev; 299 | } 300 | function pushlex(type, info) { 301 | var result = function() { 302 | var state = cx.state, indent = state.indented; 303 | if (state.lexical.type == "stat") indent = state.lexical.indented; 304 | else for (var outer = state.lexical; outer && outer.type == ")" && outer.align; outer = outer.prev) 305 | indent = outer.indented; 306 | state.lexical = new JSLexical(indent, cx.stream.column(), type, null, state.lexical, info); 307 | }; 308 | result.lex = true; 309 | return result; 310 | } 311 | function poplex() { 312 | var state = cx.state; 313 | if (state.lexical.prev) { 314 | if (state.lexical.type == ")") 315 | state.indented = state.lexical.indented; 316 | state.lexical = state.lexical.prev; 317 | } 318 | } 319 | poplex.lex = true; 320 | 321 | function expect(wanted) { 322 | function exp(type) { 323 | if (type == wanted) return cont(); 324 | else if (wanted == ";") return pass(); 325 | else return cont(exp); 326 | }; 327 | return exp; 328 | } 329 | 330 | function statement(type, value) { 331 | if (type == "var") return cont(pushlex("vardef", value.length), vardef, expect(";"), poplex); 332 | if (type == "keyword a") return cont(pushlex("form"), expression, statement, poplex); 333 | if (type == "keyword b") return cont(pushlex("form"), statement, poplex); 334 | if (type == "{") return cont(pushlex("}"), block, poplex); 335 | if (type == ";") return cont(); 336 | if (type == "if") { 337 | if (cx.state.lexical.info == "else" && cx.state.cc[cx.state.cc.length - 1] == poplex) 338 | cx.state.cc.pop()(); 339 | return cont(pushlex("form"), expression, statement, poplex, maybeelse); 340 | } 341 | if (type == "function") return cont(functiondef); 342 | if (type == "for") return cont(pushlex("form"), forspec, statement, poplex); 343 | if (type == "variable") return cont(pushlex("stat"), maybelabel); 344 | if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"), 345 | block, poplex, poplex); 346 | if (type == "case") return cont(expression, expect(":")); 347 | if (type == "default") return cont(expect(":")); 348 | if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"), 349 | statement, poplex, popcontext); 350 | if (type == "module") return cont(pushlex("form"), pushcontext, afterModule, popcontext, poplex); 351 | if (type == "class") return cont(pushlex("form"), className, poplex); 352 | if (type == "export") return cont(pushlex("form"), afterExport, poplex); 353 | if (type == "import") return cont(pushlex("form"), afterImport, poplex); 354 | return pass(pushlex("stat"), expression, expect(";"), poplex); 355 | } 356 | function expression(type) { 357 | return expressionInner(type, false); 358 | } 359 | function expressionNoComma(type) { 360 | return expressionInner(type, true); 361 | } 362 | function expressionInner(type, noComma) { 363 | if (cx.state.fatArrowAt == cx.stream.start) { 364 | var body = noComma ? arrowBodyNoComma : arrowBody; 365 | if (type == "(") return cont(pushcontext, pushlex(")"), commasep(pattern, ")"), poplex, expect("=>"), body, popcontext); 366 | else if (type == "variable") return pass(pushcontext, pattern, expect("=>"), body, popcontext); 367 | } 368 | 369 | var maybeop = noComma ? maybeoperatorNoComma : maybeoperatorComma; 370 | if (atomicTypes.hasOwnProperty(type)) return cont(maybeop); 371 | if (type == "function") return cont(functiondef, maybeop); 372 | if (type == "keyword c") return cont(noComma ? maybeexpressionNoComma : maybeexpression); 373 | if (type == "(") return cont(pushlex(")"), maybeexpression, comprehension, expect(")"), poplex, maybeop); 374 | if (type == "operator" || type == "spread") return cont(noComma ? expressionNoComma : expression); 375 | if (type == "[") return cont(pushlex("]"), arrayLiteral, poplex, maybeop); 376 | if (type == "{") return contCommasep(objprop, "}", null, maybeop); 377 | if (type == "quasi") { return pass(quasi, maybeop); } 378 | return cont(); 379 | } 380 | function maybeexpression(type) { 381 | if (type.match(/[;\}\)\],]/)) return pass(); 382 | return pass(expression); 383 | } 384 | function maybeexpressionNoComma(type) { 385 | if (type.match(/[;\}\)\],]/)) return pass(); 386 | return pass(expressionNoComma); 387 | } 388 | 389 | function maybeoperatorComma(type, value) { 390 | if (type == ",") return cont(expression); 391 | return maybeoperatorNoComma(type, value, false); 392 | } 393 | function maybeoperatorNoComma(type, value, noComma) { 394 | var me = noComma == false ? maybeoperatorComma : maybeoperatorNoComma; 395 | var expr = noComma == false ? expression : expressionNoComma; 396 | if (type == "=>") return cont(pushcontext, noComma ? arrowBodyNoComma : arrowBody, popcontext); 397 | if (type == "operator") { 398 | if (/\+\+|--/.test(value)) return cont(me); 399 | if (value == "?") return cont(expression, expect(":"), expr); 400 | return cont(expr); 401 | } 402 | if (type == "quasi") { return pass(quasi, me); } 403 | if (type == ";") return; 404 | if (type == "(") return contCommasep(expressionNoComma, ")", "call", me); 405 | if (type == ".") return cont(property, me); 406 | if (type == "[") return cont(pushlex("]"), maybeexpression, expect("]"), poplex, me); 407 | } 408 | function quasi(type, value) { 409 | if (type != "quasi") return pass(); 410 | if (value.slice(value.length - 2) != "${") return cont(quasi); 411 | return cont(expression, continueQuasi); 412 | } 413 | function continueQuasi(type) { 414 | if (type == "}") { 415 | cx.marked = "string-2"; 416 | cx.state.tokenize = tokenQuasi; 417 | return cont(quasi); 418 | } 419 | } 420 | function arrowBody(type) { 421 | findFatArrow(cx.stream, cx.state); 422 | return pass(type == "{" ? statement : expression); 423 | } 424 | function arrowBodyNoComma(type) { 425 | findFatArrow(cx.stream, cx.state); 426 | return pass(type == "{" ? statement : expressionNoComma); 427 | } 428 | function maybelabel(type) { 429 | if (type == ":") return cont(poplex, statement); 430 | return pass(maybeoperatorComma, expect(";"), poplex); 431 | } 432 | function property(type) { 433 | if (type == "variable") {cx.marked = "property"; return cont();} 434 | } 435 | function objprop(type, value) { 436 | if (type == "variable" || cx.style == "keyword") { 437 | cx.marked = "property"; 438 | if (value == "get" || value == "set") return cont(getterSetter); 439 | return cont(afterprop); 440 | } else if (type == "number" || type == "string") { 441 | cx.marked = jsonldMode ? "property" : (cx.style + " property"); 442 | return cont(afterprop); 443 | } else if (type == "jsonld-keyword") { 444 | return cont(afterprop); 445 | } else if (type == "[") { 446 | return cont(expression, expect("]"), afterprop); 447 | } 448 | } 449 | function getterSetter(type) { 450 | if (type != "variable") return pass(afterprop); 451 | cx.marked = "property"; 452 | return cont(functiondef); 453 | } 454 | function afterprop(type) { 455 | if (type == ":") return cont(expressionNoComma); 456 | if (type == "(") return pass(functiondef); 457 | } 458 | function commasep(what, end) { 459 | function proceed(type) { 460 | if (type == ",") { 461 | var lex = cx.state.lexical; 462 | if (lex.info == "call") lex.pos = (lex.pos || 0) + 1; 463 | return cont(what, proceed); 464 | } 465 | if (type == end) return cont(); 466 | return cont(expect(end)); 467 | } 468 | return function(type) { 469 | if (type == end) return cont(); 470 | return pass(what, proceed); 471 | }; 472 | } 473 | function contCommasep(what, end, info) { 474 | for (var i = 3; i < arguments.length; i++) 475 | cx.cc.push(arguments[i]); 476 | return cont(pushlex(end, info), commasep(what, end), poplex); 477 | } 478 | function block(type) { 479 | if (type == "}") return cont(); 480 | return pass(statement, block); 481 | } 482 | function maybetype(type) { 483 | if (isTS && type == ":") return cont(typedef); 484 | } 485 | function typedef(type) { 486 | if (type == "variable"){cx.marked = "variable-3"; return cont();} 487 | } 488 | function vardef() { 489 | return pass(pattern, maybetype, maybeAssign, vardefCont); 490 | } 491 | function pattern(type, value) { 492 | if (type == "variable") { register(value); return cont(); } 493 | if (type == "[") return contCommasep(pattern, "]"); 494 | if (type == "{") return contCommasep(proppattern, "}"); 495 | } 496 | function proppattern(type, value) { 497 | if (type == "variable" && !cx.stream.match(/^\s*:/, false)) { 498 | register(value); 499 | return cont(maybeAssign); 500 | } 501 | if (type == "variable") cx.marked = "property"; 502 | return cont(expect(":"), pattern, maybeAssign); 503 | } 504 | function maybeAssign(_type, value) { 505 | if (value == "=") return cont(expressionNoComma); 506 | } 507 | function vardefCont(type) { 508 | if (type == ",") return cont(vardef); 509 | } 510 | function maybeelse(type, value) { 511 | if (type == "keyword b" && value == "else") return cont(pushlex("form", "else"), statement, poplex); 512 | } 513 | function forspec(type) { 514 | if (type == "(") return cont(pushlex(")"), forspec1, expect(")"), poplex); 515 | } 516 | function forspec1(type) { 517 | if (type == "var") return cont(vardef, expect(";"), forspec2); 518 | if (type == ";") return cont(forspec2); 519 | if (type == "variable") return cont(formaybeinof); 520 | return pass(expression, expect(";"), forspec2); 521 | } 522 | function formaybeinof(_type, value) { 523 | if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression); } 524 | return cont(maybeoperatorComma, forspec2); 525 | } 526 | function forspec2(type, value) { 527 | if (type == ";") return cont(forspec3); 528 | if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression); } 529 | return pass(expression, expect(";"), forspec3); 530 | } 531 | function forspec3(type) { 532 | if (type != ")") cont(expression); 533 | } 534 | function functiondef(type, value) { 535 | if (value == "*") {cx.marked = "keyword"; return cont(functiondef);} 536 | if (type == "variable") {register(value); return cont(functiondef);} 537 | if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, statement, popcontext); 538 | } 539 | function funarg(type) { 540 | if (type == "spread") return cont(funarg); 541 | return pass(pattern, maybetype); 542 | } 543 | function className(type, value) { 544 | if (type == "variable") {register(value); return cont(classNameAfter);} 545 | } 546 | function classNameAfter(type, value) { 547 | if (value == "extends") return cont(expression, classNameAfter); 548 | if (type == "{") return cont(pushlex("}"), classBody, poplex); 549 | } 550 | function classBody(type, value) { 551 | if (type == "variable" || cx.style == "keyword") { 552 | if (value == "static") { 553 | cx.marked = "keyword"; 554 | return cont(classBody); 555 | } 556 | cx.marked = "property"; 557 | if (value == "get" || value == "set") return cont(classGetterSetter, functiondef, classBody); 558 | return cont(functiondef, classBody); 559 | } 560 | if (value == "*") { 561 | cx.marked = "keyword"; 562 | return cont(classBody); 563 | } 564 | if (type == ";") return cont(classBody); 565 | if (type == "}") return cont(); 566 | } 567 | function classGetterSetter(type) { 568 | if (type != "variable") return pass(); 569 | cx.marked = "property"; 570 | return cont(); 571 | } 572 | function afterModule(type, value) { 573 | if (type == "string") return cont(statement); 574 | if (type == "variable") { register(value); return cont(maybeFrom); } 575 | } 576 | function afterExport(_type, value) { 577 | if (value == "*") { cx.marked = "keyword"; return cont(maybeFrom, expect(";")); } 578 | if (value == "default") { cx.marked = "keyword"; return cont(expression, expect(";")); } 579 | return pass(statement); 580 | } 581 | function afterImport(type) { 582 | if (type == "string") return cont(); 583 | return pass(importSpec, maybeFrom); 584 | } 585 | function importSpec(type, value) { 586 | if (type == "{") return contCommasep(importSpec, "}"); 587 | if (type == "variable") register(value); 588 | if (value == "*") cx.marked = "keyword"; 589 | return cont(maybeAs); 590 | } 591 | function maybeAs(_type, value) { 592 | if (value == "as") { cx.marked = "keyword"; return cont(importSpec); } 593 | } 594 | function maybeFrom(_type, value) { 595 | if (value == "from") { cx.marked = "keyword"; return cont(expression); } 596 | } 597 | function arrayLiteral(type) { 598 | if (type == "]") return cont(); 599 | return pass(expressionNoComma, maybeArrayComprehension); 600 | } 601 | function maybeArrayComprehension(type) { 602 | if (type == "for") return pass(comprehension, expect("]")); 603 | if (type == ",") return cont(commasep(maybeexpressionNoComma, "]")); 604 | return pass(commasep(expressionNoComma, "]")); 605 | } 606 | function comprehension(type) { 607 | if (type == "for") return cont(forspec, comprehension); 608 | if (type == "if") return cont(expression, comprehension); 609 | } 610 | 611 | function isContinuedStatement(state, textAfter) { 612 | return state.lastType == "operator" || state.lastType == "," || 613 | isOperatorChar.test(textAfter.charAt(0)) || 614 | /[,.]/.test(textAfter.charAt(0)); 615 | } 616 | 617 | // Interface 618 | 619 | return { 620 | startState: function(basecolumn) { 621 | var state = { 622 | tokenize: tokenBase, 623 | lastType: "sof", 624 | cc: [], 625 | lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false), 626 | localVars: parserConfig.localVars, 627 | context: parserConfig.localVars && {vars: parserConfig.localVars}, 628 | indented: 0 629 | }; 630 | if (parserConfig.globalVars && typeof parserConfig.globalVars == "object") 631 | state.globalVars = parserConfig.globalVars; 632 | return state; 633 | }, 634 | 635 | token: function(stream, state) { 636 | if (stream.sol()) { 637 | if (!state.lexical.hasOwnProperty("align")) 638 | state.lexical.align = false; 639 | state.indented = stream.indentation(); 640 | findFatArrow(stream, state); 641 | } 642 | if (state.tokenize != tokenComment && stream.eatSpace()) return null; 643 | var style = state.tokenize(stream, state); 644 | if (type == "comment") return style; 645 | state.lastType = type == "operator" && (content == "++" || content == "--") ? "incdec" : type; 646 | return parseJS(state, style, type, content, stream); 647 | }, 648 | 649 | indent: function(state, textAfter) { 650 | if (state.tokenize == tokenComment) return CodeMirror.Pass; 651 | if (state.tokenize != tokenBase) return 0; 652 | var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical; 653 | // Kludge to prevent 'maybelse' from blocking lexical scope pops 654 | if (!/^\s*else\b/.test(textAfter)) for (var i = state.cc.length - 1; i >= 0; --i) { 655 | var c = state.cc[i]; 656 | if (c == poplex) lexical = lexical.prev; 657 | else if (c != maybeelse) break; 658 | } 659 | if (lexical.type == "stat" && firstChar == "}") lexical = lexical.prev; 660 | if (statementIndent && lexical.type == ")" && lexical.prev.type == "stat") 661 | lexical = lexical.prev; 662 | var type = lexical.type, closing = firstChar == type; 663 | 664 | if (type == "vardef") return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? lexical.info + 1 : 0); 665 | else if (type == "form" && firstChar == "{") return lexical.indented; 666 | else if (type == "form") return lexical.indented + indentUnit; 667 | else if (type == "stat") 668 | return lexical.indented + (isContinuedStatement(state, textAfter) ? statementIndent || indentUnit : 0); 669 | else if (lexical.info == "switch" && !closing && parserConfig.doubleIndentSwitch != false) 670 | return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit); 671 | else if (lexical.align) return lexical.column + (closing ? 0 : 1); 672 | else return lexical.indented + (closing ? 0 : indentUnit); 673 | }, 674 | 675 | electricInput: /^\s*(?:case .*?:|default:|\{|\})$/, 676 | blockCommentStart: jsonMode ? null : "/*", 677 | blockCommentEnd: jsonMode ? null : "*/", 678 | lineComment: jsonMode ? null : "//", 679 | fold: "brace", 680 | closeBrackets: "()[]{}''\"\"``", 681 | 682 | helperType: jsonMode ? "json" : "javascript", 683 | jsonldMode: jsonldMode, 684 | jsonMode: jsonMode 685 | }; 686 | }); 687 | 688 | CodeMirror.registerHelper("wordChars", "javascript", /[\w$]/); 689 | 690 | CodeMirror.defineMIME("text/javascript", "javascript"); 691 | CodeMirror.defineMIME("text/ecmascript", "javascript"); 692 | CodeMirror.defineMIME("application/javascript", "javascript"); 693 | CodeMirror.defineMIME("application/x-javascript", "javascript"); 694 | CodeMirror.defineMIME("application/ecmascript", "javascript"); 695 | CodeMirror.defineMIME("application/json", {name: "javascript", json: true}); 696 | CodeMirror.defineMIME("application/x-json", {name: "javascript", json: true}); 697 | CodeMirror.defineMIME("application/ld+json", {name: "javascript", jsonld: true}); 698 | CodeMirror.defineMIME("text/typescript", { name: "javascript", typescript: true }); 699 | CodeMirror.defineMIME("application/typescript", { name: "javascript", typescript: true }); 700 | 701 | }); 702 | -------------------------------------------------------------------------------- /lib/jstat.min.js: -------------------------------------------------------------------------------- 1 | this.j$=this.jStat=function(a,b){function f(b,c){var d=b>c?b:c;return a.pow(10,17-~~(a.log(d>0?d:-d)*a.LOG10E))}function h(a){return e.call(a)==="[object Function]"}function i(a){return typeof a=="number"&&a===a}function j(a){return c.apply([],a)}function k(){return new k._init(arguments)}function l(){return 0}function m(){return 1}function n(a,b){return a===b?1:0}var c=Array.prototype.concat,d=Array.prototype.slice,e=Object.prototype.toString,g=Array.isArray||function(b){return e.call(b)==="[object Array]"};k.fn=k.prototype,k._init=function(b){var c;if(g(b[0]))if(g(b[0][0])){h(b[1])&&(b[0]=k.map(b[0],b[1]));for(c=0;c=0;c--,e++)d[e]=[b[e][c]];return d},k.transpose=function(b){var c=[],d,e,f,h,i;g(b[0])||(b=[b]),e=b.length,f=b[0].length;for(i=0;i0&&(i[e][0]=b[e][0]);for(j=1;j1?d.call(this):d.call(this)[0]},o.map=function(b,c){return k(k.map(this,b,c))},o.cumreduce=function(b,c){return k(k.cumreduce(this,b,c))},o.alter=function(b){return k.alter(this,b),this},function(a){for(var b=0;b=0)g+=a[b];return g},a.sumsqrd=function(b){var c=0,d=b.length;while(--d>=0)c+=b[d]*b[d];return c},a.sumsqerr=function(c){var d=a.mean(c),e=0,f=c.length,g;while(--f>=0)g=c[f]-d,e+=g*g;return e},a.product=function(b){var c=1,d=b.length;while(--d>=0)c*=b[d];return c},a.min=function(b){var c=b[0],d=0;while(++dc&&(c=b[d]);return c},a.mean=function(c){return a.sum(c)/c.length},a.meansqerr=function(c){return a.sumsqerr(c)/c.length},a.geomean=function(d){return b.pow(a.product(d),1/d.length)},a.median=function(b){var c=b.length,e=b.slice().sort(d);return c&1?e[c/2|0]:(e[c/2-1]+e[c/2])/2},a.cumsum=function(c){return a.cumreduce(c,function(a,b){return a+b})},a.cumprod=function(c){return a.cumreduce(c,function(a,b){return a*b})},a.diff=function(b){var c=[],d=b.length,e;for(e=1;eg?(i=[e[j]],g=f,h=0):f===g&&(i.push(e[j]),h++),f=1);return h===0?i[0]:i},a.range=function(c){return a.max(c)-a.min(c)},a.variance=function(c,d){return a.sumsqerr(c)/(c.length-(d?1:0))},a.stdev=function(d,e){return b.sqrt(a.variance(d,e))},a.meandev=function(d){var e=0,f=a.mean(d),g;for(g=d.length-1;g>=0;g--)e+=b.abs(d[g]-f);return e/d.length},a.meddev=function(d){var e=0,f=a.median(d),g;for(g=d.length-1;g>=0;g--)e+=b.abs(d[g]-f);return e/d.length},a.coeffvar=function(c){return a.stdev(c)/a.mean(c)},a.quartiles=function(c){var e=c.length,f=c.slice().sort(d);return[f[b.round(e/4)-1],f[b.round(e/2)-1],f[b.round(e*3/4)-1]]},a.quantiles=function(c,f,g,h){var i=c.slice().sort(d),j=[f.length],k=c.length,l,m,n,o,p,q;typeof g=="undefined"&&(g=3/8),typeof h=="undefined"&&(h=3/8);for(l=0;l1){i=d===!0?this:this.transpose();for(;h1){i=d===!0?this:this.transpose();for(;h1){g=g.transpose();for(;ej)for(k=0;k=1?d:1/d)*8.5+d*.4+17),p,q;if(e<0||d<=0)return NaN;if(e170||e>170?b.exp(a.combinationln(d,e)):a.factorial(d)/a.factorial(e)/a.factorial(d-e)},a.combinationln=function(c,d){return a.factorialln(c)-a.factorialln(d)-a.factorialln(c-d)},a.permutation=function(c,d){return a.factorial(c)/a.factorial(c-d)},a.betafn=function(d,e){return d<=0||e<=0?undefined:d+e>170?b.exp(a.betaln(d,e)):a.gammafn(d)*a.gammafn(e)/a.gammafn(d+e)},a.betaln=function(c,d){return a.gammaln(c)+a.gammaln(d)-a.gammaln(c+d)},a.betacf=function(c,d,e){var f=1e-30,g=1,h=d+e,i=d+1,j=d-1,k=1,l=1-h*c/i,m,n,o,p;b.abs(l)=1)return b.max(100,e+100*b.sqrt(e));if(d<=0)return 0;e>1?(o=b.log(g),p=b.exp(g*(o-1)-i),n=d<.5?d:1-d,l=b.sqrt(-2*b.log(n)),j=(2.30753+l*.27061)/(1+l*(.99229+l*.04481))-l,d<.5&&(j=-j),j=b.max(.001,e*b.pow(1-1/(9*e)-j/(3*b.sqrt(e)),3))):(l=1-e*(.253+e*.12),d1?l=p*b.exp(-(j-g)+g*(b.log(j)-o)):l=b.exp(-j+g*b.log(j)-i),m=k/l,j-=l=m/(1-.5*b.min(1,m*((e-1)/j-1))),j<=0&&(j=.5*(j+l));if(b.abs(l)0;e--)k=g,g=j*g-h+d[e],h=k;return l=i*b.exp(-c*c+.5*(d[0]+j*g)-h),f?l-1:1-l},a.erfc=function(c){return 1-a.erf(c)},a.erfcinv=function(d){var e=0,f,g,h,i;if(d>=2)return-100;if(d<=0)return 100;i=d<1?d:2-d,h=b.sqrt(-2*b.log(i/2)),f=-0.70711*((2.30753+h*.27061)/(1+h*(.99229+h*.04481))-h);for(;e<2;e++)g=a.erfc(f)-i,f+=g/(1.1283791670955126*b.exp(-f*f)-f*g);return d<1?f:-f},a.ibetainv=function(d,e,f){var g=1e-8,h=e-1,i=f-1,j=0,k,l,m,n,o,p,q,r,s,t,u;if(d<=0)return 0;if(d>=1)return 1;e>=1&&f>=1?(m=d<.5?d:1-d,n=b.sqrt(-2*b.log(m)),q=(2.30753+n*.27061)/(1+n*(.99229+n*.04481))-n,d<.5&&(q=-q),r=(q*q-3)/6,s=2/(1/(2*e-1)+1/(2*f-1)),t=q*b.sqrt(r+s)/s-(1/(2*f-1)-1/(2*e-1))*(r+5/6-2/(3*s)),q=e/(e+f*b.exp(2*t))):(k=b.log(e/(e+f)),l=b.log(f/(e+f)),n=b.exp(e*k)/e,o=b.exp(f*l)/f,t=n+o,d=1&&(q=.5*(q+n+1));if(b.abs(n)0)break}return q},a.ibeta=function(d,e,f){var g=d===0||d===1?0:b.exp(a.gammaln(e+f)-a.gammaln(e)-a.gammaln(f)+e*b.log(d)+f*b.log(1-d));return d<0||d>1?!1:d<(e+1)/(e+f+2)?g*a.betacf(d,e,f)/e:1-g*a.betacf(1-d,f,e)/f},a.randn=function(d,e){var f,g,h,i,j,k;e||(e=d);if(d)return a.create(d,e,function(){return a.randn()});do f=b.random(),g=1.7156*(b.random()-.5),h=f-.449871,i=b.abs(g)+.386595,j=h*h+i*(.196*i-.25472*h);while(j>.27597&&(j>.27846||g*g>-4*b.log(f)*f*f));return g/f},a.randg=function(d,e,f){var g=d,h,i,j,k,l,m;f||(f=e),d||(d=1);if(e)return m=a.zeros(e,f),m.alter(function(){return a.randg(d)}),m;d<1&&(d+=1),h=d-1/3,i=1/b.sqrt(9*h);do{do l=a.randn(),k=1+i*l;while(k<=0);k=k*k*k,j=b.random()}while(j>1-.331*b.pow(l,4)&&b.log(j)>.5*l*l+h*(1-k+b.log(k)));if(d==g)return h*k;do j=b.random();while(j===0);return b.pow(j,1/g)*h*k},function(b){for(var c=0;c1||d<0?0:e==1&&f==1?1:e<512||f<512?b.pow(d,e-1)*b.pow(1-d,f-1)/a.betafn(e,f):b.exp((e-1)*b.log(d)+(f-1)*b.log(1-d)-a.betaln(e,f))},cdf:function(c,d,e){return c>1||c<0?(c>1)*1:a.ibeta(c,d,e)},inv:function(c,d,e){return a.ibetainv(c,d,e)},mean:function(b,c){return b/(b+c)},median:function(b,c){throw new Error("median not yet implemented")},mode:function(c,d){return c*d/(b.pow(c+d,2)*(c+d+1))},sample:function(c,d){var e=a.randg(c);return e/(e+a.randg(d))},variance:function(c,d){return c*d/(b.pow(c+d,2)*(c+d+1))}}),a.extend(a.centralF,{pdf:function(d,e,f){var g,h,i;return d<0?undefined:e<=2?e===1&&f===1?Infinity:e===2&&f===1?1:b.sqrt(b.pow(e*d,e)*b.pow(f,f)/b.pow(e*d+f,e+f))/(d*a.betafn(e/2,f/2)):(g=e*d/(f+d*e),h=f/(f+d*e),i=e*h/2,i*a.binomial.pdf((e-2)/2,(e+f-2)/2,g))},cdf:function(c,d,e){return a.ibeta(d*c/(d*c+e),d/2,e/2)},inv:function(c,d,e){return e/(d*(1/a.ibetainv(c,d/2,e/2)-1))},mean:function(b,c){return c>2?c/(c-2):undefined},mode:function(b,c){return b>2?c*(b-2)/(b*(c+2)):undefined},sample:function(c,d){var e=a.randg(c/2)*2,f=a.randg(d/2)*2;return e/c/(f/d)},variance:function(b,c){return c<=4?undefined:2*c*c*(b+c-2)/(b*(c-2)*(c-2)*(c-4))}}),a.extend(a.cauchy,{pdf:function(c,d,e){return e/(b.pow(c-d,2)+b.pow(e,2))/b.PI},cdf:function(c,d,e){return b.atan((c-d)/e)/b.PI+.5},inv:function(a,c,d){return c+d*b.tan(b.PI*(a-.5))},median:function(b,c){return b},mode:function(b,c){return b},sample:function(d,e){return a.randn()*b.sqrt(1/(2*a.randg(.5)))*e+d}}),a.extend(a.chisquare,{pdf:function(d,e){return d===0?0:b.exp((e/2-1)*b.log(d)-d/2-e/2*b.log(2)-a.gammaln(e/2))},cdf:function(c,d){return a.lowRegGamma(d/2,c/2)},inv:function(b,c){return 2*a.gammapinv(b,.5*c)},mean:function(a){return a},median:function(c){return c*b.pow(1-2/(9*c),3)},mode:function(b){return b-2>0?b-2:0},sample:function(c){return a.randg(c/2)*2},variance:function(b){return 2*b}}),a.extend(a.exponential,{pdf:function(c,d){return c<0?0:d*b.exp(-d*c)},cdf:function(c,d){return c<0?0:1-b.exp(-d*c)},inv:function(a,c){return-b.log(1-a)/c},mean:function(a){return 1/a},median:function(a){return 1/a*b.log(2)},mode:function(b){return 0},sample:function(c){return-1/c*b.log(b.random())},variance:function(a){return b.pow(a,-2)}}),a.extend(a.gamma,{pdf:function(d,e,f){return b.exp((e-1)*b.log(d)-d/f-a.gammaln(e)-e*b.log(f))},cdf:function(c,d,e){return a.lowRegGamma(d,c/e)},inv:function(b,c,d){return a.gammapinv(b,c)*d},mean:function(a,b){return a*b},mode:function(b,c){return b>1?(b-1)*c:undefined},sample:function(c,d){return a.randg(c)*d},variance:function(b,c){return b*c*c}}),a.extend(a.invgamma,{pdf:function(d,e,f){return b.exp(-(e+1)*b.log(d)-f/d-a.gammaln(e)+e*b.log(f))},cdf:function(c,d,e){return 1-a.lowRegGamma(d,e/c)},inv:function(b,c,d){return d/a.gammapinv(1-b,c)},mean:function(a,b){return a>1?b/(a-1):undefined},mode:function(b,c){return c/(b+1)},sample:function(c,d){return d/a.randg(c)},variance:function(b,c){return b<=2?undefined:c*c/((b-1)*(b-1)*(b-2))}}),a.extend(a.kumaraswamy,{pdf:function(c,d,e){return b.exp(b.log(d)+b.log(e)+(d-1)*b.log(c)+(e-1)*b.log(1-b.pow(c,d)))},cdf:function(c,d,e){return 1-b.pow(1-b.pow(c,d),e)},mean:function(b,c){return c*a.gammafn(1+1/b)*a.gammafn(c)/a.gammafn(1+1/b+c)},median:function(c,d){return b.pow(1-b.pow(2,-1/d),1/c)},mode:function(c,d){return c>=1&&d>=1&&c!==1&&d!==1?b.pow((c-1)/(c*d-1),1/c):undefined},variance:function(b,c){throw new Error("variance not yet implemented")}}),a.extend(a.lognormal,{pdf:function(c,d,e){return b.exp(-b.log(c)-.5*b.log(2*b.PI)-b.log(e)-b.pow(b.log(c)-d,2)/(2*e*e))},cdf:function(d,e,f){return.5+.5*a.erf((b.log(d)-e)/b.sqrt(2*f*f))},inv:function(c,d,e){return b.exp(-1.4142135623730951*e*a.erfcinv(2*c)+d)},mean:function(c,d){return b.exp(c+d*d/2)},median:function(c,d){return b.exp(c)},mode:function(c,d){return b.exp(c-d*d)},sample:function(d,e){return b.exp(a.randn()*e+d)},variance:function(c,d){return(b.exp(d*d)-1)*b.exp(2*c+d*d)}}),a.extend(a.normal,{pdf:function(c,d,e){return b.exp(-0.5*b.log(2*b.PI)-b.log(e)-b.pow(c-d,2)/(2*e*e))},cdf:function(d,e,f){return.5*(1+a.erf((d-e)/b.sqrt(2*f*f)))},inv:function(b,c,d){return-1.4142135623730951*d*a.erfcinv(2*b)+c},mean:function(a,b){return a},median:function(b,c){return b},mode:function(a,b){return a},sample:function(c,d){return a.randn()*d+c},variance:function(a,b){return b*b}}),a.extend(a.pareto,{pdf:function(c,d,e){return c.5?e:-e},mean:function(b){return b>1?0:undefined},median:function(b){return 0},mode:function(b){return 0},sample:function(d){return a.randn()*b.sqrt(d/(2*a.randg(d/2)))},variance:function(b){return b>2?b/(b-2):b>1?Infinity:undefined}}),a.extend(a.weibull,{pdf:function(c,d,e){return c<0?0:e/d*b.pow(c/d,e-1)*b.exp(-b.pow(c/d,e))},cdf:function(c,d,e){return c<0?0:1-b.exp(-b.pow(c/d,e))},inv:function(a,c,d){return c*b.pow(-b.log(1-a),1/d)},mean:function(b,c){return b*a.gammafn(1+1/c)},median:function(c,d){return c*b.pow(b.log(2),1/d)},mode:function(c,d){return d<=1?undefined:c*b.pow((d-1)/d,1/d)},sample:function(c,d){return c*b.pow(-b.log(b.random()),1/d)},variance:function(d,e){return d*d*a.gammafn(1+2/e)-b.pow(this.mean(d,e),2)}}),a.extend(a.uniform,{pdf:function(b,c,d){return bd?0:1/(d-c)},cdf:function(b,c,d){return bg||d>f)return 0;if(f*2>e)return g*2>e?a.hypgeom.pdf(e-f-g+d,e,e-f,e-g):a.hypgeom.pdf(g-d,e,e-f,g);if(g*2>e)return a.hypgeom.pdf(f-d,e,f,e-g);if(f1&&i=g||d>=f)return 1;if(f*2>e)return g*2>e?a.hypgeom.cdf(e-f-g+d,e,e-f,e-g):1-a.hypgeom.cdf(g-d-1,e,e-f,g);if(g*2>e)return 1-a.hypgeom.cdf(f-d-1,e,f,e-g);if(f1&&jf);return e-1}}),a.extend(a.triangular,{pdf:function(b,c,d,e){return d<=c||ed?undefined:bd?0:b<=e?2*(b-c)/((d-c)*(e-c)):2*(d-b)/((d-c)*(d-e))},cdf:function(c,d,e,f){return e<=d||fe?undefined:c(c+d)/2)return c+b.sqrt((d-c)*(e-c))/b.sqrt(2)},mode:function(b,c,d){return d},sample:function(c,d,e){var f=b.random();return f<(e-c)/(d-c)?c+b.sqrt(f*(d-c)*(e-c)):d-b.sqrt((1-f)*(d-c)*(d-e))},variance:function(b,c,d){return(b*b+c*c+d*d-b*c-b*d-c*d)/18}})}(this.jStat,Math),function(a,b){var d=Array.prototype.push,e=a.utils.isArray;a.extend({add:function(c,d){return e(d)?(e(d[0])||(d=[d]),a.map(c,function(a,b,c){return a+d[b][c]})):a.map(c,function(a){return a+d})},subtract:function(c,d){return e(d)?(e(d[0])||(d=[d]),a.map(c,function(a,b,c){return a-d[b][c]||0})):a.map(c,function(a){return a-d})},divide:function(c,d){return e(d)?(e(d[0])||(d=[d]),a.multiply(c,a.inv(d))):a.map(c,function(a){return a/d})},multiply:function(c,d){var f,g,h,i,j=c.length,k=c[0].length,l=a.zeros(j,h=e(d)?d[0].length:k),m=0;if(e(d)){for(;m=0;f--){k=0;for(g=f+1;g<=h-1;g++)k+=l[g]*d[f][g];l[f]=(d[f][m-1]-k)/d[f][f]}return l},gauss_jordan:function(e,f){var g=a.aug(e,f),h=g.length,i=g[0].length;for(var j=0;jb.abs(g[k][j])&&(k=l);var m=g[j];g[j]=g[k],g[k]=m;for(var l=j+1;l=0;j--){c=g[j][j];for(var l=0;lj-1;n--)g[l][n]-=g[j][n]*g[l][j]/c;g[j][j]/=c;for(var n=h;ni?(k[h][i]=d[h][i],l[h][i]=m[h][i]=0):hg)n=q,q=a.add(a.multiply(p,n),o),h++;return q},gauss_seidel:function(d,e,f,g){var h=0,i=d.length,j=[],k=[],l=[],m,n,o,p,q;for(;hm?(j[h][m]=d[h][m],k[h][m]=l[h][m]=0):hg)n=q,q=a.add(a.multiply(p,n),o),h+=1;return q},SOR:function(d,e,f,g,h){var i=0,j=d.length,k=[],l=[],m=[],n,o,p,q,r;for(;in?(k[i][n]=d[i][n],l[i][n]=m[i][n]=0):ig)o=r,r=a.add(a.multiply(q,o),p),i++;return r},householder:function(d){var e=d.length,f=d[0].length,g=0,h=[],i=[],j,k,l,m,n;for(;g0?-1:1,j=n*b.sqrt(j),k=b.sqrt((j*j-d[g+1][g]*j)/2),h=a.zeros(e,1),h[g+1][0]=(d[g+1][g]-j)/(2*k);for(l=g+2;l0?-1:1,m=p*b.sqrt(m),n=b.sqrt((m*m-d[h+1][h]*m)/2),i=a.zeros(f,1),i[h+1][0]=(d[h+1][h]-m)/(2*n);for(o=h+2;o=0;h--){q=0;for(l=h+1;l<=g-1;l++)q=k[l]*d[h][l];k[h]=e[h][0]/d[h][h]}return k},jacobi:function(d){var e=1,f=0,g=d.length,h=a.identity(g,g),i=[],j,k,l,m,n,o,p,q;while(e===1){f++,o=d[0][1],m=0,n=1;for(k=0;k0?b.PI/4:-b.PI/4:p=b.atan(2*d[m][n]/(d[m][m]-d[n][n]))/2,q=a.identity(g,g),q[m][m]=b.cos(p),q[m][n]=-b.sin(p),q[n][m]=b.sin(p),q[n][n]=b.cos(p),h=a.multiply(h,q),j=a.multiply(a.multiply(a.inv(q),d),q),d=j,e=0;for(k=1;k.001&&(e=1)}for(k=0;k=i)m=g(c,e+f),n=g(c,e),k[j]=(d[m]-2*d[n]+d[2*n-m])/(f*f),f/=2,j++;p=k.length,o=1;while(p!=1){for(q=0;qe)break;return h-=1,d[h]+(e-c[h])*n[h]+a.sq(e-c[h])*l[h]+(e-c[h])*a.sq(e-c[h])*o[h]},gauss_quadrature:function(){throw new Error("gauss_quadrature not yet implemented")},PCA:function(c){var d=c.length,e=c[0].length,f=!1,g=0,h,i,j=[],k=[],l=[],m=[],n=[],o=[],p=[],q=[],r=[],s=[];for(g=0;g. 20 | 21 | */ 22 | 23 | 24 | //***************************************************** 25 | 26 | //close prices 27 | //column 11 is 'Adjusted Close' on Y! 28 | //column 4 is 'Close' on Goog 29 | 30 | function getQuandlCall(source, ticker, key){ 31 | 32 | if (source == "GOOG"){ 33 | var column = 4; 34 | } 35 | else if (source == "YAHOO") { 36 | var column = 6; 37 | 38 | //yahoo doesn't have proper server side setup 39 | /* 40 | var column = 7; 41 | return { 42 | url:'http://crossorigin.me/http://real-chart.finance.yahoo.com/table.csv?s='+ticker, 43 | ticker:ticker 44 | }; 45 | */ 46 | } 47 | else if (source == "CBOE") { 48 | var column = 1; 49 | } 50 | else if (source == "SPDJ") { 51 | var column = 1; 52 | } 53 | 54 | return { 55 | url:'https://www.quandl.com/api/v1/datasets/'+source+'/'+ticker+'.json?column='+column+'&auth_token='+key, 56 | ticker:ticker 57 | }; 58 | } 59 | 60 | 61 | 62 | //***************************************************** 63 | //make api call 64 | xhr = function(){ 65 | var xmlhttp = new XMLHttpRequest(); 66 | xmlhttp.open("GET", this.url, false); 67 | xmlhttp.send(); 68 | 69 | 70 | return JSON.parse(xmlhttp.responseText).data; 71 | } 72 | 73 | Object.defineProperty( 74 | Object.prototype, 75 | 'xhr', 76 | { 77 | value: xhr, 78 | writable: true, 79 | configurable: true, 80 | enumerable: false 81 | } 82 | ); 83 | 84 | -------------------------------------------------------------------------------- /lib/strategyHelpers.js: -------------------------------------------------------------------------------- 1 | /* 2 | (c)2015 John Orford 3 | 4 | This file is part of Lazy Backtesting. 5 | 6 | Lazy Backtesting is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU Affero General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | Lazy Backtesting is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU Affero General Public License for more details. 15 | 16 | You should have received a copy of the GNU Affero General Public License 17 | along with Lazy Backtesting. If not, see . 18 | 19 | */ 20 | 21 | //*************************************************************************** 22 | 23 | Array.prototype.returns = function(){ 24 | return _.reduce( 25 | this, 26 | function(acc,cur){ 27 | if(acc.prev===null){ 28 | return {result:[],prev:cur.datum}; 29 | } 30 | else{ 31 | return {result: acc.result.concat( Math.log(acc.prev/cur.datum) ), 32 | prev:cur.datum 33 | }; 34 | } 35 | }, 36 | {result:[],prev:null} 37 | ) 38 | .result; 39 | } 40 | 41 | Array.prototype.prices = function(){ 42 | return _.map( this, 43 | function(cur){ 44 | return cur.datum; 45 | } 46 | ); 47 | } 48 | 49 | Array.prototype.dsfct = function(){ 50 | return _.map( 51 | this, 52 | function(cur){ 53 | return Math.exp(-cur.datum/100); 54 | } 55 | ); 56 | } 57 | 58 | Array.prototype.prices = function(){ 59 | return _.map( this, 60 | function(cur){ 61 | return cur.datum; 62 | } 63 | ); 64 | } 65 | 66 | Array.prototype.period = function(p){ 67 | return _.reduce( 68 | this, 69 | function(acc,cur){ 70 | 71 | if (acc.counter%p === 0){ 72 | return {result:acc.result.concat(cur),counter:acc.counter+1}; 73 | } 74 | else { 75 | return {result:acc.result,counter:acc.counter+1}; 76 | } 77 | } 78 | ,{result:[],counter:0} 79 | ) 80 | .result; 81 | } 82 | 83 | Array.prototype.quarterly = function(){ 84 | return this.period(63); 85 | } 86 | Array.prototype.monthly = function(){ 87 | return this.period(21); 88 | } 89 | Array.prototype.weekly = function(){ 90 | return this.period(5); 91 | } 92 | Array.prototype.daily = function(){ 93 | return this; 94 | } 95 | 96 | Array.prototype.lookback = function(p){ 97 | //take a number of elements 98 | //throw error if number of elements is less than amount requested 99 | var r = Lazy(this).take(p).toArray(); 100 | 101 | if (r.length === p){return r;} 102 | else {throw "Not enough data left";} 103 | } 104 | //*************************************************************************** 105 | -------------------------------------------------------------------------------- /lib/utility.js: -------------------------------------------------------------------------------- 1 | /* 2 | (c)2015 John Orford 3 | 4 | This file is part of Lazy Backtesting. 5 | 6 | Lazy Backtesting is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU Affero General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | Lazy Backtesting is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU Affero General Public License for more details. 15 | 16 | You should have received a copy of the GNU Affero General Public License 17 | along with Lazy Backtesting. If not, see . 18 | 19 | */ 20 | 21 | 22 | //***************************************************** 23 | 24 | Number.prototype.yieldToDsft = function(){ 25 | return Math.exp(-this/100); 26 | } 27 | 28 | 29 | 30 | //***************************************************** 31 | 32 | 33 | write = function(desc,id){ 34 | 35 | if (id === undefined){ 36 | id = "messages"; 37 | } 38 | 39 | var p = document.createElement('p'); 40 | var text = document.createTextNode(desc+" "+this); 41 | p.appendChild(text); 42 | document.getElementById(id).appendChild(p); 43 | 44 | } 45 | 46 | Object.defineProperty( 47 | Object.prototype, 48 | 'write', 49 | { 50 | value: write, 51 | writable: true, 52 | configurable: true, 53 | enumerable: false 54 | } 55 | ); 56 | 57 | //***************************************************** 58 | 59 | 60 | //print between dot passing 61 | tap2 = function(desc,id,status){ 62 | this.ticker.write(desc,id); 63 | return this; 64 | } 65 | 66 | 67 | Object.defineProperty( 68 | Object.prototype, 69 | 'tap2', 70 | { 71 | value: tap2, 72 | writable: true, 73 | configurable: true, 74 | enumerable: false 75 | } 76 | ); 77 | 78 | //***************************************************** 79 | 80 | 81 | function isFunction(functionToCheck) { 82 | var getType = {}; 83 | return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]'; 84 | } 85 | 86 | --------------------------------------------------------------------------------