Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
amshenoy committed Jul 9, 2022
0 parents commit 7fbe629
Show file tree
Hide file tree
Showing 5 changed files with 321 additions and 0 deletions.
72 changes: 72 additions & 0 deletions assets/js/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Worker initialisation
const myWorker = new Worker("./assets/js/worker.js");

function updatePlot(data){

var xPts = data[0], yPts = data[1], zPts = data[2]

var labels = ["HL", "AJB", "BARC", "FID", "MIN"]
var colors = ["rgb(0,0,255)", "rgb(255,180,0)", "rgb(0,255,0)", "rgb(255,0,0)", "rgb(200,200,200)"]

var traces = []
for (let [i, label] of labels.entries()){
var trace = {
x: xPts,
y: yPts,
z: zPts[label],
type: 'surface',
name: labels[i],
showscale: false,
colorscale: [[0,colors[i]],[1,colors[i]]],
showlegend:true,
}
traces.push(trace)
}

var layout = {
title: 'Stocks & Shares ISA Providers',
scene:{
xaxis: {title: "Fund Value"},
yaxis: {title: "Share Value"},
zaxis: {title: "Total Annual Cost"},
camera: {
// center: {x: 0, y: 0, z: 0 },
eye: {x: 1.8, y: 1.2, z: 1.2 },
up: {x: 0, y: 0, z: 0.3 }
},
},
autosize: false,
width: 1200,
height: 840,
// margin: {
// l: 65,
// r: 50,
// b: 65,
// t: 90,
// },
}

Plotly.newPlot('plot', traces, layout);
}

// Process received data here
myWorker.onmessage = function(e) {
var result = e.data
// Process/display data here
updatePlot(result)
loading.style.display = "none"
}

const loading = document.querySelector('#sim-loading')
const numFunds = document.querySelector('#num-funds')
const numStocks = document.querySelector('#num-stocks')
const simulate = document.querySelector('#simulate')

simulate.onclick = function() {
myWorker.postMessage({fundRange: 40e3, fundStep: 2e2, stockRange: 40e3, stockStep: 2e2, fundTrans: parseInt(numFunds.value), stockTrans: parseInt(numStocks.value)})
loading.style.display = "inline-block"
}

document.addEventListener("DOMContentLoaded", function(event) {
simulate.click()
})
3 changes: 3 additions & 0 deletions assets/js/numjs.min.js

Large diffs are not rendered by default.

141 changes: 141 additions & 0 deletions assets/js/providers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
//References:
// https://github.com/nicolaspanel/numjs/blob/master/src/ndarray.js
// https://github.com/nicolaspanel/numjs
// https://github.com/scijs/ndarray-ops


// Stocks and Shares ISA Providers
/*
> a.slice([null,null,-1]) // same as a[::-1]
array([ 4, 3, 2, 1, 0])
*/

var INF = Number.MAX_SAFE_INTEGER;
var gt0 = x => x > 0 ? 1: 0;
var gte0 = x => x >= 0 ? 1: 0;
var lt0 = x => x < 0 ? 1: 0;
var lte0 = x => x <= 0 ? 1: 0;
var nanfilter = x => Math.abs(x) > INF/2 ? 0: x;

//TEST: nj.subtract([2,3,4], [4,3,2]).apply(gte0)

nj.NdArray.prototype.apply = function (func, copy) {
if (arguments.length === 2) { copy = true; }
var arr = copy ? this.clone() : this;
arr.selection.data = arr.selection.data.map(x=>func(x))
return arr;
};


nj.NdArray.prototype.nansum = function (copy) {
if (arguments.length === 1) { copy = true; }
var arr = copy ? this.clone() : this;
return arr.apply(nanfilter).sum();
}
//nj.NdArray.prototype.nansum = nj.NdArray.prototype.apply(nanfilter).sum() // Simpler form if it works (needs to be corrected)

//TEST: nj.subtract([2,3,4], [3,1,Number.MAX_SAFE_INTEGER]).nansum()

/*
nj.NdArray.prototype.geq = function (value, copy) {
if (arguments.length === 1) { copy = true; }
var arr = copy ? this.clone() : this;
ops.geq(arr.selection, value);
return arr;
};
*/

function tier_sum(tiers, charges, value){
tiers = nj.array(tiers)
charges = nj.array(charges)
var ends = tiers.slice(1)
var starts = tiers.slice([0, -1])

var a = ends.subtract(value).apply(gte0)
var b = starts.subtract(value).apply(lt0)
var c = starts.subtract(value).negative()
var A = (a.multiply(b)).multiply(c)
var B = (ends.subtract(value).apply(lt0)).multiply(ends.subtract(starts))
return charges.multiply( A.add(B) ).nansum()
}


function HL(F, S, Nf, Ns){
var V = F + S
var tiers = [0, 250e3, 500e3, 1000e3, 2000e3, INF]
var charges = nj.array([0.45, 0.25, 0.25, 0.1, 0]).divide(100)

var share_trans_tiers = [0, 10, 20, INF]
var fund_trans_cost = 0
var share_trans_cost = [11.95, 8.95, 5.95]

var holding_cost = tier_sum(tiers, charges, V)
var trans_cost = ( Nf*fund_trans_cost + tier_sum(share_trans_tiers, share_trans_cost, Ns) ) * 12

return holding_cost + trans_cost
}


function AJB(F, S, Nf, Ns){
var V = F + S
var tiers = [0, 250e3, 500e3, 1000e3, 2000e3, INF]
var fund_charges = nj.array([0.25, 0.1, 0, 0, 0]).divide(100)
var share_charges = nj.array([0.25, 0, 0, 0, 0]).divide(100)

var share_trans_tiers = [0, 10, 20, INF]
var fund_trans_cost = 1.5
var share_trans_cost = [9.95, 4.95, 4.95]

var holding_cost = tier_sum(tiers, fund_charges, F) + Math.max(tier_sum(tiers, share_charges, S), 3.5*12)
var trans_cost = ( Nf*fund_trans_cost + tier_sum(share_trans_tiers, share_trans_cost, Ns) ) * 12

return holding_cost + trans_cost
}


function BARC(F, S, Nf, Ns){
var V = F + S
//tiers = [0, 250e3, 500e3, 1000e3, 2000e3, np.inf]
//charges = np.array([0.2, 0.2, 0.2, 0.2, 0.2])/100
var fund_charge = 0.2/100
var share_charge = 0.1/100

//share_trans_tiers = [0, 10, 20, np.inf]
var fund_trans_cost = 3
//share_trans_cost = [6, 6, 6]
var share_trans_cost = 6

var holding_cost = Math.max(Math.min(fund_charge * F + share_charge * S, 125*12), 4*12)
//trans_cost = ( Nf*fund_trans_cost + tier_sum(share_trans_tiers, share_trans_cost, Ns) ) * 12
var trans_cost = (Nf*fund_trans_cost + Ns*share_trans_cost)*12

return holding_cost + trans_cost
}


// Verified using https://www.fidelity.co.uk/fees-calculator/
function FID(F, S, Nf, Ns){

var tiers = nj.array([250e3, 500e3, 1000e3])
var charges = [0.35 / 100, 0.2 / 100, 0.2 / 100] //nj.array([0.35, 0.2, 0.2]).divide(100).tolist()

// Fidelity charges the rate on the amount for the tier you are in rather than on the amount over the previous tier
// For ETIs (Exchange-traded instrs eg. stocks, ETFs), the fee is capped at 45 annually
// But for Mutual Funds, this is capped at 2000 annually
var holding_cost = Math.min(F*charges[tiers.subtract(F).apply(lte0).sum()], 2000) + Math.min(S*charges[tiers.subtract(S).apply(lte0).sum()], 45)

var instr_trans_cost = 10
//var trans_cost = ((Nf + Ns)*instr_trans_cost)*12
var trans_cost = (Ns*instr_trans_cost)*12
return holding_cost + trans_cost
}


function MIN(f, s, Nf, Ns){
var providers = [HL, AJB, BARC, FID]
var arr = []
for (let [i, provider] of providers.entries()){
arr.push(provider(f, s, Nf, Ns))
}
return Math.min(...arr)
}
54 changes: 54 additions & 0 deletions assets/js/worker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Import external scripts here
importScripts("./numjs.min.js", "./providers.js");

self.onmessage = function(e) {
var t0 = performance.now();

var data = e.data;

var xlim = data.fundRange;
var xstep = data.fundStep;
var ylim = data.stockRange;
var ystep = data.stockStep;
var Nf = data.fundTrans;
var Ns = data.stockTrans;

// PROCESS DATA HERE
var labels = ["HL", "AJB", "BARC", "FID", "MIN"]
var providers = [HL, AJB, BARC, FID, MIN]

// Create template for provider value matrix
var templateZ = {}
for (var provider of labels) {
templateZ[provider] = [];
}

var zPts = JSON.parse(JSON.stringify(templateZ));
var xPts = [];
var yPts = [];

for(x=0; x <= xlim; x += xstep) {
let zTemp = JSON.parse(JSON.stringify(templateZ));
let yTemp = [];
let xTemp = [];
for (y=0; y <= ylim; y += ystep) {
xTemp.push(x);
yTemp.push(y);
for (let [i, provider] of providers.entries()){
zTemp[labels[i]].push(provider(x, y, Nf, Ns));
}
}
xPts.push(xTemp);
yPts.push(yTemp);
for (let [i, provider] of providers.entries()){
zPts[labels[i]].push(zTemp[labels[i]]);
}
}


var result = [xPts, yPts, zPts];

var t1 = performance.now();
console.log("Processing took " + (t1 - t0) + " milliseconds.");
self.postMessage(result);
}
51 changes: 51 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<html>
<head>
<title>Stocks & Shares ISA Providers</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="description" content="Stocks Shares ISA Providers">
<meta name="keywords" content="stocks, equities, comparison, analysis, calculator, engineering">
<meta name="author" content="Abhishek Shenoy">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
</head>
<body>
<div id="main">
<nav id="menu" style="padding:10px;">
<div class="container">
<div class="row">
<div class="col-sm-4">
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text" style="font-size: 14px;">Num Monthly Fund Trades</span>
</div>
<input type="text" id="num-funds" class="form-control" type="number" value="0"/>
</div>
</div>
<div class="col-sm-4">
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text" style="font-size: 14px;">Num Monthly Stock Trades</span>
</div>
<input type="text" id="num-stocks" class="form-control" type="number" value="0"/>
</div>
</div>
<div class="col-sm-4">
<button id="simulate" class="btn btn-primary">
<span id="sim-available">Update</span>
<span id="sim-loading"class="spinner-grow spinner-grow-sm" role="status" aria-hidden="true" style="display: none;"></span>
</button>
</div>
</div>
</div>
</nav>
<div class="container">
<div class="row">
<div id="plot"></div>
</div>
</div>
</div>
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
<!-- <script src="https://cdn.jsdelivr.net/gh/nicolaspanel/[email protected]/dist/numjs.min.js"></script> -->
<script src="./assets/js/app.js"></script>
</body>
</html>

0 comments on commit 7fbe629

Please sign in to comment.