Compare commits
2 Commits
882d721899
...
42cffe2fae
Author | SHA1 | Date | |
---|---|---|---|
42cffe2fae | |||
5f53d2c336 |
Binary file not shown.
File diff suppressed because one or more lines are too long
@ -141,6 +141,30 @@
|
|||||||
<div id="chartTooltip" class="chart-tooltip"></div>
|
<div id="chartTooltip" class="chart-tooltip"></div>
|
||||||
<script>
|
<script>
|
||||||
let importedFunctions = {};
|
let importedFunctions = {};
|
||||||
|
function logInfo(msg) {
|
||||||
|
console.info(msg);
|
||||||
|
if (typeof msg == 'object') {
|
||||||
|
$("#logDiv").css("font-color", "white").text(JSON.stringify(msg));
|
||||||
|
} else {
|
||||||
|
$("#logDiv").css("font-color", "white").text(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function logWarning(msg) {
|
||||||
|
console.warn(msg);
|
||||||
|
if (typeof msg == 'object') {
|
||||||
|
$("#logDiv").css("font-color", "orange").text(JSON.stringify(msg));
|
||||||
|
} else {
|
||||||
|
$("#logDiv").css("font-color", "orange").text(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function logError(msg) {
|
||||||
|
console.error(msg);
|
||||||
|
if (typeof msg == 'object') {
|
||||||
|
$("#logDiv").css("font-color", "red").text(JSON.stringify(msg));
|
||||||
|
} else {
|
||||||
|
$("#logDiv").css("font-color", "red").text(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
function showTab(tab) {
|
function showTab(tab) {
|
||||||
//$('.navbar>a').removeClass('activenav');
|
//$('.navbar>a').removeClass('activenav');
|
||||||
$('.navbar').find('.activenav').removeClass('activenav');
|
$('.navbar').find('.activenav').removeClass('activenav');
|
||||||
@ -161,11 +185,11 @@
|
|||||||
redrawAllSharesCharts();
|
redrawAllSharesCharts();
|
||||||
break;*/
|
break;*/
|
||||||
case 'displayAsPercent':
|
case 'displayAsPercent':
|
||||||
redrawAllSharesCharts();
|
importedFunctions.redrawAllSharesCharts();
|
||||||
break;
|
break;
|
||||||
case 'excludeNoHoldings':
|
case 'excludeNoHoldings':
|
||||||
createSharesTable();
|
importedFunctions.createSharesTable();
|
||||||
redrawAllSharesCharts();
|
importedFunctions.redrawAllSharesCharts();
|
||||||
break;
|
break;
|
||||||
/*case 'showPreviousHoldings':
|
/*case 'showPreviousHoldings':
|
||||||
redrawAllSharesCharts();
|
redrawAllSharesCharts();
|
||||||
@ -182,12 +206,24 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import { getInstruments, refreshHistoryChart, showModalDialog_AddInstrument, showModalDialog_AddHolding, showModalDialog_FullScreenChart, showModalDialog_SoldHolding } from "./scripts/SharePrices.js";
|
import {
|
||||||
|
createSharesTable,
|
||||||
|
getInstruments,
|
||||||
|
redrawAllSharesCharts,
|
||||||
|
refreshHistoryChart,
|
||||||
|
showModalDialog_AddInstrument,
|
||||||
|
showModalDialog_AddHolding,
|
||||||
|
showModalDialog_FullScreenChart,
|
||||||
|
showModalDialog_SoldHolding
|
||||||
|
} from "./scripts/SharePrices.js";
|
||||||
importedFunctions = {
|
importedFunctions = {
|
||||||
|
createSharesTable: createSharesTable,
|
||||||
|
getInstruments: getInstruments,
|
||||||
|
redrawAllSharesCharts: redrawAllSharesCharts,
|
||||||
|
refreshHistoryChart: refreshHistoryChart,
|
||||||
showModalDialog_AddInstrument: showModalDialog_AddInstrument,
|
showModalDialog_AddInstrument: showModalDialog_AddInstrument,
|
||||||
showModalDialog_AddHolding: showModalDialog_AddHolding,
|
showModalDialog_AddHolding: showModalDialog_AddHolding,
|
||||||
showModalDialog_FullScreenChart: showModalDialog_FullScreenChart,
|
showModalDialog_FullScreenChart: showModalDialog_FullScreenChart,
|
||||||
refreshHistoryChart: refreshHistoryChart,
|
|
||||||
showModalDialog_SoldHolding: showModalDialog_SoldHolding
|
showModalDialog_SoldHolding: showModalDialog_SoldHolding
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,6 +20,8 @@
|
|||||||
<IISExpressAnonymousAuthentication />
|
<IISExpressAnonymousAuthentication />
|
||||||
<IISExpressWindowsAuthentication />
|
<IISExpressWindowsAuthentication />
|
||||||
<IISExpressUseClassicPipelineMode />
|
<IISExpressUseClassicPipelineMode />
|
||||||
|
<Use64BitIISExpress />
|
||||||
|
<UseGlobalApplicationHostFile />
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||||
<DebugSymbols>true</DebugSymbols>
|
<DebugSymbols>true</DebugSymbols>
|
||||||
@ -259,6 +261,7 @@
|
|||||||
<Content Include="scripts\js.cookie-2.2.1.min.js" />
|
<Content Include="scripts\js.cookie-2.2.1.min.js" />
|
||||||
<Content Include="scripts\SharePrices.js" />
|
<Content Include="scripts\SharePrices.js" />
|
||||||
<Content Include="scripts\SharePrices_Charts.js" />
|
<Content Include="scripts\SharePrices_Charts.js" />
|
||||||
|
<Content Include="scripts\SharePrices_Holdings.js" />
|
||||||
<Content Include="scripts\tablesorter\dragtable.mod.css" />
|
<Content Include="scripts\tablesorter\dragtable.mod.css" />
|
||||||
<Content Include="scripts\tablesorter\filter.formatter.css" />
|
<Content Include="scripts\tablesorter\filter.formatter.css" />
|
||||||
<Content Include="scripts\tablesorter\highlights.css" />
|
<Content Include="scripts\tablesorter\highlights.css" />
|
||||||
|
@ -1,7 +1,15 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
<Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<NameOfLastUsedPublishProfile>WinSrv1</NameOfLastUsedPublishProfile>
|
<NameOfLastUsedPublishProfile>WinSrv1</NameOfLastUsedPublishProfile>
|
||||||
|
<LastActiveSolutionConfig>Debug|Any CPU</LastActiveSolutionConfig>
|
||||||
|
<UseIISExpress>false</UseIISExpress>
|
||||||
|
<Use64BitIISExpress />
|
||||||
|
<IISExpressSSLPort />
|
||||||
|
<IISExpressAnonymousAuthentication />
|
||||||
|
<IISExpressWindowsAuthentication />
|
||||||
|
<IISExpressUseClassicPipelineMode />
|
||||||
|
<UseGlobalApplicationHostFile />
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ProjectExtensions>
|
<ProjectExtensions>
|
||||||
<VisualStudio>
|
<VisualStudio>
|
||||||
|
Binary file not shown.
@ -1,21 +1,35 @@
|
|||||||
// @ts-check
|
// @ts-check
|
||||||
|
|
||||||
import {generateAxisBreaks, getProfitOrLoss, timespans} from "./SharePrices_Common.js";
|
import {formatAmount, generateAxisBreaks, getProfitOrLoss, timespans} from "./SharePrices_Common.js";
|
||||||
import {createLongChart, createMidChart, createShortChart, createSingleDayChart, createChart_Flot,
|
import {createLongChart, createMidChart, createShortChart, createSingleDayChart, createChart_Flot,
|
||||||
createChart, createFullScreenChart, createFullScreenChart_Flot, createFullScreenComparisonChart,
|
createChart, createFullScreenChart, createFullScreenChart_Flot, createFullScreenComparisonChart,
|
||||||
createFullScreenComparisonChart_Flot} from "./SharePrices_Charts.js";
|
createFullScreenComparisonChart_Flot
|
||||||
|
} from "./SharePrices_Charts.js";
|
||||||
|
import {createHoldingsTable} from "./SharePrices_Holdings.js";
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
var vars = {
|
||||||
|
fetchTiming: {
|
||||||
|
fetchIntervalDaily: 12 * timespans.oneHour,
|
||||||
|
fetchIntervalIntraday: 4 * timespans.oneHour,
|
||||||
|
fetchIntervalSingleDay: 2 * timespans.oneMinute,
|
||||||
|
lastSuccessfulFetch: new Date(0),
|
||||||
|
lastFetchStartTime: new Date(0),
|
||||||
|
lastFetchDuration: 0,
|
||||||
|
fetchTimer: 0,
|
||||||
|
displayTimings: false
|
||||||
|
},
|
||||||
|
lastTotalHoldingsValue: 0, //The last total holdings value successfully set in the database
|
||||||
|
initialPageTitle: '',
|
||||||
|
previousDay: 0,
|
||||||
|
previousClose: 0,
|
||||||
|
previousWeek: 0,
|
||||||
|
previousWeekClose:0
|
||||||
|
}
|
||||||
|
|
||||||
//var timespans = { oneSecond: 1000, oneMinute: 60 * 1000, oneHour: 60 * 60 * 1000, oneDay: 24 * 60 * 60 * 1000, oneWeek: 7 * 24 * 60 * 60 * 1000, oneMonth: 31 * 24 * 60 * 60 * 1000 };
|
//var timespans = { oneSecond: 1000, oneMinute: 60 * 1000, oneHour: 60 * 60 * 1000, oneDay: 24 * 60 * 60 * 1000, oneWeek: 7 * 24 * 60 * 60 * 1000, oneMonth: 31 * 24 * 60 * 60 * 1000 };
|
||||||
var fetchIntervalDaily = 12 * timespans.oneHour;
|
//var lastSuccessfulFetch;
|
||||||
var fetchIntervalIntraday = 4 * timespans.oneHour;
|
|
||||||
var fetchIntervalSingleDay = 2 * timespans.oneMinute;
|
|
||||||
var initialPageTitle;
|
|
||||||
var fetchTimer = 0;
|
|
||||||
var lastSuccessfulFetch;
|
|
||||||
var lastTotalHoldingsValue = 0; //The last total holdings value successfully set in the database
|
|
||||||
var currencyFormatter = new Intl.NumberFormat('en-GB', { style: 'currency', currency: 'GBP' });
|
var currencyFormatter = new Intl.NumberFormat('en-GB', { style: 'currency', currency: 'GBP' });
|
||||||
var instrumentSearchResults = [];
|
var instrumentSearchResults = [];
|
||||||
var tablesUpdateTimings = { lastUpdate: 0, timeBetweenUpdates: 10000, timer: 0, updateNeeded: true };
|
var tablesUpdateTimings = { lastUpdate: 0, timeBetweenUpdates: 10000, timer: 0, updateNeeded: true };
|
||||||
@ -90,13 +104,13 @@ var Instruments = {
|
|||||||
},
|
},
|
||||||
GetLastFetchedDaily: function () {
|
GetLastFetchedDaily: function () {
|
||||||
//if (!this.Data[0].lastFetchedDaily) { this.Data[0].lastFetchedDaily = this.Data[0].MaxDailyDate };
|
//if (!this.Data[0].lastFetchedDaily) { this.Data[0].lastFetchedDaily = this.Data[0].MaxDailyDate };
|
||||||
if (!this.Data[0].lastFetchedDaily) { this.Data[0].lastFetchedDaily = new Date().getTime() - fetchIntervalDaily + (4 * timespans.oneMinute) };
|
if (!this.Data[0].lastFetchedDaily) { this.Data[0].lastFetchedDaily = new Date().getTime() - vars.fetchTiming.fetchIntervalDaily + (4 * timespans.oneMinute) };
|
||||||
let lastFetchedDate = this.Data[0].lastFetchedDaily;
|
let lastFetchedDate = this.Data[0].lastFetchedDaily;
|
||||||
let lastFetchedIndex = 0;
|
let lastFetchedIndex = 0;
|
||||||
for (let i = 1; i < this.Data.length; i++) {
|
for (let i = 1; i < this.Data.length; i++) {
|
||||||
if (this.Data[i].Symbol != 'GBP' && this.Data[i].Symbol != 'GBp') {
|
if (this.Data[i].Symbol != 'GBP' && this.Data[i].Symbol != 'GBp') {
|
||||||
//if (!this.Data[i].lastFetchedDaily) { this.Data[i].lastFetchedDaily = this.Data[i].MaxDailyDate };
|
//if (!this.Data[i].lastFetchedDaily) { this.Data[i].lastFetchedDaily = this.Data[i].MaxDailyDate };
|
||||||
if (!this.Data[i].lastFetchedDaily) { this.Data[i].lastFetchedDaily = new Date().getTime() - fetchIntervalDaily + (4 * timespans.oneMinute) };
|
if (!this.Data[i].lastFetchedDaily) { this.Data[i].lastFetchedDaily = new Date().getTime() - vars.fetchTiming.fetchIntervalDaily + (4 * timespans.oneMinute) };
|
||||||
if (lastFetchedDate > this.Data[i].lastFetchedDaily) {
|
if (lastFetchedDate > this.Data[i].lastFetchedDaily) {
|
||||||
lastFetchedDate = this.Data[i].lastFetchedDaily;
|
lastFetchedDate = this.Data[i].lastFetchedDaily;
|
||||||
lastFetchedIndex = i;
|
lastFetchedIndex = i;
|
||||||
@ -108,13 +122,13 @@ var Instruments = {
|
|||||||
},
|
},
|
||||||
GetLastFetchedIntraday: function () {
|
GetLastFetchedIntraday: function () {
|
||||||
//if (!this.Data[0].lastFetchedIntraday) { this.Data[0].lastFetchedIntraday = this.Data[0].MaxIntradayDate };
|
//if (!this.Data[0].lastFetchedIntraday) { this.Data[0].lastFetchedIntraday = this.Data[0].MaxIntradayDate };
|
||||||
if (!this.Data[0].lastFetchedIntraday) { this.Data[0].lastFetchedIntraday = new Date().getTime() - fetchIntervalIntraday + (2 * timespans.oneMinute) };
|
if (!this.Data[0].lastFetchedIntraday) { this.Data[0].lastFetchedIntraday = new Date().getTime() - vars.fetchTiming.fetchIntervalIntraday + (2 * timespans.oneMinute) };
|
||||||
let lastFetchedDate = this.Data[0].lastFetchedIntraday;
|
let lastFetchedDate = this.Data[0].lastFetchedIntraday;
|
||||||
let lastFetchedIndex = 0;
|
let lastFetchedIndex = 0;
|
||||||
for (let i = 1; i < this.Data.length; i++) {
|
for (let i = 1; i < this.Data.length; i++) {
|
||||||
if (this.Data[i].Symbol != 'GBP' && this.Data[i].Symbol != 'GBp') {
|
if (this.Data[i].Symbol != 'GBP' && this.Data[i].Symbol != 'GBp') {
|
||||||
//if (!this.Data[i].lastFetchedIntraday) { this.Data[i].lastFetchedIntraday = this.Data[i].MaxIntradayDate };
|
//if (!this.Data[i].lastFetchedIntraday) { this.Data[i].lastFetchedIntraday = this.Data[i].MaxIntradayDate };
|
||||||
if (!this.Data[i].lastFetchedIntraday) { this.Data[i].lastFetchedIntraday = new Date().getTime() - fetchIntervalIntraday + (2 * timespans.oneMinute) };
|
if (!this.Data[i].lastFetchedIntraday) { this.Data[i].lastFetchedIntraday = new Date().getTime() - vars.fetchTiming.fetchIntervalIntraday + (2 * timespans.oneMinute) };
|
||||||
if (lastFetchedDate > this.Data[i].lastFetchedIntraday) {
|
if (lastFetchedDate > this.Data[i].lastFetchedIntraday) {
|
||||||
lastFetchedDate = this.Data[i].lastFetchedIntraday;
|
lastFetchedDate = this.Data[i].lastFetchedIntraday;
|
||||||
lastFetchedIndex = i;
|
lastFetchedIndex = i;
|
||||||
@ -222,7 +236,6 @@ var Currencies = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var previousDay, previousClose, previousWeek, previousWeekClose;
|
|
||||||
|
|
||||||
Date.prototype.yyyymmddhhmmss = function () {
|
Date.prototype.yyyymmddhhmmss = function () {
|
||||||
var yyyy = this.getFullYear().toString();
|
var yyyy = this.getFullYear().toString();
|
||||||
@ -285,78 +298,6 @@ Number.prototype.autoScale = function () {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function logInfo(msg) {
|
|
||||||
console.info(msg);
|
|
||||||
if (typeof msg == 'object') {
|
|
||||||
$("#logDiv").css("font-color", "white").text(JSON.stringify(msg));
|
|
||||||
} else {
|
|
||||||
$("#logDiv").css("font-color", "white").text(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function logWarning(msg) {
|
|
||||||
console.warn(msg);
|
|
||||||
if (typeof msg == 'object') {
|
|
||||||
$("#logDiv").css("font-color", "orange").text(JSON.stringify(msg));
|
|
||||||
} else {
|
|
||||||
$("#logDiv").css("font-color", "orange").text(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function logError(msg) {
|
|
||||||
console.error(msg);
|
|
||||||
if (typeof msg == 'object') {
|
|
||||||
$("#logDiv").css("font-color", "red").text(JSON.stringify(msg));
|
|
||||||
} else {
|
|
||||||
$("#logDiv").css("font-color", "red").text(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function formatAmount(amount, currency, decimals) {
|
|
||||||
if (amount) {
|
|
||||||
let isNegative = (amount < 0);
|
|
||||||
let s = Math.abs(amount).toFixed(decimals);
|
|
||||||
if (Math.abs(amount) < 0.01) {
|
|
||||||
s = Math.abs(amount).toPrecision(2);
|
|
||||||
}
|
|
||||||
let pos = s.indexOf('.');
|
|
||||||
let wholeNumber = (pos >= 0) ? s.substring(0, pos) : s;
|
|
||||||
let decimal = (pos >= 0) ? s.substring(pos) : '';
|
|
||||||
s = '';
|
|
||||||
while (wholeNumber.length > 0) {
|
|
||||||
//if (s.length > 0 && wholeNumber!='-') s = ',' + s; //Don't add a comma if the only thing left to add to the string is the minus sign
|
|
||||||
if (s.length > 0) s = ',' + s;
|
|
||||||
if (wholeNumber.length >= 3) {
|
|
||||||
s = wholeNumber.substring(wholeNumber.length - 3) + s;
|
|
||||||
wholeNumber = wholeNumber.substring(0, wholeNumber.length - 3);
|
|
||||||
} else {
|
|
||||||
s = wholeNumber + s;
|
|
||||||
wholeNumber = '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s = s + decimal;
|
|
||||||
let sign = isNegative ? '-' : '';
|
|
||||||
switch (currency) {
|
|
||||||
case 'GBP':
|
|
||||||
s = sign + '£' + s;
|
|
||||||
break;
|
|
||||||
case 'GBp':
|
|
||||||
s = sign + s + 'p';
|
|
||||||
break;
|
|
||||||
case 'USD':
|
|
||||||
s = sign + 'U$' + s;
|
|
||||||
break;
|
|
||||||
case 'AUD':
|
|
||||||
s = sign + 'A$' + s;
|
|
||||||
break;
|
|
||||||
case 'EUR':
|
|
||||||
s = sign + '€' + s;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
s = sign + currency + s;
|
|
||||||
}
|
|
||||||
return s;
|
|
||||||
} else {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/*function getProfitOrLoss(BasePrice, CurrentPrice, ShowPercent) {
|
/*function getProfitOrLoss(BasePrice, CurrentPrice, ShowPercent) {
|
||||||
//let priceMovement = (CurrentPrice - BasePrice).toFixed(2);
|
//let priceMovement = (CurrentPrice - BasePrice).toFixed(2);
|
||||||
let priceMovement = (CurrentPrice - BasePrice).autoDP();
|
let priceMovement = (CurrentPrice - BasePrice).autoDP();
|
||||||
@ -364,35 +305,12 @@ function formatAmount(amount, currency, decimals) {
|
|||||||
if (percentMovement[0] == '-') percentMovement = percentMovement.substring(1);
|
if (percentMovement[0] == '-') percentMovement = percentMovement.substring(1);
|
||||||
return '<span class="' + (CurrentPrice < BasePrice ? 'loss' : 'profit') + '">' + (CurrentPrice < BasePrice ? '' : '+') + priceMovement + (ShowPercent ? ' (' + percentMovement + ')' : '') + '</span>';
|
return '<span class="' + (CurrentPrice < BasePrice ? 'loss' : 'profit') + '">' + (CurrentPrice < BasePrice ? '' : '+') + priceMovement + (ShowPercent ? ' (' + percentMovement + ')' : '') + '</span>';
|
||||||
}*/
|
}*/
|
||||||
function createCategoryAggregator() {
|
|
||||||
let aggregator = {
|
|
||||||
data: [],
|
|
||||||
indexOfCategory: function (categoryName) { return this.data.map(function (item) { return item.categoryName; }).indexOf(categoryName); },
|
|
||||||
getCategory: function (categoryName) { return this.data[this.indexOfCategory(categoryName)]; },
|
|
||||||
addCategoryAmount: function (categoryName, amount) {
|
|
||||||
let c = this.getCategory(categoryName);
|
|
||||||
if (c) {
|
|
||||||
c.total += amount;
|
|
||||||
} else {
|
|
||||||
this.data.push({ categoryName: categoryName, total: amount });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getChartData: function () {
|
|
||||||
let d = []
|
|
||||||
for (let i = 0; i < this.data.length; i++) {
|
|
||||||
d.push({ label: this.data[i].categoryName, data: this.data[i].total });
|
|
||||||
}
|
|
||||||
return d;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return aggregator;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getInstruments() {
|
export function getInstruments() {
|
||||||
try {
|
try {
|
||||||
//logInfo('getInstruments()');
|
//logInfo('getInstruments()');
|
||||||
initialPageTitle = document.title;
|
vars.initialPageTitle = document.title;
|
||||||
if (fetchTimer != 0) { clearTimeout(fetchTimer) };
|
if (vars.fetchTiming.fetchTimer != 0) { clearTimeout(vars.fetchTiming.fetchTimer) };
|
||||||
indexMarquee.init($('#divIndexMarquee'), ' ');
|
indexMarquee.init($('#divIndexMarquee'), ' ');
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: "POST",
|
type: "POST",
|
||||||
@ -436,7 +354,7 @@ export function getInstruments() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function createSharesTable() {
|
export function createSharesTable() {
|
||||||
try {
|
try {
|
||||||
let excludeNoHoldings = $('#excludeNoHoldings').prop('checked');
|
let excludeNoHoldings = $('#excludeNoHoldings').prop('checked');
|
||||||
//logInfo('createSharesTable()');
|
//logInfo('createSharesTable()');
|
||||||
@ -649,27 +567,45 @@ function getNewRemoteData() {
|
|||||||
|
|
||||||
//logInfo(new Date().yyyymmddhhmmss() + ": getNewRemoteData()")
|
//logInfo(new Date().yyyymmddhhmmss() + ": getNewRemoteData()")
|
||||||
|
|
||||||
|
let nothingToFetch = false;
|
||||||
let lastFetchedDailyInstrument = Instruments.GetLastFetchedDaily();
|
let lastFetchedDailyInstrument = Instruments.GetLastFetchedDaily();
|
||||||
if (lastFetchedDailyInstrument.lastFetchedDaily < (new Date().getTime() - fetchIntervalDaily)) {
|
if (lastFetchedDailyInstrument.lastFetchedDaily < (new Date().getTime() - vars.fetchTiming.fetchIntervalDaily)) {
|
||||||
//logInfo(new Date().yyyymmddhhmmss() + ": Last fetched daily: " + lastFetchedDailyInstrument.Symbol + ", Last fetched date: " + new Date(lastFetchedDailyInstrument.lastFetchedDaily).yyyymmddhhmmss());
|
//logInfo(new Date().yyyymmddhhmmss() + ": Last fetched daily: " + lastFetchedDailyInstrument.Symbol + ", Last fetched date: " + new Date(lastFetchedDailyInstrument.lastFetchedDaily).yyyymmddhhmmss());
|
||||||
getYahooDailyData(lastFetchedDailyInstrument);
|
getYahooDailyData(lastFetchedDailyInstrument);
|
||||||
} else {
|
} else {
|
||||||
let lastFetchedIntradayInstrument = Instruments.GetLastFetchedIntraday();
|
let lastFetchedIntradayInstrument = Instruments.GetLastFetchedIntraday();
|
||||||
if (lastFetchedIntradayInstrument.lastFetchedIntraday < (new Date().getTime() - fetchIntervalIntraday)) {
|
if (lastFetchedIntradayInstrument.lastFetchedIntraday < (new Date().getTime() - vars.fetchTiming.fetchIntervalIntraday)) {
|
||||||
//logInfo(new Date().yyyymmddhhmmss() + ": Last fetched intraday: " + lastFetchedIntradayInstrument.Symbol + ", Last fetched date: " + new Date(lastFetchedIntradayInstrument.lastFetchedIntraday).yyyymmddhhmmss());
|
//logInfo(new Date().yyyymmddhhmmss() + ": Last fetched intraday: " + lastFetchedIntradayInstrument.Symbol + ", Last fetched date: " + new Date(lastFetchedIntradayInstrument.lastFetchedIntraday).yyyymmddhhmmss());
|
||||||
getYahooIntradayData(lastFetchedIntradayInstrument);
|
getYahooIntradayData(lastFetchedIntradayInstrument);
|
||||||
} else {
|
} else {
|
||||||
let lastFetchedSingleDayInstrument = Instruments.GetLastFetchedSingleDay();
|
let lastFetchedSingleDayInstrument = Instruments.GetLastFetchedSingleDay();
|
||||||
if (lastFetchedSingleDayInstrument.lastFetchedSingleDay < (new Date().getTime() - fetchIntervalSingleDay)) {
|
if (lastFetchedSingleDayInstrument.lastFetchedSingleDay < (new Date().getTime() - vars.fetchTiming.fetchIntervalSingleDay)) {
|
||||||
//logInfo(new Date().yyyymmddhhmmss() + ": Last fetched single day: " + lastFetchedSingleDayInstrument.Symbol + ", Last fetched date: " + new Date(lastFetchedSingleDayInstrument.lastFetchedSingleDay).yyyymmddhhmmss());
|
//logInfo(new Date().yyyymmddhhmmss() + ": Last fetched single day: " + lastFetchedSingleDayInstrument.Symbol + ", Last fetched date: " + new Date(lastFetchedSingleDayInstrument.lastFetchedSingleDay).yyyymmddhhmmss());
|
||||||
getYahooSingleDayData(lastFetchedSingleDayInstrument);
|
getYahooSingleDayData(lastFetchedSingleDayInstrument);
|
||||||
} //else {
|
} else {
|
||||||
//fetchInterval = 3000;
|
//fetchInterval = 3000;
|
||||||
//fetchTimer = setTimeout(getNewRemoteData, fetchInterval);
|
//fetchTimer = setTimeout(getNewRemoteData, fetchInterval);
|
||||||
//}
|
nothingToFetch = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var nextFetchDelay = 5000;
|
||||||
|
if (!nothingToFetch) {
|
||||||
|
nextFetchDelay = vars.fetchTiming.lastFetchDuration + 100;
|
||||||
|
}
|
||||||
|
if (vars.fetchTiming.displayTimings) {
|
||||||
|
console.info({
|
||||||
|
msg: 'Fetch timing info',
|
||||||
|
currentTime: (new Date()).yyyymmddhhmmss(),
|
||||||
|
nothingToFetch: nothingToFetch,
|
||||||
|
lastFetchDuration: vars.fetchTiming.lastFetchDuration,
|
||||||
|
nextFetchDelay: nextFetchDelay
|
||||||
|
});
|
||||||
|
}
|
||||||
|
vars.fetchTiming.fetchTimer = setTimeout(getNewRemoteData, nextFetchDelay);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
vars.fetchTiming.fetchTimer = setTimeout(getNewRemoteData, 15000);
|
||||||
logError({ MSG: "Error in getNewRemoteData", err: err });
|
logError({ MSG: "Error in getNewRemoteData", err: err });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -733,17 +669,21 @@ function getAllLocalIntradayData() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
//fetchTimer = setTimeout(getNewRemoteData, 500);
|
//fetchTimer = setTimeout(getNewRemoteData, 500);
|
||||||
//fetchTimer = setInterval(getNewRemoteData, 1250);
|
//vars.fetchTiming.fetchTimer = setInterval(getNewRemoteData, 1000);
|
||||||
fetchTimer = setInterval(getNewRemoteData, 1000);
|
vars.fetchTiming.fetchTimer = setTimeout(getNewRemoteData, 200);
|
||||||
},
|
},
|
||||||
failure: function (response) { console.error("getYTDData error:"); console.info(response); }
|
failure: function (response) {
|
||||||
|
vars.fetchTiming.fetchTimer = setTimeout(getNewRemoteData, 5000);
|
||||||
|
console.error("getYTDData error:");
|
||||||
|
console.info(response);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logError({ MSG: "Error in getAllLocalIntradayData", err: err });
|
logError({ MSG: "Error in getAllLocalIntradayData", err: err });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function redrawAllSharesCharts() {
|
export function redrawAllSharesCharts() {
|
||||||
let excludeNoHoldings = $('#excludeNoHoldings').prop('checked');
|
let excludeNoHoldings = $('#excludeNoHoldings').prop('checked');
|
||||||
for (let n = 0; n < Instruments.Data.length; n++) {
|
for (let n = 0; n < Instruments.Data.length; n++) {
|
||||||
let i = Instruments.Data[n];
|
let i = Instruments.Data[n];
|
||||||
@ -770,6 +710,7 @@ function fixDecimals(val, place) {
|
|||||||
function getYahooDailyData(Instrument) {
|
function getYahooDailyData(Instrument) {
|
||||||
//console.info('getYahooDailyData: ' + Instrument.Symbol);
|
//console.info('getYahooDailyData: ' + Instrument.Symbol);
|
||||||
Instrument.lastFetchedDaily = new Date().getTime();
|
Instrument.lastFetchedDaily = new Date().getTime();
|
||||||
|
vars.fetchTiming.lastFetchStartTime = new Date().getTime();
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: "POST",
|
type: "POST",
|
||||||
contentType: "application/json",
|
contentType: "application/json",
|
||||||
@ -777,6 +718,7 @@ function getYahooDailyData(Instrument) {
|
|||||||
dataType: "json",
|
dataType: "json",
|
||||||
data: JSON.stringify({ Symbol: Instrument.Symbol, MinDailyDT: (Instrument.DailyData.length ? Instrument.DailyData[0].DT: new Date().getTime()) }),
|
data: JSON.stringify({ Symbol: Instrument.Symbol, MinDailyDT: (Instrument.DailyData.length ? Instrument.DailyData[0].DT: new Date().getTime()) }),
|
||||||
success: function (response) {
|
success: function (response) {
|
||||||
|
vars.fetchTiming.lastFetchDuration = (new Date().getTime()) - vars.fetchTiming.lastFetchStartTime;
|
||||||
let newData = [];
|
let newData = [];
|
||||||
if (response.chart && response.chart.result) {
|
if (response.chart && response.chart.result) {
|
||||||
let dataSeries = response.chart.result[0];
|
let dataSeries = response.chart.result[0];
|
||||||
@ -817,7 +759,8 @@ function getYahooDailyData(Instrument) {
|
|||||||
//console.info("getYahooDailyData: " + Instrument.Symbol + ', Last data point: ' + d);
|
//console.info("getYahooDailyData: " + Instrument.Symbol + ', Last data point: ' + d);
|
||||||
|
|
||||||
if (newData.length > 0) {
|
if (newData.length > 0) {
|
||||||
lastSuccessfulFetch = new Date();
|
vars.fetchTiming.lastSuccessfulFetch = new Date();
|
||||||
|
vars.fetchTiming.lastSuccessfulFetchDuration = (new Date()) - vars.fetchTiming.lastSuccessfulFetchStartTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
submitNewDailyData(Instrument, newData);
|
submitNewDailyData(Instrument, newData);
|
||||||
@ -828,13 +771,18 @@ function getYahooDailyData(Instrument) {
|
|||||||
logWarning({ MSG: "No data received for symbol: " + Instrument.Symbol, response: response })
|
logWarning({ MSG: "No data received for symbol: " + Instrument.Symbol, response: response })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
failure: function (response) { console.error("getYahooDailyData error:"); console.info(response); }
|
failure: function (response) {
|
||||||
|
vars.fetchTiming.lastFetchDuration = (new Date().getTime()) - vars.fetchTiming.lastFetchStartTime;
|
||||||
|
console.error("getYahooDailyData error:");
|
||||||
|
console.info(response);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function getYahooIntradayData(Instrument) {
|
function getYahooIntradayData(Instrument) {
|
||||||
//console.info('getYahooIntradayData: ' + Instrument.Symbol);
|
//console.info('getYahooIntradayData: ' + Instrument.Symbol);
|
||||||
Instrument.lastFetchedIntraday = new Date().getTime();
|
Instrument.lastFetchedIntraday = new Date().getTime();
|
||||||
let minIntradayDT = Instrument.IntradayData.length > 0 ? Instrument.IntradayData[0].DT : new Date().getTime();
|
let minIntradayDT = Instrument.IntradayData.length > 0 ? Instrument.IntradayData[0].DT : new Date().getTime();
|
||||||
|
vars.fetchTiming.lastFetchStartTime = new Date().getTime();
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: "POST",
|
type: "POST",
|
||||||
contentType: "application/json",
|
contentType: "application/json",
|
||||||
@ -842,6 +790,7 @@ function getYahooIntradayData(Instrument) {
|
|||||||
dataType: "json",
|
dataType: "json",
|
||||||
data: JSON.stringify({ Symbol: Instrument.Symbol, MinIntradayDT: minIntradayDT }),
|
data: JSON.stringify({ Symbol: Instrument.Symbol, MinIntradayDT: minIntradayDT }),
|
||||||
success: function (response) {
|
success: function (response) {
|
||||||
|
vars.fetchTiming.lastFetchDuration = (new Date().getTime()) - vars.fetchTiming.lastFetchStartTime;
|
||||||
let newData = [];
|
let newData = [];
|
||||||
if (response.chart && response.chart.result) {
|
if (response.chart && response.chart.result) {
|
||||||
let dataSeries = response.chart.result[0];
|
let dataSeries = response.chart.result[0];
|
||||||
@ -874,7 +823,7 @@ function getYahooIntradayData(Instrument) {
|
|||||||
//console.warn("Didn't submit " + nonSubmittedEntries.toString() + " entries (" + newData.length.toString() + " OK) with null values for " + Instrument.Symbol);
|
//console.warn("Didn't submit " + nonSubmittedEntries.toString() + " entries (" + newData.length.toString() + " OK) with null values for " + Instrument.Symbol);
|
||||||
}
|
}
|
||||||
if (newData.length) {
|
if (newData.length) {
|
||||||
lastSuccessfulFetch = new Date();
|
vars.fetchTiming.lastSuccessfulFetch = new Date();
|
||||||
submitNewIntradayData(Instrument, newData);
|
submitNewIntradayData(Instrument, newData);
|
||||||
createMidChart(Instrument);
|
createMidChart(Instrument);
|
||||||
createShortChart(Instrument);
|
createShortChart(Instrument);
|
||||||
@ -889,12 +838,17 @@ function getYahooIntradayData(Instrument) {
|
|||||||
logWarning({ MSG: "No data received for symbol: " + Instrument.Symbol, response: response });
|
logWarning({ MSG: "No data received for symbol: " + Instrument.Symbol, response: response });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
failure: function (response) { console.error("getYahooIntradayData error:"); console.info(response); }
|
failure: function (response) {
|
||||||
|
vars.fetchTiming.lastFetchDuration = (new Date().getTime()) - vars.fetchTiming.lastFetchStartTime;
|
||||||
|
console.error("getYahooIntradayData error:");
|
||||||
|
console.info(response);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function getYahooSingleDayData(Instrument) {
|
function getYahooSingleDayData(Instrument) {
|
||||||
//console.info('getYahooSingleDayData: ' + Instrument.Symbol);
|
//console.info('getYahooSingleDayData: ' + Instrument.Symbol);
|
||||||
Instrument.lastFetchedSingleDay = new Date().getTime();
|
Instrument.lastFetchedSingleDay = new Date().getTime();
|
||||||
|
vars.fetchTiming.lastFetchStartTime = new Date().getTime();
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: "POST",
|
type: "POST",
|
||||||
contentType: "application/json",
|
contentType: "application/json",
|
||||||
@ -902,6 +856,7 @@ function getYahooSingleDayData(Instrument) {
|
|||||||
dataType: "json",
|
dataType: "json",
|
||||||
data: JSON.stringify({ Symbol: Instrument.Symbol }),
|
data: JSON.stringify({ Symbol: Instrument.Symbol }),
|
||||||
success: function (response) {
|
success: function (response) {
|
||||||
|
vars.fetchTiming.lastFetchDuration = (new Date().getTime()) - vars.fetchTiming.lastFetchStartTime;
|
||||||
let newData = [];
|
let newData = [];
|
||||||
if (response.chart && response.chart.result) {
|
if (response.chart && response.chart.result) {
|
||||||
let dataSeries = response.chart.result[0];
|
let dataSeries = response.chart.result[0];
|
||||||
@ -930,7 +885,7 @@ function getYahooSingleDayData(Instrument) {
|
|||||||
//console.warn("Ignored " + ignoredEntries.toString() + " entries (" + newData.length.toString() + " OK) with null values for " + Instrument.Symbol);
|
//console.warn("Ignored " + ignoredEntries.toString() + " entries (" + newData.length.toString() + " OK) with null values for " + Instrument.Symbol);
|
||||||
}
|
}
|
||||||
if (newData.length > 0) {
|
if (newData.length > 0) {
|
||||||
lastSuccessfulFetch = new Date();
|
vars.fetchTiming.lastSuccessfulFetch = new Date();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Instrument.SingleDayData || Instrument.SingleDayData[0].DT != newData[0].DT || newData.length > Instrument.SingleDayData.length) {
|
if (!Instrument.SingleDayData || Instrument.SingleDayData[0].DT != newData[0].DT || newData.length > Instrument.SingleDayData.length) {
|
||||||
@ -960,7 +915,11 @@ function getYahooSingleDayData(Instrument) {
|
|||||||
logWarning({ MSG: "No data received for symbol: " + Instrument.Symbol, response: response });
|
logWarning({ MSG: "No data received for symbol: " + Instrument.Symbol, response: response });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
failure: function (response) { console.error("getYahooSingleDayData error:"); console.info(response); }
|
failure: function (response) {
|
||||||
|
vars.fetchTiming.lastFetchDuration = (new Date().getTime()) - vars.fetchTiming.lastFetchStartTime;
|
||||||
|
console.error("getYahooSingleDayData error:");
|
||||||
|
console.info(response);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function getLseSingleDayData(Instrument) {
|
function getLseSingleDayData(Instrument) {
|
||||||
@ -968,6 +927,7 @@ function getLseSingleDayData(Instrument) {
|
|||||||
Instrument.lastFetchedSingleDay = new Date().getTime();
|
Instrument.lastFetchedSingleDay = new Date().getTime();
|
||||||
let fromDate = new Date().setHours(0, 0, 0);
|
let fromDate = new Date().setHours(0, 0, 0);
|
||||||
let toDate = new Date().getTime();
|
let toDate = new Date().getTime();
|
||||||
|
vars.fetchTiming.lastFetchStartTime = new Date().getTime();
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: "POST",
|
type: "POST",
|
||||||
contentType: "application/json",
|
contentType: "application/json",
|
||||||
@ -975,10 +935,15 @@ function getLseSingleDayData(Instrument) {
|
|||||||
dataType: "json",
|
dataType: "json",
|
||||||
data: JSON.stringify({ Symbol: Instrument.Symbol, FromDate: fromDate, ToDate: toDate }),
|
data: JSON.stringify({ Symbol: Instrument.Symbol, FromDate: fromDate, ToDate: toDate }),
|
||||||
success: function (response) {
|
success: function (response) {
|
||||||
|
vars.fetchTiming.lastFetchDuration = (new Date().getTime()) - vars.fetchTiming.lastFetchStartTime;
|
||||||
console.info("getLseSingleDayData: " + JSON.stringify(response));
|
console.info("getLseSingleDayData: " + JSON.stringify(response));
|
||||||
|
|
||||||
},
|
},
|
||||||
failure: function (response) { console.error("getLseSingleDayData error:"); console.info(response); }
|
failure: function (response) {
|
||||||
|
vars.fetchTiming.lastFetchDuration = (new Date().getTime()) - vars.fetchTiming.lastFetchStartTime;
|
||||||
|
console.error("getLseSingleDayData error:");
|
||||||
|
console.info(response);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1088,7 +1053,7 @@ function updateTables() {
|
|||||||
if (tablesUpdateTimings.updateNeeded && tablesUpdateTimings.lastUpdate + tablesUpdateTimings.timeBetweenUpdates < new Date().getTime()) {
|
if (tablesUpdateTimings.updateNeeded && tablesUpdateTimings.lastUpdate + tablesUpdateTimings.timeBetweenUpdates < new Date().getTime()) {
|
||||||
tablesUpdateTimings.lastUpdate = new Date().getTime();
|
tablesUpdateTimings.lastUpdate = new Date().getTime();
|
||||||
|
|
||||||
createHoldingsTable();
|
createHoldingsTable(Instruments, vars);
|
||||||
createAccountsTables();
|
createAccountsTables();
|
||||||
createAnalysisTable();
|
createAnalysisTable();
|
||||||
createIndexMarquee();
|
createIndexMarquee();
|
||||||
@ -1096,570 +1061,6 @@ function updateTables() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createHoldingsTable() {
|
|
||||||
function getHoldings() {
|
|
||||||
function aggregateHoldings(i) {
|
|
||||||
let aggUnits = 0;
|
|
||||||
let aggBookCost = 0;
|
|
||||||
let aggBookCostGBP = 0;
|
|
||||||
let minPurchaseData = new Date().getTime();
|
|
||||||
let maxPurchaseDate = 0;
|
|
||||||
let marketIsOpen = Instruments.MarketIsOpen(i);
|
|
||||||
let exchangeRate = Instruments.GetExchangeRate(i.Currency, 'GBP');
|
|
||||||
let accountNames = [];
|
|
||||||
let aggSingleDayGainGBP = 0;
|
|
||||||
|
|
||||||
for (let hn = 0; hn < i.Holdings.length; hn++) {
|
|
||||||
let h = i.Holdings[hn];
|
|
||||||
|
|
||||||
if (h.SoldDate == null && h.NoUnits != 0) {
|
|
||||||
aggUnits += h.NoUnits;
|
|
||||||
aggBookCost += h.NoUnits * h.PurchasePricePerUnit;
|
|
||||||
//aggBookCostGBP += h.NoUnits * h.PurchasePricePerUnit / exchangeRate;
|
|
||||||
aggBookCostGBP += h.BookCostGBP;
|
|
||||||
//bookCostPerAccount.addCategoryAmount(h.AccountName, (h.NoUnits * h.PurchasePricePerUnit) / exchangeRate);
|
|
||||||
bookCostPerAccount.addCategoryAmount(h.AccountName, h.BookCostGBP);
|
|
||||||
|
|
||||||
if (h.PurchaseDate < minPurchaseData) { minPurchaseData = h.PurchaseDate; }
|
|
||||||
if (h.PurchaseDate > maxPurchaseDate) { maxPurchaseDate = h.PurchaseDate; }
|
|
||||||
if (!accountNames.includes(h.AccountName)) { accountNames.push(h.AccountName); }
|
|
||||||
|
|
||||||
if (i.CurrentPrice && exchangeRate) {
|
|
||||||
aggSingleDayGainGBP += ((i.CurrentPrice - (h.PurchaseDate > i.SingleDayStartDate ? h.PurchasePricePerUnit : i.SingleDayPreviousClose)) * h.NoUnits) / exchangeRate;
|
|
||||||
currentValuePerAccount.addCategoryAmount(h.AccountName, (h.NoUnits * i.CurrentPrice) / exchangeRate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let aggPurchasePricePerUnit = aggBookCost / aggUnits;
|
|
||||||
let aggCurrentValue = 0,
|
|
||||||
aggCurrentValueGBP = 0,
|
|
||||||
aggGainGBP = 0,
|
|
||||||
aggSingleDayGainPercent = 0,
|
|
||||||
aggSingleDayProfitOrLoss = 0;
|
|
||||||
if (i.CurrentPrice) {
|
|
||||||
aggCurrentValue = aggUnits * (i.Currency == 'GBp' ? i.CurrentPrice / 100 : i.CurrentPrice);
|
|
||||||
if (exchangeRate) {
|
|
||||||
aggCurrentValueGBP = (aggUnits * i.CurrentPrice) / exchangeRate;
|
|
||||||
//aggGainGBP = ((i.CurrentPrice - aggPurchasePricePerUnit) * aggUnits) / exchangeRate;
|
|
||||||
aggGainGBP = aggCurrentValueGBP - aggBookCostGBP;
|
|
||||||
aggSingleDayGainPercent = ((i.CurrentPrice / i.SingleDayPreviousClose) - 1) * 100;
|
|
||||||
aggSingleDayProfitOrLoss = aggSingleDayGainGBP < 0 ? 'loss' : 'profit';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
displayOrder: i.DisplayOrder,
|
|
||||||
holdingNumber: 0,
|
|
||||||
instrumentType: i.InstrumentType,
|
|
||||||
instrumentName: i.InstrumentName,
|
|
||||||
displayName: i.DisplayName,
|
|
||||||
symbol: i.Symbol,
|
|
||||||
currency: i.Currency,
|
|
||||||
accountName: accountNames.length > 1 ? 'Multiple (' + accountNames.length + ')' : accountNames[0],
|
|
||||||
accountNames: accountNames.length > 1 ? accountNames.join(', ') : '',
|
|
||||||
purchaseDateMin: minPurchaseData,
|
|
||||||
purchaseDateMax: maxPurchaseDate,
|
|
||||||
noUnits: aggUnits,
|
|
||||||
purchasePricePerUnit: aggPurchasePricePerUnit,
|
|
||||||
bookCost: aggBookCost,
|
|
||||||
bookCostGBP: aggBookCostGBP,
|
|
||||||
currentValue: aggCurrentValue,
|
|
||||||
gain: (i.Currency == 'GBp' ? (i.CurrentPrice - aggPurchasePricePerUnit) / 100 : (i.CurrentPrice - aggPurchasePricePerUnit)) * aggUnits,
|
|
||||||
//gainPercent: (i.CurrentPrice - aggPurchasePricePerUnit) / aggPurchasePricePerUnit * 100,
|
|
||||||
gainPercent: aggGainGBP / aggBookCostGBP * 100,
|
|
||||||
currentValueGBP: aggCurrentValueGBP,
|
|
||||||
gainGBP: aggGainGBP,
|
|
||||||
singleDayGainGBP: aggSingleDayGainGBP,
|
|
||||||
singleDayGainPercent: aggSingleDayGainPercent,
|
|
||||||
singleDayProfitOrLoss: aggSingleDayProfitOrLoss,
|
|
||||||
currentPrice: i.CurrentPrice,
|
|
||||||
currentExchangeRate: exchangeRate,
|
|
||||||
marketIsOpen: marketIsOpen
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
//Create a structure to summarise total holdings in each currency, to be used in the pie chart
|
|
||||||
let holdingCurrencies = {
|
|
||||||
data: [],
|
|
||||||
indexOfHoldingCurrency: function (symbol) { return this.data.map(function (item) { return item.symbol; }).indexOf(symbol); },
|
|
||||||
getHoldingCurrency: function (symbol) { return this.data[this.indexOfHoldingCurrency(symbol)]; },
|
|
||||||
addHoldingCurrency: function (symbol, amount) {
|
|
||||||
let c = this.getHoldingCurrency(symbol);
|
|
||||||
if (c) {
|
|
||||||
c.total += amount;
|
|
||||||
} else {
|
|
||||||
this.data.push({ symbol: symbol, total: amount });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getChartData: function () {
|
|
||||||
let d = []
|
|
||||||
for (let i = 0; i < this.data.length; i++) {
|
|
||||||
//d.push({ label: this.data[i].symbol, data: this.data[i].total / Instruments.GetExchangeRate(this.data[i].symbol, 'GBP') });
|
|
||||||
d.push({ label: this.data[i].symbol, data: this.data[i].total });
|
|
||||||
}
|
|
||||||
return d;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let holdingCurrenciesCurrentValue = {
|
|
||||||
data: [],
|
|
||||||
indexOfHoldingCurrency: function (symbol) { return this.data.map(function (item) { return item.symbol; }).indexOf(symbol); },
|
|
||||||
getHoldingCurrency: function (symbol) { return this.data[this.indexOfHoldingCurrency(symbol)]; },
|
|
||||||
addHoldingCurrency: function (symbol, amount) {
|
|
||||||
let c = this.getHoldingCurrency(symbol);
|
|
||||||
if (c) {
|
|
||||||
c.total += amount;
|
|
||||||
} else {
|
|
||||||
this.data.push({ symbol: symbol, total: amount });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getChartData: function () {
|
|
||||||
let d = []
|
|
||||||
for (let i = 0; i < this.data.length; i++) {
|
|
||||||
d.push({ label: this.data[i].symbol, data: this.data[i].total / Instruments.GetExchangeRate(this.data[i].symbol, 'GBP') });
|
|
||||||
}
|
|
||||||
return d;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let bookCostPerAccount = createCategoryAggregator();
|
|
||||||
let currentValuePerAccount = createCategoryAggregator();
|
|
||||||
let totalHoldingsValueGBP = 0;
|
|
||||||
let totalBookCostGBP = 0;
|
|
||||||
let totalValueGBP = 0;
|
|
||||||
let totalGainGBP = 0;
|
|
||||||
let todaysGainGBP = 0;
|
|
||||||
let maxCurrentValueGBP = 0;
|
|
||||||
let groupHoldings = $('#groupBySymbol').prop('checked');
|
|
||||||
let r = [];
|
|
||||||
let w = []; //Watchlist (instruments with no current holdings)
|
|
||||||
|
|
||||||
for (let n = 0; n < Instruments.Data.length; n++) {
|
|
||||||
let i = Instruments.Data[n];
|
|
||||||
//if (i.InstrumentType == 'EQUITY') {
|
|
||||||
if (i.InstrumentType == 'EQUITY' || i.InstrumentType == 'ETF' || i.InstrumentType == 'CRYPTOCURRENCY' || i.InstrumentType == 'CURRENCY') {
|
|
||||||
//if (i.CurrentPrice) {
|
|
||||||
if (groupHoldings) {
|
|
||||||
let agg = aggregateHoldings(i);
|
|
||||||
if (agg.noUnits > 0) {
|
|
||||||
r.push(agg);
|
|
||||||
|
|
||||||
//holdingCurrencies.addHoldingCurrency(i.Currency, agg.noUnits * agg.purchasePricePerUnit);
|
|
||||||
//holdingCurrencies.addHoldingCurrency(i.Currency.toUpperCase(), agg.noUnits * agg.purchasePricePerUnit / (i.Currency == "GBp" ? 100 : 1));
|
|
||||||
holdingCurrencies.addHoldingCurrency(i.Currency.toUpperCase(), agg.bookCostGBP);
|
|
||||||
if (i.CurrentPrice) {
|
|
||||||
if (agg.currentValueGBP > maxCurrentValueGBP) { maxCurrentValueGBP = agg.currentValueGBP };
|
|
||||||
//holdingCurrenciesCurrentValue.addHoldingCurrency(i.Currency, agg.noUnits * i.CurrentPrice);
|
|
||||||
holdingCurrenciesCurrentValue.addHoldingCurrency(i.Currency.toUpperCase(), agg.noUnits * i.CurrentPrice / (i.Currency == "GBp" ? 100 : 1));
|
|
||||||
if (agg.currentExchangeRate) {
|
|
||||||
totalBookCostGBP += agg.bookCostGBP;
|
|
||||||
totalValueGBP += agg.currentValueGBP;
|
|
||||||
totalGainGBP += agg.gainGBP;
|
|
||||||
todaysGainGBP += (agg.marketIsOpen == 0 ? 0 : agg.singleDayGainGBP);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
//No current holdings for this instrument - add it to the watchlist
|
|
||||||
if (i.WatchlistID) {
|
|
||||||
w.push(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let marketIsOpen = Instruments.MarketIsOpen(i);
|
|
||||||
let exchangeRate = Instruments.GetExchangeRate(i.Currency, 'GBP');
|
|
||||||
|
|
||||||
let noCurrentHoldings = 0;
|
|
||||||
for (let hn = 0; hn < i.Holdings.length; hn++) {
|
|
||||||
let h = i.Holdings[hn];
|
|
||||||
if (h.SoldDate == null) {
|
|
||||||
noCurrentHoldings++;
|
|
||||||
let holdingBookCost = h.NoUnits * h.PurchasePricePerUnit;
|
|
||||||
|
|
||||||
let currentHoldingValue = h.NoUnits * (i.Currency == 'GBp' ? i.CurrentPrice / 100 : i.CurrentPrice);
|
|
||||||
|
|
||||||
let holdingCurrentValueGBP = 0,
|
|
||||||
holdingGainGBP = 0,
|
|
||||||
singleDayGainGBP = 0,
|
|
||||||
singleDayGainPercent = 0,
|
|
||||||
singleDayProfitOrLoss = 0;
|
|
||||||
if (i.CurrentPrice) {
|
|
||||||
let currentHoldingValue = h.NoUnits * (i.Currency == 'GBp' ? i.CurrentPrice / 100 : i.CurrentPrice);
|
|
||||||
|
|
||||||
if (exchangeRate) {
|
|
||||||
let holdingCurrentValueGBP = (h.NoUnits * i.CurrentPrice) / exchangeRate;
|
|
||||||
//let holdingGainGBP = ((i.CurrentPrice - h.PurchasePricePerUnit) * h.NoUnits) / exchangeRate;
|
|
||||||
let holdingGainGBP = holdingCurrentValueGBP - h.BookCostGBP;
|
|
||||||
|
|
||||||
let singleDayGainGBP = ((i.CurrentPrice - (h.PurchaseDate > i.SingleDayStartDate ? h.PurchasePricePerUnit : i.SingleDayPreviousClose)) * h.NoUnits) / exchangeRate;
|
|
||||||
let singleDayGainPercent = ((i.CurrentPrice / i.SingleDayPreviousClose) - 1) * 100;
|
|
||||||
let singleDayProfitOrLoss = singleDayGainGBP < 0 ? 'loss' : 'profit';
|
|
||||||
|
|
||||||
if (holdingCurrentValueGBP > maxCurrentValueGBP) { maxCurrentValueGBP = holdingCurrentValueGBP };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
r.push({
|
|
||||||
displayOrder: i.DisplayOrder,
|
|
||||||
holdingNumber: hn,
|
|
||||||
instrumentType: i.InstrumentType,
|
|
||||||
instrumentName: i.InstrumentName,
|
|
||||||
displayName: i.DisplayName,
|
|
||||||
symbol: i.Symbol,
|
|
||||||
currency: i.Currency,
|
|
||||||
accountName: h.AccountName,
|
|
||||||
purchaseDateMin: h.PurchaseDate,
|
|
||||||
purchaseDateMax: h.PurchaseDate,
|
|
||||||
noUnits: h.NoUnits,
|
|
||||||
purchasePricePerUnit: h.PurchasePricePerUnit,
|
|
||||||
bookCost: holdingBookCost,
|
|
||||||
currentValue: currentHoldingValue,
|
|
||||||
gain: (i.Currency == 'GBp' ? (i.CurrentPrice - h.PurchasePricePerUnit) / 100 : (i.CurrentPrice - h.PurchasePricePerUnit)) * h.NoUnits,
|
|
||||||
//gainPercent: (i.CurrentPrice - h.PurchasePricePerUnit) / h.PurchasePricePerUnit * 100,
|
|
||||||
gainPercent: holdingGainGBP / h.BookCostGBP * 100,
|
|
||||||
currentValueGBP: holdingCurrentValueGBP,
|
|
||||||
gainGBP: holdingGainGBP,
|
|
||||||
singleDayGainGBP: singleDayGainGBP,
|
|
||||||
singleDayGainPercent: singleDayGainPercent,
|
|
||||||
singleDayProfitOrLoss: singleDayProfitOrLoss,
|
|
||||||
currentPrice: i.CurrentPrice,
|
|
||||||
currentExchangeRate: exchangeRate,
|
|
||||||
marketIsOpen: marketIsOpen
|
|
||||||
});
|
|
||||||
|
|
||||||
holdingCurrencies.addHoldingCurrency(i.Currency, h.NoUnits * h.PurchasePricePerUnit);
|
|
||||||
if (i.CurrentPrice) {
|
|
||||||
holdingCurrenciesCurrentValue.addHoldingCurrency(i.Currency, h.NoUnits * i.CurrentPrice);
|
|
||||||
if (exchangeRate) {
|
|
||||||
currentValuePerAccount.addCategoryAmount(h.AccountName, holdingCurrentValueGBP);
|
|
||||||
bookCostPerAccount.addCategoryAmount(h.AccountName, holdingBookCost / exchangeRate);
|
|
||||||
|
|
||||||
//totalBookCostGBP += holdingBookCost / exchangeRate;
|
|
||||||
totalBookCostGBP += h.BookCostGBP;
|
|
||||||
totalValueGBP += holdingCurrentValueGBP;
|
|
||||||
totalGainGBP += holdingGainGBP;
|
|
||||||
todaysGainGBP += (marketIsOpen == 0 ? 0 : singleDayGainGBP);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (noCurrentHoldings == 0) {
|
|
||||||
//No current holdings for this instrument - add it to the watchlist
|
|
||||||
if (i.WatchlistID) {
|
|
||||||
w.push(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//} //if (i.CurrentPrice)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
totalBookCostGBP: totalBookCostGBP,
|
|
||||||
totalValueGBP: totalValueGBP,
|
|
||||||
totalGainGBP: totalGainGBP,
|
|
||||||
todaysGainGBP: todaysGainGBP,
|
|
||||||
maxCurrentValueGBP: maxCurrentValueGBP,
|
|
||||||
holdings: r,
|
|
||||||
watchlist: w,
|
|
||||||
holdingCurrencies: holdingCurrencies,
|
|
||||||
holdingCurrenciesCurrentValue: holdingCurrenciesCurrentValue,
|
|
||||||
bookCostPerAccount: bookCostPerAccount,
|
|
||||||
currentValuePerAccount: currentValuePerAccount
|
|
||||||
};
|
|
||||||
}
|
|
||||||
function createTable() {
|
|
||||||
let t = $("#tblMainHoldings");
|
|
||||||
if (t.length) {
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
let tbl = '<table id="tblMainHoldings" class="mainHoldings"><thead>' +
|
|
||||||
'<tr><th>Type</th>' +
|
|
||||||
'<th>Name</th>' +
|
|
||||||
'<th>Symbol</th>' +
|
|
||||||
'<th>Ccy</th>' +
|
|
||||||
'<th>Account</th>' +
|
|
||||||
'<th>Buy Date</th>' +
|
|
||||||
'<th>No Units</th>' +
|
|
||||||
'<th>Buy<br>Price</th>' +
|
|
||||||
'<th>Current<br>Price</th>' +
|
|
||||||
'<th>Book<br>Cost</th>' +
|
|
||||||
'<th>Current<br>Value</th>' +
|
|
||||||
'<th>Gain</th>' +
|
|
||||||
'<th>Gain £</th>' +
|
|
||||||
'<th>Gain %</th>' +
|
|
||||||
'<th>Current<br>Value £</th>' +
|
|
||||||
'<th>Allocation</th>' +
|
|
||||||
'<th>Today's<br>Gain £</th>' +
|
|
||||||
'<th>Today's<br>Gain %</th>' +
|
|
||||||
'</tr></thead><tbody id="tbMainHoldings"></tbody><tfoot id="tfMainHoldings"><tr><td colspan="18"> </td></tr></tfoot></table>';
|
|
||||||
|
|
||||||
//$('#holdingsDiv').html(tbl);
|
|
||||||
$('#holdingsTableDiv').html(tbl);
|
|
||||||
|
|
||||||
$("#tblMainHoldings").tablesorter({ ignoreCase: false });
|
|
||||||
$("#tblMainHoldings").trigger("sorton", Cookies.get('sortHoldings'));
|
|
||||||
$('#tblMainHoldings').on('sortEnd', function (event) {
|
|
||||||
// Prints the current sort order to the console
|
|
||||||
if (event.target.config.sortList.length > 3) {
|
|
||||||
console.info({ "Setting sortHoldings Cookie (array too long?)": event.target.config.sortList });
|
|
||||||
}
|
|
||||||
Cookies.set('sortHoldings', event.target.config.sortList, { 'sameSite': 'strict' });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function createWatchlistTable() {
|
|
||||||
let t = $("#tblWatchlist");
|
|
||||||
if (t.length) {
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
let tbl = '<table id="tblWatchlist" class="watchlist"><thead>' +
|
|
||||||
'<tr><th>Type</th>' +
|
|
||||||
'<th>Name</th>' +
|
|
||||||
'<th>Symbol</th>' +
|
|
||||||
'<th>Ccy</th>' +
|
|
||||||
'<th>Current Price</th>' +
|
|
||||||
'<th>Today's Gain</th>' +
|
|
||||||
'<th>Today's Gain %</th>' +
|
|
||||||
'<th>Since Last Sold<th>' +
|
|
||||||
'</tr></thead><tbody id="tbWatchlist"></tbody></table>';
|
|
||||||
|
|
||||||
$('#watchlistTableDiv').html(tbl);
|
|
||||||
|
|
||||||
$("#tblWatchlist").tablesorter({ ignoreCase: false });
|
|
||||||
$("#tblWatchlist").trigger("sorton", Cookies.get('sortWatchlist'));
|
|
||||||
$('#tblWatchlist').on('sortEnd', function (event) {
|
|
||||||
// Prints the current sort order to the console
|
|
||||||
Cookies.set('sorWatchlist', event.target.config.sortList, { 'sameSite': 'strict' });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function addTableRow(rowID, content, tbodyID, highlightUpdates) {
|
|
||||||
let r = $("#" + rowID);
|
|
||||||
let newText = $(content).text(); //$(content).text();
|
|
||||||
if (r.length) {
|
|
||||||
let currentContent = r.text();
|
|
||||||
if (currentContent != newText) { //Only update row if the content has changed
|
|
||||||
$("#" + rowID).replaceWith(content);
|
|
||||||
if (highlightUpdates) {
|
|
||||||
$("#" + rowID).addClass("highlighted");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$("#" + tbodyID).append(content); //.addClass("highlighted");
|
|
||||||
//$("#" + rowID).addClass("highlighted");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function getPercentCell(backgroundColor, cellWidth, valuePercent, label) {
|
|
||||||
let labelText = label ? label : (valuePercent.toFixed(1) + '%');
|
|
||||||
let barWidth = ((valuePercent / 100) * cellWidth);
|
|
||||||
return '<td>' +
|
|
||||||
'<div class="pcBackground" style="background-color: ' + backgroundColor + '; width:' + barWidth.toFixed(0) + 'px;"> </div>' +
|
|
||||||
'<div class="pcLabel" style="width:' + cellWidth + 'px">' + labelText + '</div>' +
|
|
||||||
'</td>';
|
|
||||||
}
|
|
||||||
function profitOrLoss(value) {
|
|
||||||
return value >= 0 ? "profit" : "loss";
|
|
||||||
}
|
|
||||||
|
|
||||||
let t = getHoldings();
|
|
||||||
let totalBookCostGBP = t.totalBookCostGBP;
|
|
||||||
let totalValueGBP = t.totalValueGBP;
|
|
||||||
let totalGainGBP = t.totalGainGBP;
|
|
||||||
let todaysGainGBP = t.todaysGainGBP;
|
|
||||||
let maxCurrentValueGBP = t.maxCurrentValueGBP;
|
|
||||||
let holdings = t.holdings;
|
|
||||||
let watchlist = t.watchlist;
|
|
||||||
let holdingCurrencies = t.holdingCurrencies;
|
|
||||||
let holdingCurrenciesCurrentValue = t.holdingCurrenciesCurrentValue;
|
|
||||||
let bookCostPerAccount = t.bookCostPerAccount;
|
|
||||||
let currentValuePerAccount = t.currentValuePerAccount;
|
|
||||||
|
|
||||||
let maxAllocationPercent = (maxCurrentValueGBP / totalValueGBP) * 100;
|
|
||||||
let allocationScaleFactor = 100 / maxAllocationPercent;
|
|
||||||
|
|
||||||
let altRow = 1;
|
|
||||||
|
|
||||||
createTable();
|
|
||||||
for (let n = 0; n < holdings.length; n++) {
|
|
||||||
try {
|
|
||||||
altRow = !altRow;
|
|
||||||
|
|
||||||
let h = holdings[n];
|
|
||||||
let type = h.instrumentType == 'CURRENCY' ? '$' : h.instrumentType.substring(0, 1);
|
|
||||||
//let profitOrLoss = h.currentPrice < h.purchasePricePerUnit ? 'loss' : 'profit'; //let profitOrLoss = i.CurrentPrice < h.PurchasePricePerUnit ? 'loss' : 'profit';
|
|
||||||
let openOrClosed = h.marketIsOpen == 1 ? 'open' : 'closed';
|
|
||||||
let allocationPercent = h.currentValueGBP / totalValueGBP * 100;
|
|
||||||
let scaledAllocation = allocationScaleFactor * allocationPercent;
|
|
||||||
|
|
||||||
let rowID = "holdingRow_" + h.displayOrder + "_" + h.holdingNumber;
|
|
||||||
let row = '<tr' + (altRow ? ' class="altShareRow"' : '') + ' id="' + rowID + '">' +
|
|
||||||
'<td class="' + openOrClosed + '">' + type + '</td>' +
|
|
||||||
'<td class="' + openOrClosed + '">' + (h.displayName != '' ? h.displayName : h.instrumentName) + '</td>' +
|
|
||||||
'<td><a target="_blank" href="https://uk.finance.yahoo.com/quote/' + h.symbol + '">' + h.symbol + '</a>' + '</td>' +
|
|
||||||
'<td class="' + openOrClosed + '">' + h.currency + '</td>' +
|
|
||||||
'<td class="' + openOrClosed + '" title="' + (h.accountNames ? h.accountNames : '') + '">' + h.accountName + '</td>' +
|
|
||||||
'<td class="' + openOrClosed + '"' + (h.purchaseDateMin == h.purchaseDateMax ? ('>' + new Date(h.purchaseDateMin).yyyymmdd()) : (' title="' + new Date(h.purchaseDateMax).yyyymmdd() + ' - ' + new Date(h.purchaseDateMin).yyyymmdd() + '">' + new Date(h.purchaseDateMin).yyyymmdd()) + '+') + '</td>' +
|
|
||||||
//'<td class="num ' + openOrClosed + '">' + formatAmount(h.noUnits, '', 0) + '</td>' +
|
|
||||||
'<td class="num ' + openOrClosed + '">' + h.noUnits.autoScale() + '</td>' +
|
|
||||||
'<td class="num ' + openOrClosed + '">' + formatAmount(h.purchasePricePerUnit, h.currency, 2) + '</td>' + //Buy Price
|
|
||||||
'<td class="num ' + openOrClosed + '">' + (h.currentPrice ? formatAmount(h.currentPrice, h.currency, 2) : '') + '</td>' + //Current Price
|
|
||||||
'<td class="num ' + openOrClosed + '">' + formatAmount(h.currency == 'GBp' ? h.bookCost / 100 : h.bookCost, h.currency == 'GBp' ? 'GBP' : h.currency, 0) + '</td>' + //Book Cost
|
|
||||||
'<td class="num ' + openOrClosed + '">' + (h.currentExchangeRate ? formatAmount(h.currentValue, h.currency == 'GBp' ? 'GBP' : h.currency, 0) : '') + '</td>' + //Current Value
|
|
||||||
'<td class="num ' + profitOrLoss(h.currentPrice - h.purchasePricePerUnit) + '">' + (h.currentPrice ? formatAmount(h.gain, h.currency == 'GBp' ? 'GBP' : h.currency, 0) : '') + '</td>' + //Gain
|
|
||||||
'<td class="num ' + profitOrLoss(h.gainGBP) + '">' + (h.currentExchangeRate ? formatAmount(h.gainGBP, 'GBP', 0) : '') + '</td>' + //Gain £
|
|
||||||
'<td class="num ' + profitOrLoss(h.gainGBP) + '">' + (h.currentExchangeRate ? h.gainPercent.toFixed(1) + '%' : '') + '</td>' + //Gain %
|
|
||||||
'<td class="num ' + openOrClosed + '">' + (h.currentExchangeRate ? formatAmount(h.currentValueGBP, 'GBP', 0) : '') + '</td>' + //Current Value £
|
|
||||||
(h.currentExchangeRate ? getPercentCell('#3f2d95', 70, scaledAllocation, allocationPercent.toFixed(1) + '%') : '') + //Allocation
|
|
||||||
(h.currentExchangeRate ? (h.marketIsOpen == 0 ? '<td/>' : '<td class="num ' + h.singleDayProfitOrLoss + '">' + formatAmount(h.singleDayGainGBP, 'GBP', 0) + '</td>') : '') + //Today's Gain £
|
|
||||||
(h.singleDayGainPercent ? (h.marketIsOpen == 0 ? '<td/>' : '<td class="num ' + h.singleDayProfitOrLoss + '">' + h.singleDayGainPercent.toFixed(1) + '%</td>') : '<td/>') + //Today's Gain %
|
|
||||||
'</tr>';
|
|
||||||
addTableRow(rowID, row, "tbMainHoldings", true);
|
|
||||||
}
|
|
||||||
catch (err) {
|
|
||||||
logError("Error adding " + holdings[n].symbol + " to main holdings table: " + err.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Add the totals line at the bottom of the table
|
|
||||||
//let profitOrLoss = totalValueGBP < totalBookCostGBP ? 'loss' : 'profit';
|
|
||||||
let todaysProfitOrLoss = todaysGainGBP < 0 ? 'loss' : 'profit';
|
|
||||||
let lSF = '';
|
|
||||||
if (lastSuccessfulFetch) {
|
|
||||||
let lastFetchAge = ((new Date()) - lastSuccessfulFetch);
|
|
||||||
let staleFetch = ((new Date()) - lastSuccessfulFetch) > (timespans.oneMinute * 3);
|
|
||||||
lSF = (lastSuccessfulFetch ? ' - Last Successful Fetch: ' + (staleFetch ? '<span class="loss">' : '') + lastSuccessfulFetch.yyyymmddhhmmss() + (staleFetch ? '</span>' : '') : '');
|
|
||||||
}
|
|
||||||
let row = '<tr id="tfHoldingsTotals"><td></td><td id="holdingsLastUpdated" colspan="8">Updated: ' + new Date().yyyymmddhhmmss() + lSF + '</td>' +
|
|
||||||
'<td class="num">' + formatAmount(totalBookCostGBP, 'GBP', 0) + '</td>' + //Book Cost
|
|
||||||
'<td></td><td></td>' +
|
|
||||||
'<td class="num ' + profitOrLoss(totalValueGBP - totalBookCostGBP) + '">' + formatAmount(totalGainGBP, 'GBP', 0) + '</td>' + //Gain GBP
|
|
||||||
'<td class="num ' + profitOrLoss(totalValueGBP - totalBookCostGBP) + '">' + formatAmount((((totalValueGBP / totalBookCostGBP) - 1) * 100), '', 1) + '%</td>' + //Gain %
|
|
||||||
'<td class="num">' + formatAmount(totalValueGBP, 'GBP', 0) + '</td>' + //Current Value
|
|
||||||
'<td> </td>' + //Allocation
|
|
||||||
'<td class="num ' + todaysProfitOrLoss + '">' + formatAmount(todaysGainGBP, 'GBP', 0) + '</td>' + //Today's Gain GBP
|
|
||||||
//'<td class="num ' + todaysProfitOrLoss + '">' + formatAmount(((todaysGainGBP / totalBookCostGBP) * 100), '', 1) + '%</td>' + //Today's Gain %
|
|
||||||
'<td class="num ' + todaysProfitOrLoss + '">' + formatAmount(((todaysGainGBP / (totalValueGBP-todaysGainGBP)) * 100), '', 1) + '%</td>' + //Today's Gain %
|
|
||||||
'</tr>';
|
|
||||||
addTableRow("tfHoldingsTotals", row, "tfMainHoldings", false);
|
|
||||||
|
|
||||||
//Create the watchlist table
|
|
||||||
createWatchlistTable();
|
|
||||||
altRow = 1;
|
|
||||||
for (let n = 0; n < watchlist.length; n++) {
|
|
||||||
altRow = !altRow;
|
|
||||||
let wi = watchlist[n];
|
|
||||||
try {
|
|
||||||
let type = wi.InstrumentType == 'CURRENCY' ? '$' : wi.InstrumentType.substring(0, 1);
|
|
||||||
let openOrClosed = Instruments.MarketIsOpen(wi) == 1 ? 'open' : 'closed';
|
|
||||||
let profitOrLoss = wi.CurrentPrice < wi.SingleDayPreviousClose ? 'loss' : 'profit';
|
|
||||||
let rowID = "watchlistRow_" + wi.DisplayOrder;
|
|
||||||
|
|
||||||
let sinceLastSold = '<td></td>';
|
|
||||||
if (wi.LastSoldPrice) {
|
|
||||||
let delta = (wi.CurrentPrice - wi.LastSoldPrice) / wi.LastSoldPrice * 100;
|
|
||||||
let tooltip = new Date(wi.LastSoldDate).yyyymmdd() + ' @ ' + wi.LastSoldPrice.toFixed(2);
|
|
||||||
sinceLastSold = '<td title="' + tooltip + '" class="num ' + (delta < 0 ? 'loss' : 'profit') + '">' + (delta).toFixed(1) + '%</td>';
|
|
||||||
}
|
|
||||||
|
|
||||||
//Type, Name, Symbol, Ccy, Current Price, Today's Gain, Today's Gain %
|
|
||||||
let row = '<tr' + (altRow ? ' class="altShareRow"' : '') + ' id="' + rowID + '">' +
|
|
||||||
'<td class="' + openOrClosed + '">' + type + '</td>' +
|
|
||||||
'<td class="' + openOrClosed + '">' + wi.InstrumentName + '</td>' +
|
|
||||||
'<td><a target="_blank" href="https://uk.finance.yahoo.com/quote/' + wi.Symbol + '">' + wi.Symbol + '</a>' + '</td>' +
|
|
||||||
'<td class="' + openOrClosed + '">' + wi.Currency + '</td>' +
|
|
||||||
'<td class="num ' + openOrClosed + '">' + (wi.CurrentPrice ? formatAmount(wi.CurrentPrice, wi.Currency, 2) : '') + '</td>' + //Current Price
|
|
||||||
(Instruments.marketIsOpen == 0 ? '<td/>' : '<td class="num ' + profitOrLoss + '">' + (wi.CurrentPrice ? formatAmount(wi.CurrentPrice - wi.SingleDayPreviousClose, wi.Currency, 2) : '') + '</td>') + //Today's Gain
|
|
||||||
(Instruments.marketIsOpen == 0 ? '<td/>' : '<td class="num ' + profitOrLoss + '">' + (wi.CurrentPrice ? ((wi.CurrentPrice - wi.SingleDayPreviousClose) / wi.SingleDayPreviousClose * 100).toFixed(1) : '') + '</td>') + //Today's Gain %
|
|
||||||
//'<td>' + (new Date(wi.LastSoldDate)).yyyymmdd() + '</td>' +
|
|
||||||
sinceLastSold +
|
|
||||||
'</tr>';
|
|
||||||
addTableRow(rowID, row, "tbWatchlist", true);
|
|
||||||
}
|
|
||||||
catch (err) {
|
|
||||||
logError("Error adding " + wi.symbol + " to watchlist table: " + err.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Flash the last updated cell
|
|
||||||
$("#holdingsLastUpdated").addClass("highlighted");
|
|
||||||
//Remove the highlight from all rows/cells
|
|
||||||
$("#tblMainHoldings").find(".highlighted").removeClass("highlighted", 1200);
|
|
||||||
$("#tblWatchlist").find(".highlighted").removeClass("highlighted", 1200);
|
|
||||||
|
|
||||||
//Tell tablesorter that the table has been updated
|
|
||||||
var resort = true;
|
|
||||||
$("#tblMainHoldings").trigger("update", [resort]);
|
|
||||||
|
|
||||||
//Update the daily holdings value table in the DB
|
|
||||||
if (totalValueGBP != lastTotalHoldingsValue) {
|
|
||||||
$.ajax({
|
|
||||||
type: "POST",
|
|
||||||
contentType: "application/json",
|
|
||||||
url: "SharePrices.aspx/SetTotalHoldingsValue",
|
|
||||||
dataType: "json",
|
|
||||||
data: JSON.stringify({ TotalValue: totalValueGBP }),
|
|
||||||
success: function (response) {
|
|
||||||
previousDay = response.PreviousDay;
|
|
||||||
previousClose = response.PreviousClose;
|
|
||||||
previousWeek = response.PreviousWeekDate;
|
|
||||||
previousWeekClose = response.PreviousWeekClose;
|
|
||||||
|
|
||||||
//Update the total holdings span in the site banner
|
|
||||||
let formattedAmount = formatAmount(totalValueGBP, "GBP", 0);
|
|
||||||
let holdingsHTML = '<span style="font-size: small;">Total holdings: </span>' + '<span style="font-size: large">' + formattedAmount + '</span> ';
|
|
||||||
holdingsHTML += '<span style="font-size: small;"><span class="' + (previousClose < totalValueGBP ? 'profit">+' : 'loss">') + formatAmount(totalValueGBP - previousClose, "GBP", 0) + '</span> (d)';
|
|
||||||
holdingsHTML += ' / <span class="' + (previousWeekClose < totalValueGBP ? 'profit">+' : 'loss">') + formatAmount(totalValueGBP - previousWeekClose, "GBP", 0) + '</span> (w)</span>';
|
|
||||||
$("#spnTotalHoldings").html(holdingsHTML);
|
|
||||||
//Set the page title
|
|
||||||
document.title = formattedAmount + ' - ' + initialPageTitle;
|
|
||||||
|
|
||||||
lastTotalHoldingsValue = totalValueGBP;
|
|
||||||
},
|
|
||||||
failure: function (response) { console.error("SetTotalHoldingsValue error: " + JSON.stringify(response)); }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
//Create / update the currency allocation pie chart
|
|
||||||
function labelFormatter(label, series) {
|
|
||||||
return "<div class='pieLabel'>" + label + "<br/>" + Math.round(series.percent) + "%<br>" + formatAmount(series.data[0][1], 'GBP', 0) + "</div>";
|
|
||||||
}
|
|
||||||
let ops = {
|
|
||||||
series: {
|
|
||||||
downsample: { threshold: 0 },
|
|
||||||
pie: { show: true, radius: 1, label: { show: true, radius: 0.7, formatter: labelFormatter } }
|
|
||||||
},
|
|
||||||
legend: { show: false },
|
|
||||||
grid: { hoverable: true },
|
|
||||||
tooltip: {
|
|
||||||
show: true,
|
|
||||||
content: "%p.0%, %s, n=%n", // show percentages, rounding to 2 decimal places
|
|
||||||
shifts: { x: 20, y: 0 },
|
|
||||||
defaultTheme: false
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let cd = holdingCurrencies.getChartData();
|
|
||||||
if (cd.length) {
|
|
||||||
$.plot('#divHoldingCurrenciesChart', cd, ops);
|
|
||||||
}
|
|
||||||
|
|
||||||
cd = holdingCurrenciesCurrentValue.getChartData();
|
|
||||||
if (cd.length) {
|
|
||||||
$.plot('#divHoldingCurrenciesChartCurrentValue', cd, ops);
|
|
||||||
}
|
|
||||||
|
|
||||||
function labelFormatter2(label, series) {
|
|
||||||
return "<div class='pieLabel'>" + label + "<br/>" + formatAmount(series.data[0][1], 'GBP', 0) + "</div>";
|
|
||||||
}
|
|
||||||
ops.series.pie.label.formatter = labelFormatter2;
|
|
||||||
cd = bookCostPerAccount.getChartData();
|
|
||||||
if (cd.length) {
|
|
||||||
$.plot('#divAccountBookCost', cd, ops);
|
|
||||||
}
|
|
||||||
|
|
||||||
cd = currentValuePerAccount.getChartData();
|
|
||||||
if (cd.length) {
|
|
||||||
$.plot('#divAccountValue', cd, ops);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function createAccountsTables() {
|
function createAccountsTables() {
|
||||||
function getAccounts() {
|
function getAccounts() {
|
||||||
let accounts = [];
|
let accounts = [];
|
||||||
|
@ -6,6 +6,55 @@
|
|||||||
oneWeek: 7 * 24 * 60 * 60 * 1000,
|
oneWeek: 7 * 24 * 60 * 60 * 1000,
|
||||||
oneMonth: 31 * 24 * 60 * 60 * 1000 };
|
oneMonth: 31 * 24 * 60 * 60 * 1000 };
|
||||||
|
|
||||||
|
export function formatAmount(amount, currency, decimals) {
|
||||||
|
if (amount) {
|
||||||
|
let isNegative = (amount < 0);
|
||||||
|
let s = Math.abs(amount).toFixed(decimals);
|
||||||
|
if (Math.abs(amount) < 0.01) {
|
||||||
|
s = Math.abs(amount).toPrecision(2);
|
||||||
|
}
|
||||||
|
let pos = s.indexOf('.');
|
||||||
|
let wholeNumber = (pos >= 0) ? s.substring(0, pos) : s;
|
||||||
|
let decimal = (pos >= 0) ? s.substring(pos) : '';
|
||||||
|
s = '';
|
||||||
|
while (wholeNumber.length > 0) {
|
||||||
|
//if (s.length > 0 && wholeNumber!='-') s = ',' + s; //Don't add a comma if the only thing left to add to the string is the minus sign
|
||||||
|
if (s.length > 0) s = ',' + s;
|
||||||
|
if (wholeNumber.length >= 3) {
|
||||||
|
s = wholeNumber.substring(wholeNumber.length - 3) + s;
|
||||||
|
wholeNumber = wholeNumber.substring(0, wholeNumber.length - 3);
|
||||||
|
} else {
|
||||||
|
s = wholeNumber + s;
|
||||||
|
wholeNumber = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s = s + decimal;
|
||||||
|
let sign = isNegative ? '-' : '';
|
||||||
|
switch (currency) {
|
||||||
|
case 'GBP':
|
||||||
|
s = sign + '£' + s;
|
||||||
|
break;
|
||||||
|
case 'GBp':
|
||||||
|
s = sign + s + 'p';
|
||||||
|
break;
|
||||||
|
case 'USD':
|
||||||
|
s = sign + 'U$' + s;
|
||||||
|
break;
|
||||||
|
case 'AUD':
|
||||||
|
s = sign + 'A$' + s;
|
||||||
|
break;
|
||||||
|
case 'EUR':
|
||||||
|
s = sign + '€' + s;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
s = sign + currency + s;
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
} else {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function getProfitOrLoss(BasePrice, CurrentPrice, ShowPercent) {
|
export function getProfitOrLoss(BasePrice, CurrentPrice, ShowPercent) {
|
||||||
//let priceMovement = (CurrentPrice - BasePrice).toFixed(2);
|
//let priceMovement = (CurrentPrice - BasePrice).toFixed(2);
|
||||||
let priceMovement = (CurrentPrice - BasePrice).autoDP();
|
let priceMovement = (CurrentPrice - BasePrice).autoDP();
|
||||||
|
591
Websites/SharePrices/SharePrices/scripts/SharePrices_Holdings.js
Normal file
591
Websites/SharePrices/SharePrices/scripts/SharePrices_Holdings.js
Normal file
@ -0,0 +1,591 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
import { formatAmount } from "./SharePrices_Common.js";
|
||||||
|
|
||||||
|
export function createHoldingsTable(Instruments, vars) {
|
||||||
|
function getHoldings() {
|
||||||
|
function aggregateHoldings(i) {
|
||||||
|
let aggUnits = 0;
|
||||||
|
let aggBookCost = 0;
|
||||||
|
let aggBookCostGBP = 0;
|
||||||
|
let minPurchaseData = new Date().getTime();
|
||||||
|
let maxPurchaseDate = 0;
|
||||||
|
let marketIsOpen = Instruments.MarketIsOpen(i);
|
||||||
|
let exchangeRate = Instruments.GetExchangeRate(i.Currency, 'GBP');
|
||||||
|
let accountNames = [];
|
||||||
|
let aggSingleDayGainGBP = 0;
|
||||||
|
|
||||||
|
for (let hn = 0; hn < i.Holdings.length; hn++) {
|
||||||
|
let h = i.Holdings[hn];
|
||||||
|
|
||||||
|
if (h.SoldDate == null && h.NoUnits != 0) {
|
||||||
|
aggUnits += h.NoUnits;
|
||||||
|
aggBookCost += h.NoUnits * h.PurchasePricePerUnit;
|
||||||
|
//aggBookCostGBP += h.NoUnits * h.PurchasePricePerUnit / exchangeRate;
|
||||||
|
aggBookCostGBP += h.BookCostGBP;
|
||||||
|
//bookCostPerAccount.addCategoryAmount(h.AccountName, (h.NoUnits * h.PurchasePricePerUnit) / exchangeRate);
|
||||||
|
bookCostPerAccount.addCategoryAmount(h.AccountName, h.BookCostGBP);
|
||||||
|
|
||||||
|
if (h.PurchaseDate < minPurchaseData) { minPurchaseData = h.PurchaseDate; }
|
||||||
|
if (h.PurchaseDate > maxPurchaseDate) { maxPurchaseDate = h.PurchaseDate; }
|
||||||
|
if (!accountNames.includes(h.AccountName)) { accountNames.push(h.AccountName); }
|
||||||
|
|
||||||
|
if (i.CurrentPrice && exchangeRate) {
|
||||||
|
aggSingleDayGainGBP += ((i.CurrentPrice - (h.PurchaseDate > i.SingleDayStartDate ? h.PurchasePricePerUnit : i.SingleDayPreviousClose)) * h.NoUnits) / exchangeRate;
|
||||||
|
currentValuePerAccount.addCategoryAmount(h.AccountName, (h.NoUnits * i.CurrentPrice) / exchangeRate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let aggPurchasePricePerUnit = aggBookCost / aggUnits;
|
||||||
|
let aggCurrentValue = 0,
|
||||||
|
aggCurrentValueGBP = 0,
|
||||||
|
aggGainGBP = 0,
|
||||||
|
aggSingleDayGainPercent = 0,
|
||||||
|
aggSingleDayProfitOrLoss = 0;
|
||||||
|
if (i.CurrentPrice) {
|
||||||
|
aggCurrentValue = aggUnits * (i.Currency == 'GBp' ? i.CurrentPrice / 100 : i.CurrentPrice);
|
||||||
|
if (exchangeRate) {
|
||||||
|
aggCurrentValueGBP = (aggUnits * i.CurrentPrice) / exchangeRate;
|
||||||
|
//aggGainGBP = ((i.CurrentPrice - aggPurchasePricePerUnit) * aggUnits) / exchangeRate;
|
||||||
|
aggGainGBP = aggCurrentValueGBP - aggBookCostGBP;
|
||||||
|
aggSingleDayGainPercent = ((i.CurrentPrice / i.SingleDayPreviousClose) - 1) * 100;
|
||||||
|
aggSingleDayProfitOrLoss = aggSingleDayGainGBP < 0 ? 'loss' : 'profit';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
displayOrder: i.DisplayOrder,
|
||||||
|
holdingNumber: 0,
|
||||||
|
instrumentType: i.InstrumentType,
|
||||||
|
instrumentName: i.InstrumentName,
|
||||||
|
displayName: i.DisplayName,
|
||||||
|
symbol: i.Symbol,
|
||||||
|
currency: i.Currency,
|
||||||
|
accountName: accountNames.length > 1 ? 'Multiple (' + accountNames.length + ')' : accountNames[0],
|
||||||
|
accountNames: accountNames.length > 1 ? accountNames.join(', ') : '',
|
||||||
|
purchaseDateMin: minPurchaseData,
|
||||||
|
purchaseDateMax: maxPurchaseDate,
|
||||||
|
noUnits: aggUnits,
|
||||||
|
purchasePricePerUnit: aggPurchasePricePerUnit,
|
||||||
|
bookCost: aggBookCost,
|
||||||
|
bookCostGBP: aggBookCostGBP,
|
||||||
|
currentValue: aggCurrentValue,
|
||||||
|
gain: (i.Currency == 'GBp' ? (i.CurrentPrice - aggPurchasePricePerUnit) / 100 : (i.CurrentPrice - aggPurchasePricePerUnit)) * aggUnits,
|
||||||
|
//gainPercent: (i.CurrentPrice - aggPurchasePricePerUnit) / aggPurchasePricePerUnit * 100,
|
||||||
|
gainPercent: aggGainGBP / aggBookCostGBP * 100,
|
||||||
|
currentValueGBP: aggCurrentValueGBP,
|
||||||
|
gainGBP: aggGainGBP,
|
||||||
|
singleDayGainGBP: aggSingleDayGainGBP,
|
||||||
|
singleDayGainPercent: aggSingleDayGainPercent,
|
||||||
|
singleDayProfitOrLoss: aggSingleDayProfitOrLoss,
|
||||||
|
currentPrice: i.CurrentPrice,
|
||||||
|
currentExchangeRate: exchangeRate,
|
||||||
|
marketIsOpen: marketIsOpen
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
//Create a structure to summarise total holdings in each currency, to be used in the pie chart
|
||||||
|
let holdingCurrencies = {
|
||||||
|
data: [],
|
||||||
|
indexOfHoldingCurrency: function (symbol) { return this.data.map(function (item) { return item.symbol; }).indexOf(symbol); },
|
||||||
|
getHoldingCurrency: function (symbol) { return this.data[this.indexOfHoldingCurrency(symbol)]; },
|
||||||
|
addHoldingCurrency: function (symbol, amount) {
|
||||||
|
let c = this.getHoldingCurrency(symbol);
|
||||||
|
if (c) {
|
||||||
|
c.total += amount;
|
||||||
|
} else {
|
||||||
|
this.data.push({ symbol: symbol, total: amount });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getChartData: function () {
|
||||||
|
let d = []
|
||||||
|
for (let i = 0; i < this.data.length; i++) {
|
||||||
|
//d.push({ label: this.data[i].symbol, data: this.data[i].total / Instruments.GetExchangeRate(this.data[i].symbol, 'GBP') });
|
||||||
|
d.push({ label: this.data[i].symbol, data: this.data[i].total });
|
||||||
|
}
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let holdingCurrenciesCurrentValue = {
|
||||||
|
data: [],
|
||||||
|
indexOfHoldingCurrency: function (symbol) { return this.data.map(function (item) { return item.symbol; }).indexOf(symbol); },
|
||||||
|
getHoldingCurrency: function (symbol) { return this.data[this.indexOfHoldingCurrency(symbol)]; },
|
||||||
|
addHoldingCurrency: function (symbol, amount) {
|
||||||
|
let c = this.getHoldingCurrency(symbol);
|
||||||
|
if (c) {
|
||||||
|
c.total += amount;
|
||||||
|
} else {
|
||||||
|
this.data.push({ symbol: symbol, total: amount });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getChartData: function () {
|
||||||
|
let d = []
|
||||||
|
for (let i = 0; i < this.data.length; i++) {
|
||||||
|
d.push({ label: this.data[i].symbol, data: this.data[i].total / Instruments.GetExchangeRate(this.data[i].symbol, 'GBP') });
|
||||||
|
}
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let bookCostPerAccount = createCategoryAggregator();
|
||||||
|
let currentValuePerAccount = createCategoryAggregator();
|
||||||
|
let totalHoldingsValueGBP = 0;
|
||||||
|
let totalBookCostGBP = 0;
|
||||||
|
let totalValueGBP = 0;
|
||||||
|
let totalGainGBP = 0;
|
||||||
|
let todaysGainGBP = 0;
|
||||||
|
let maxCurrentValueGBP = 0;
|
||||||
|
let groupHoldings = $('#groupBySymbol').prop('checked');
|
||||||
|
let r = [];
|
||||||
|
let w = []; //Watchlist (instruments with no current holdings)
|
||||||
|
|
||||||
|
for (let n = 0; n < Instruments.Data.length; n++) {
|
||||||
|
let i = Instruments.Data[n];
|
||||||
|
//if (i.InstrumentType == 'EQUITY') {
|
||||||
|
if (i.InstrumentType == 'EQUITY' || i.InstrumentType == 'ETF' || i.InstrumentType == 'CRYPTOCURRENCY' || i.InstrumentType == 'CURRENCY') {
|
||||||
|
//if (i.CurrentPrice) {
|
||||||
|
if (groupHoldings) {
|
||||||
|
let agg = aggregateHoldings(i);
|
||||||
|
if (agg.noUnits > 0) {
|
||||||
|
r.push(agg);
|
||||||
|
|
||||||
|
//holdingCurrencies.addHoldingCurrency(i.Currency, agg.noUnits * agg.purchasePricePerUnit);
|
||||||
|
//holdingCurrencies.addHoldingCurrency(i.Currency.toUpperCase(), agg.noUnits * agg.purchasePricePerUnit / (i.Currency == "GBp" ? 100 : 1));
|
||||||
|
holdingCurrencies.addHoldingCurrency(i.Currency.toUpperCase(), agg.bookCostGBP);
|
||||||
|
if (i.CurrentPrice) {
|
||||||
|
if (agg.currentValueGBP > maxCurrentValueGBP) { maxCurrentValueGBP = agg.currentValueGBP };
|
||||||
|
//holdingCurrenciesCurrentValue.addHoldingCurrency(i.Currency, agg.noUnits * i.CurrentPrice);
|
||||||
|
holdingCurrenciesCurrentValue.addHoldingCurrency(i.Currency.toUpperCase(), agg.noUnits * i.CurrentPrice / (i.Currency == "GBp" ? 100 : 1));
|
||||||
|
if (agg.currentExchangeRate) {
|
||||||
|
totalBookCostGBP += agg.bookCostGBP;
|
||||||
|
totalValueGBP += agg.currentValueGBP;
|
||||||
|
totalGainGBP += agg.gainGBP;
|
||||||
|
todaysGainGBP += (agg.marketIsOpen == 0 ? 0 : agg.singleDayGainGBP);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//No current holdings for this instrument - add it to the watchlist
|
||||||
|
if (i.WatchlistID) {
|
||||||
|
w.push(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let marketIsOpen = Instruments.MarketIsOpen(i);
|
||||||
|
let exchangeRate = Instruments.GetExchangeRate(i.Currency, 'GBP');
|
||||||
|
|
||||||
|
let noCurrentHoldings = 0;
|
||||||
|
for (let hn = 0; hn < i.Holdings.length; hn++) {
|
||||||
|
let h = i.Holdings[hn];
|
||||||
|
if (h.SoldDate == null) {
|
||||||
|
noCurrentHoldings++;
|
||||||
|
let holdingBookCost = h.NoUnits * h.PurchasePricePerUnit;
|
||||||
|
|
||||||
|
let currentHoldingValue = h.NoUnits * (i.Currency == 'GBp' ? i.CurrentPrice / 100 : i.CurrentPrice);
|
||||||
|
|
||||||
|
let holdingCurrentValueGBP = 0,
|
||||||
|
holdingGainGBP = 0,
|
||||||
|
singleDayGainGBP = 0,
|
||||||
|
singleDayGainPercent = 0,
|
||||||
|
singleDayProfitOrLoss = 0;
|
||||||
|
if (i.CurrentPrice) {
|
||||||
|
let currentHoldingValue = h.NoUnits * (i.Currency == 'GBp' ? i.CurrentPrice / 100 : i.CurrentPrice);
|
||||||
|
|
||||||
|
if (exchangeRate) {
|
||||||
|
let holdingCurrentValueGBP = (h.NoUnits * i.CurrentPrice) / exchangeRate;
|
||||||
|
//let holdingGainGBP = ((i.CurrentPrice - h.PurchasePricePerUnit) * h.NoUnits) / exchangeRate;
|
||||||
|
let holdingGainGBP = holdingCurrentValueGBP - h.BookCostGBP;
|
||||||
|
|
||||||
|
let singleDayGainGBP = ((i.CurrentPrice - (h.PurchaseDate > i.SingleDayStartDate ? h.PurchasePricePerUnit : i.SingleDayPreviousClose)) * h.NoUnits) / exchangeRate;
|
||||||
|
let singleDayGainPercent = ((i.CurrentPrice / i.SingleDayPreviousClose) - 1) * 100;
|
||||||
|
let singleDayProfitOrLoss = singleDayGainGBP < 0 ? 'loss' : 'profit';
|
||||||
|
|
||||||
|
if (holdingCurrentValueGBP > maxCurrentValueGBP) { maxCurrentValueGBP = holdingCurrentValueGBP };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r.push({
|
||||||
|
displayOrder: i.DisplayOrder,
|
||||||
|
holdingNumber: hn,
|
||||||
|
instrumentType: i.InstrumentType,
|
||||||
|
instrumentName: i.InstrumentName,
|
||||||
|
displayName: i.DisplayName,
|
||||||
|
symbol: i.Symbol,
|
||||||
|
currency: i.Currency,
|
||||||
|
accountName: h.AccountName,
|
||||||
|
purchaseDateMin: h.PurchaseDate,
|
||||||
|
purchaseDateMax: h.PurchaseDate,
|
||||||
|
noUnits: h.NoUnits,
|
||||||
|
purchasePricePerUnit: h.PurchasePricePerUnit,
|
||||||
|
bookCost: holdingBookCost,
|
||||||
|
currentValue: currentHoldingValue,
|
||||||
|
gain: (i.Currency == 'GBp' ? (i.CurrentPrice - h.PurchasePricePerUnit) / 100 : (i.CurrentPrice - h.PurchasePricePerUnit)) * h.NoUnits,
|
||||||
|
//gainPercent: (i.CurrentPrice - h.PurchasePricePerUnit) / h.PurchasePricePerUnit * 100,
|
||||||
|
gainPercent: holdingGainGBP / h.BookCostGBP * 100,
|
||||||
|
currentValueGBP: holdingCurrentValueGBP,
|
||||||
|
gainGBP: holdingGainGBP,
|
||||||
|
singleDayGainGBP: singleDayGainGBP,
|
||||||
|
singleDayGainPercent: singleDayGainPercent,
|
||||||
|
singleDayProfitOrLoss: singleDayProfitOrLoss,
|
||||||
|
currentPrice: i.CurrentPrice,
|
||||||
|
currentExchangeRate: exchangeRate,
|
||||||
|
marketIsOpen: marketIsOpen
|
||||||
|
});
|
||||||
|
|
||||||
|
holdingCurrencies.addHoldingCurrency(i.Currency, h.NoUnits * h.PurchasePricePerUnit);
|
||||||
|
if (i.CurrentPrice) {
|
||||||
|
holdingCurrenciesCurrentValue.addHoldingCurrency(i.Currency, h.NoUnits * i.CurrentPrice);
|
||||||
|
if (exchangeRate) {
|
||||||
|
currentValuePerAccount.addCategoryAmount(h.AccountName, holdingCurrentValueGBP);
|
||||||
|
bookCostPerAccount.addCategoryAmount(h.AccountName, holdingBookCost / exchangeRate);
|
||||||
|
|
||||||
|
//totalBookCostGBP += holdingBookCost / exchangeRate;
|
||||||
|
totalBookCostGBP += h.BookCostGBP;
|
||||||
|
totalValueGBP += holdingCurrentValueGBP;
|
||||||
|
totalGainGBP += holdingGainGBP;
|
||||||
|
todaysGainGBP += (marketIsOpen == 0 ? 0 : singleDayGainGBP);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (noCurrentHoldings == 0) {
|
||||||
|
//No current holdings for this instrument - add it to the watchlist
|
||||||
|
if (i.WatchlistID) {
|
||||||
|
w.push(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//} //if (i.CurrentPrice)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
totalBookCostGBP: totalBookCostGBP,
|
||||||
|
totalValueGBP: totalValueGBP,
|
||||||
|
totalGainGBP: totalGainGBP,
|
||||||
|
todaysGainGBP: todaysGainGBP,
|
||||||
|
maxCurrentValueGBP: maxCurrentValueGBP,
|
||||||
|
holdings: r,
|
||||||
|
watchlist: w,
|
||||||
|
holdingCurrencies: holdingCurrencies,
|
||||||
|
holdingCurrenciesCurrentValue: holdingCurrenciesCurrentValue,
|
||||||
|
bookCostPerAccount: bookCostPerAccount,
|
||||||
|
currentValuePerAccount: currentValuePerAccount
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function createTable() {
|
||||||
|
let t = $("#tblMainHoldings");
|
||||||
|
if (t.length) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
let tbl = '<table id="tblMainHoldings" class="mainHoldings"><thead>' +
|
||||||
|
'<tr><th>Type</th>' +
|
||||||
|
'<th>Name</th>' +
|
||||||
|
'<th>Symbol</th>' +
|
||||||
|
'<th>Ccy</th>' +
|
||||||
|
'<th>Account</th>' +
|
||||||
|
'<th>Buy Date</th>' +
|
||||||
|
'<th>No Units</th>' +
|
||||||
|
'<th>Buy<br>Price</th>' +
|
||||||
|
'<th>Current<br>Price</th>' +
|
||||||
|
'<th>Book<br>Cost</th>' +
|
||||||
|
'<th>Current<br>Value</th>' +
|
||||||
|
'<th>Gain</th>' +
|
||||||
|
'<th>Gain £</th>' +
|
||||||
|
'<th>Gain %</th>' +
|
||||||
|
'<th>Current<br>Value £</th>' +
|
||||||
|
'<th>Allocation</th>' +
|
||||||
|
'<th>Today's<br>Gain £</th>' +
|
||||||
|
'<th>Today's<br>Gain %</th>' +
|
||||||
|
'</tr></thead><tbody id="tbMainHoldings"></tbody><tfoot id="tfMainHoldings"><tr><td colspan="18"> </td></tr></tfoot></table>';
|
||||||
|
|
||||||
|
//$('#holdingsDiv').html(tbl);
|
||||||
|
$('#holdingsTableDiv').html(tbl);
|
||||||
|
|
||||||
|
$("#tblMainHoldings").tablesorter({ ignoreCase: false });
|
||||||
|
$("#tblMainHoldings").trigger("sorton", Cookies.get('sortHoldings'));
|
||||||
|
$('#tblMainHoldings').on('sortEnd', function (event) {
|
||||||
|
// Prints the current sort order to the console
|
||||||
|
if (event.target.config.sortList.length > 3) {
|
||||||
|
console.info({ "Setting sortHoldings Cookie (array too long?)": event.target.config.sortList });
|
||||||
|
}
|
||||||
|
Cookies.set('sortHoldings', event.target.config.sortList, { 'sameSite': 'strict' });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function createWatchlistTable() {
|
||||||
|
let t = $("#tblWatchlist");
|
||||||
|
if (t.length) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
let tbl = '<table id="tblWatchlist" class="watchlist"><thead>' +
|
||||||
|
'<tr><th>Type</th>' +
|
||||||
|
'<th>Name</th>' +
|
||||||
|
'<th>Symbol</th>' +
|
||||||
|
'<th>Ccy</th>' +
|
||||||
|
'<th>Current Price</th>' +
|
||||||
|
'<th>Today's Gain</th>' +
|
||||||
|
'<th>Today's Gain %</th>' +
|
||||||
|
'<th>Since Last Sold<th>' +
|
||||||
|
'</tr></thead><tbody id="tbWatchlist"></tbody></table>';
|
||||||
|
|
||||||
|
$('#watchlistTableDiv').html(tbl);
|
||||||
|
|
||||||
|
$("#tblWatchlist").tablesorter({ ignoreCase: false });
|
||||||
|
$("#tblWatchlist").trigger("sorton", Cookies.get('sortWatchlist'));
|
||||||
|
$('#tblWatchlist').on('sortEnd', function (event) {
|
||||||
|
// Prints the current sort order to the console
|
||||||
|
Cookies.set('sorWatchlist', event.target.config.sortList, { 'sameSite': 'strict' });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function addTableRow(rowID, content, tbodyID, highlightUpdates) {
|
||||||
|
let r = $("#" + rowID);
|
||||||
|
let newText = $(content).text(); //$(content).text();
|
||||||
|
if (r.length) {
|
||||||
|
let currentContent = r.text();
|
||||||
|
if (currentContent != newText) { //Only update row if the content has changed
|
||||||
|
$("#" + rowID).replaceWith(content);
|
||||||
|
if (highlightUpdates) {
|
||||||
|
$("#" + rowID).addClass("highlighted");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$("#" + tbodyID).append(content); //.addClass("highlighted");
|
||||||
|
//$("#" + rowID).addClass("highlighted");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function getPercentCell(backgroundColor, cellWidth, valuePercent, label) {
|
||||||
|
let labelText = label ? label : (valuePercent.toFixed(1) + '%');
|
||||||
|
let barWidth = ((valuePercent / 100) * cellWidth);
|
||||||
|
return '<td>' +
|
||||||
|
'<div class="pcBackground" style="background-color: ' + backgroundColor + '; width:' + barWidth.toFixed(0) + 'px;"> </div>' +
|
||||||
|
'<div class="pcLabel" style="width:' + cellWidth + 'px">' + labelText + '</div>' +
|
||||||
|
'</td>';
|
||||||
|
}
|
||||||
|
function profitOrLoss(value) {
|
||||||
|
return value >= 0 ? "profit" : "loss";
|
||||||
|
}
|
||||||
|
|
||||||
|
let t = getHoldings();
|
||||||
|
let totalBookCostGBP = t.totalBookCostGBP;
|
||||||
|
let totalValueGBP = t.totalValueGBP;
|
||||||
|
let totalGainGBP = t.totalGainGBP;
|
||||||
|
let todaysGainGBP = t.todaysGainGBP;
|
||||||
|
let maxCurrentValueGBP = t.maxCurrentValueGBP;
|
||||||
|
let holdings = t.holdings;
|
||||||
|
let watchlist = t.watchlist;
|
||||||
|
let holdingCurrencies = t.holdingCurrencies;
|
||||||
|
let holdingCurrenciesCurrentValue = t.holdingCurrenciesCurrentValue;
|
||||||
|
let bookCostPerAccount = t.bookCostPerAccount;
|
||||||
|
let currentValuePerAccount = t.currentValuePerAccount;
|
||||||
|
|
||||||
|
let maxAllocationPercent = (maxCurrentValueGBP / totalValueGBP) * 100;
|
||||||
|
let allocationScaleFactor = 100 / maxAllocationPercent;
|
||||||
|
|
||||||
|
let altRow = 1;
|
||||||
|
|
||||||
|
createTable();
|
||||||
|
for (let n = 0; n < holdings.length; n++) {
|
||||||
|
try {
|
||||||
|
altRow = !altRow;
|
||||||
|
|
||||||
|
let h = holdings[n];
|
||||||
|
let type = h.instrumentType == 'CURRENCY' ? '$' : h.instrumentType.substring(0, 1);
|
||||||
|
//let profitOrLoss = h.currentPrice < h.purchasePricePerUnit ? 'loss' : 'profit'; //let profitOrLoss = i.CurrentPrice < h.PurchasePricePerUnit ? 'loss' : 'profit';
|
||||||
|
let openOrClosed = h.marketIsOpen == 1 ? 'open' : 'closed';
|
||||||
|
let allocationPercent = h.currentValueGBP / totalValueGBP * 100;
|
||||||
|
let scaledAllocation = allocationScaleFactor * allocationPercent;
|
||||||
|
|
||||||
|
let rowID = "holdingRow_" + h.displayOrder + "_" + h.holdingNumber;
|
||||||
|
let row = '<tr' + (altRow ? ' class="altShareRow"' : '') + ' id="' + rowID + '">' +
|
||||||
|
'<td class="' + openOrClosed + '">' + type + '</td>' +
|
||||||
|
'<td class="' + openOrClosed + '">' + (h.displayName != '' ? h.displayName : h.instrumentName) + '</td>' +
|
||||||
|
'<td><a target="_blank" href="https://uk.finance.yahoo.com/quote/' + h.symbol + '">' + h.symbol + '</a>' + '</td>' +
|
||||||
|
'<td class="' + openOrClosed + '">' + h.currency + '</td>' +
|
||||||
|
'<td class="' + openOrClosed + '" title="' + (h.accountNames ? h.accountNames : '') + '">' + h.accountName + '</td>' +
|
||||||
|
'<td class="' + openOrClosed + '"' + (h.purchaseDateMin == h.purchaseDateMax ? ('>' + new Date(h.purchaseDateMin).yyyymmdd()) : (' title="' + new Date(h.purchaseDateMax).yyyymmdd() + ' - ' + new Date(h.purchaseDateMin).yyyymmdd() + '">' + new Date(h.purchaseDateMin).yyyymmdd()) + '+') + '</td>' +
|
||||||
|
//'<td class="num ' + openOrClosed + '">' + formatAmount(h.noUnits, '', 0) + '</td>' +
|
||||||
|
'<td class="num ' + openOrClosed + '">' + h.noUnits.autoScale() + '</td>' +
|
||||||
|
'<td class="num ' + openOrClosed + '">' + formatAmount(h.purchasePricePerUnit, h.currency, 2) + '</td>' + //Buy Price
|
||||||
|
'<td class="num ' + openOrClosed + '">' + (h.currentPrice ? formatAmount(h.currentPrice, h.currency, 2) : '') + '</td>' + //Current Price
|
||||||
|
'<td class="num ' + openOrClosed + '">' + formatAmount(h.currency == 'GBp' ? h.bookCost / 100 : h.bookCost, h.currency == 'GBp' ? 'GBP' : h.currency, 0) + '</td>' + //Book Cost
|
||||||
|
'<td class="num ' + openOrClosed + '">' + (h.currentExchangeRate ? formatAmount(h.currentValue, h.currency == 'GBp' ? 'GBP' : h.currency, 0) : '') + '</td>' + //Current Value
|
||||||
|
'<td class="num ' + profitOrLoss(h.currentPrice - h.purchasePricePerUnit) + '">' + (h.currentPrice ? formatAmount(h.gain, h.currency == 'GBp' ? 'GBP' : h.currency, 0) : '') + '</td>' + //Gain
|
||||||
|
'<td class="num ' + profitOrLoss(h.gainGBP) + '">' + (h.currentExchangeRate ? formatAmount(h.gainGBP, 'GBP', 0) : '') + '</td>' + //Gain £
|
||||||
|
'<td class="num ' + profitOrLoss(h.gainGBP) + '">' + (h.currentExchangeRate ? h.gainPercent.toFixed(1) + '%' : '') + '</td>' + //Gain %
|
||||||
|
'<td class="num ' + openOrClosed + '">' + (h.currentExchangeRate ? formatAmount(h.currentValueGBP, 'GBP', 0) : '') + '</td>' + //Current Value £
|
||||||
|
(h.currentExchangeRate ? getPercentCell('#3f2d95', 70, scaledAllocation, allocationPercent.toFixed(1) + '%') : '') + //Allocation
|
||||||
|
(h.currentExchangeRate ? (h.marketIsOpen == 0 ? '<td/>' : '<td class="num ' + h.singleDayProfitOrLoss + '">' + formatAmount(h.singleDayGainGBP, 'GBP', 0) + '</td>') : '') + //Today's Gain £
|
||||||
|
(h.singleDayGainPercent ? (h.marketIsOpen == 0 ? '<td/>' : '<td class="num ' + h.singleDayProfitOrLoss + '">' + h.singleDayGainPercent.toFixed(1) + '%</td>') : '<td/>') + //Today's Gain %
|
||||||
|
'</tr>';
|
||||||
|
addTableRow(rowID, row, "tbMainHoldings", true);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
logError("Error adding " + holdings[n].symbol + " to main holdings table: " + err.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Add the totals line at the bottom of the table
|
||||||
|
//let profitOrLoss = totalValueGBP < totalBookCostGBP ? 'loss' : 'profit';
|
||||||
|
let todaysProfitOrLoss = todaysGainGBP < 0 ? 'loss' : 'profit';
|
||||||
|
let lSF = '';
|
||||||
|
if (vars.fetchTiming.lastSuccessfulFetch) {
|
||||||
|
let lastFetchAge = ((new Date()) - vars.fetchTiming.lastSuccessfulFetch);
|
||||||
|
let staleFetch = ((new Date()) - vars.fetchTiming.lastSuccessfulFetch) > (vars.fetchTiming.fetchIntervalSingleDay * 1.5);
|
||||||
|
lSF = (vars.fetchTiming.lastSuccessfulFetch ? ' - Last Successful Fetch: ' + (staleFetch ? '<span class="loss">' : '') + vars.fetchTiming.lastSuccessfulFetch.yyyymmddhhmmss() + (staleFetch ? '</span>' : '') : '');
|
||||||
|
}
|
||||||
|
let row = '<tr id="tfHoldingsTotals"><td></td><td id="holdingsLastUpdated" colspan="8">Updated: ' + new Date().yyyymmddhhmmss() + lSF + '</td>' +
|
||||||
|
'<td class="num">' + formatAmount(totalBookCostGBP, 'GBP', 0) + '</td>' + //Book Cost
|
||||||
|
'<td></td><td></td>' +
|
||||||
|
'<td class="num ' + profitOrLoss(totalValueGBP - totalBookCostGBP) + '">' + formatAmount(totalGainGBP, 'GBP', 0) + '</td>' + //Gain GBP
|
||||||
|
'<td class="num ' + profitOrLoss(totalValueGBP - totalBookCostGBP) + '">' + formatAmount((((totalValueGBP / totalBookCostGBP) - 1) * 100), '', 1) + '%</td>' + //Gain %
|
||||||
|
'<td class="num">' + formatAmount(totalValueGBP, 'GBP', 0) + '</td>' + //Current Value
|
||||||
|
'<td> </td>' + //Allocation
|
||||||
|
'<td class="num ' + todaysProfitOrLoss + '">' + formatAmount(todaysGainGBP, 'GBP', 0) + '</td>' + //Today's Gain GBP
|
||||||
|
//'<td class="num ' + todaysProfitOrLoss + '">' + formatAmount(((todaysGainGBP / totalBookCostGBP) * 100), '', 1) + '%</td>' + //Today's Gain %
|
||||||
|
'<td class="num ' + todaysProfitOrLoss + '">' + formatAmount(((todaysGainGBP / (totalValueGBP - todaysGainGBP)) * 100), '', 1) + '%</td>' + //Today's Gain %
|
||||||
|
'</tr>';
|
||||||
|
addTableRow("tfHoldingsTotals", row, "tfMainHoldings", false);
|
||||||
|
|
||||||
|
//Create the watchlist table
|
||||||
|
createWatchlistTable();
|
||||||
|
altRow = 1;
|
||||||
|
for (let n = 0; n < watchlist.length; n++) {
|
||||||
|
altRow = !altRow;
|
||||||
|
let wi = watchlist[n];
|
||||||
|
try {
|
||||||
|
let type = wi.InstrumentType == 'CURRENCY' ? '$' : wi.InstrumentType.substring(0, 1);
|
||||||
|
let openOrClosed = Instruments.MarketIsOpen(wi) == 1 ? 'open' : 'closed';
|
||||||
|
let profitOrLoss = wi.CurrentPrice < wi.SingleDayPreviousClose ? 'loss' : 'profit';
|
||||||
|
let rowID = "watchlistRow_" + wi.DisplayOrder;
|
||||||
|
|
||||||
|
let sinceLastSold = '<td></td>';
|
||||||
|
if (wi.LastSoldPrice) {
|
||||||
|
let delta = (wi.CurrentPrice - wi.LastSoldPrice) / wi.LastSoldPrice * 100;
|
||||||
|
let tooltip = new Date(wi.LastSoldDate).yyyymmdd() + ' @ ' + wi.LastSoldPrice.toFixed(2);
|
||||||
|
sinceLastSold = '<td title="' + tooltip + '" class="num ' + (delta < 0 ? 'loss' : 'profit') + '">' + (delta).toFixed(1) + '%</td>';
|
||||||
|
}
|
||||||
|
|
||||||
|
//Type, Name, Symbol, Ccy, Current Price, Today's Gain, Today's Gain %
|
||||||
|
let row = '<tr' + (altRow ? ' class="altShareRow"' : '') + ' id="' + rowID + '">' +
|
||||||
|
'<td class="' + openOrClosed + '">' + type + '</td>' +
|
||||||
|
'<td class="' + openOrClosed + '">' + wi.InstrumentName + '</td>' +
|
||||||
|
'<td><a target="_blank" href="https://uk.finance.yahoo.com/quote/' + wi.Symbol + '">' + wi.Symbol + '</a>' + '</td>' +
|
||||||
|
'<td class="' + openOrClosed + '">' + wi.Currency + '</td>' +
|
||||||
|
'<td class="num ' + openOrClosed + '">' + (wi.CurrentPrice ? formatAmount(wi.CurrentPrice, wi.Currency, 2) : '') + '</td>' + //Current Price
|
||||||
|
(Instruments.marketIsOpen == 0 ? '<td/>' : '<td class="num ' + profitOrLoss + '">' + (wi.CurrentPrice ? formatAmount(wi.CurrentPrice - wi.SingleDayPreviousClose, wi.Currency, 2) : '') + '</td>') + //Today's Gain
|
||||||
|
(Instruments.marketIsOpen == 0 ? '<td/>' : '<td class="num ' + profitOrLoss + '">' + (wi.CurrentPrice ? ((wi.CurrentPrice - wi.SingleDayPreviousClose) / wi.SingleDayPreviousClose * 100).toFixed(1) : '') + '</td>') + //Today's Gain %
|
||||||
|
//'<td>' + (new Date(wi.LastSoldDate)).yyyymmdd() + '</td>' +
|
||||||
|
sinceLastSold +
|
||||||
|
'</tr>';
|
||||||
|
addTableRow(rowID, row, "tbWatchlist", true);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
logError("Error adding " + wi.symbol + " to watchlist table: " + err.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Flash the last updated cell
|
||||||
|
$("#holdingsLastUpdated").addClass("highlighted");
|
||||||
|
//Remove the highlight from all rows/cells
|
||||||
|
$("#tblMainHoldings").find(".highlighted").removeClass("highlighted", 1200);
|
||||||
|
$("#tblWatchlist").find(".highlighted").removeClass("highlighted", 1200);
|
||||||
|
|
||||||
|
//Tell tablesorter that the table has been updated
|
||||||
|
var resort = true;
|
||||||
|
$("#tblMainHoldings").trigger("update", [resort]);
|
||||||
|
|
||||||
|
//Update the daily holdings value table in the DB
|
||||||
|
if (totalValueGBP != vars.lastTotalHoldingsValue) {
|
||||||
|
$.ajax({
|
||||||
|
type: "POST",
|
||||||
|
contentType: "application/json",
|
||||||
|
url: "SharePrices.aspx/SetTotalHoldingsValue",
|
||||||
|
dataType: "json",
|
||||||
|
data: JSON.stringify({ TotalValue: totalValueGBP }),
|
||||||
|
success: function (response) {
|
||||||
|
vars.previousDay = response.PreviousDay;
|
||||||
|
vars.previousClose = response.PreviousClose;
|
||||||
|
vars.previousWeek = response.PreviousWeekDate;
|
||||||
|
vars.previousWeekClose = response.PreviousWeekClose;
|
||||||
|
|
||||||
|
//Update the total holdings span in the site banner
|
||||||
|
let formattedAmount = formatAmount(totalValueGBP, "GBP", 0);
|
||||||
|
let holdingsHTML = '<span style="font-size: small;">Total holdings: </span>' + '<span style="font-size: large">' + formattedAmount + '</span> ';
|
||||||
|
holdingsHTML += '<span style="font-size: small;"><span class="' + (vars.previousClose < totalValueGBP ? 'profit">+' : 'loss">') + formatAmount(totalValueGBP - vars.previousClose, "GBP", 0) + '</span> (d)';
|
||||||
|
holdingsHTML += ' / <span class="' + (vars.previousWeekClose < totalValueGBP ? 'profit">+' : 'loss">') + formatAmount(totalValueGBP - vars.previousWeekClose, "GBP", 0) + '</span> (w)</span>';
|
||||||
|
$("#spnTotalHoldings").html(holdingsHTML);
|
||||||
|
//Set the page title
|
||||||
|
document.title = formattedAmount + ' - ' + vars.initialPageTitle;
|
||||||
|
|
||||||
|
vars.lastTotalHoldingsValue = totalValueGBP;
|
||||||
|
},
|
||||||
|
failure: function (response) { console.error("SetTotalHoldingsValue error: " + JSON.stringify(response)); }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//Create / update the currency allocation pie chart
|
||||||
|
function labelFormatter(label, series) {
|
||||||
|
return "<div class='pieLabel'>" + label + "<br/>" + Math.round(series.percent) + "%<br>" + formatAmount(series.data[0][1], 'GBP', 0) + "</div>";
|
||||||
|
}
|
||||||
|
let ops = {
|
||||||
|
series: {
|
||||||
|
downsample: { threshold: 0 },
|
||||||
|
pie: { show: true, radius: 1, label: { show: true, radius: 0.7, formatter: labelFormatter } }
|
||||||
|
},
|
||||||
|
legend: { show: false },
|
||||||
|
grid: { hoverable: true },
|
||||||
|
tooltip: {
|
||||||
|
show: true,
|
||||||
|
content: "%p.0%, %s, n=%n", // show percentages, rounding to 2 decimal places
|
||||||
|
shifts: { x: 20, y: 0 },
|
||||||
|
defaultTheme: false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let cd = holdingCurrencies.getChartData();
|
||||||
|
if (cd.length) {
|
||||||
|
$.plot('#divHoldingCurrenciesChart', cd, ops);
|
||||||
|
}
|
||||||
|
|
||||||
|
cd = holdingCurrenciesCurrentValue.getChartData();
|
||||||
|
if (cd.length) {
|
||||||
|
$.plot('#divHoldingCurrenciesChartCurrentValue', cd, ops);
|
||||||
|
}
|
||||||
|
|
||||||
|
function labelFormatter2(label, series) {
|
||||||
|
return "<div class='pieLabel'>" + label + "<br/>" + formatAmount(series.data[0][1], 'GBP', 0) + "</div>";
|
||||||
|
}
|
||||||
|
ops.series.pie.label.formatter = labelFormatter2;
|
||||||
|
cd = bookCostPerAccount.getChartData();
|
||||||
|
if (cd.length) {
|
||||||
|
$.plot('#divAccountBookCost', cd, ops);
|
||||||
|
}
|
||||||
|
|
||||||
|
cd = currentValuePerAccount.getChartData();
|
||||||
|
if (cd.length) {
|
||||||
|
$.plot('#divAccountValue', cd, ops);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function createCategoryAggregator() {
|
||||||
|
let aggregator = {
|
||||||
|
data: [],
|
||||||
|
indexOfCategory: function (categoryName) { return this.data.map(function (item) { return item.categoryName; }).indexOf(categoryName); },
|
||||||
|
getCategory: function (categoryName) { return this.data[this.indexOfCategory(categoryName)]; },
|
||||||
|
addCategoryAmount: function (categoryName, amount) {
|
||||||
|
let c = this.getCategory(categoryName);
|
||||||
|
if (c) {
|
||||||
|
c.total += amount;
|
||||||
|
} else {
|
||||||
|
this.data.push({ categoryName: categoryName, total: amount });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getChartData: function () {
|
||||||
|
let d = []
|
||||||
|
for (let i = 0; i < this.data.length; i++) {
|
||||||
|
d.push({ label: this.data[i].categoryName, data: this.data[i].total });
|
||||||
|
}
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return aggregator;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user