diff --git a/Websites/SharePrices/.vs/SharePrices/v17/.suo b/Websites/SharePrices/.vs/SharePrices/v17/.suo index b6ea760..029dbbf 100644 Binary files a/Websites/SharePrices/.vs/SharePrices/v17/.suo and b/Websites/SharePrices/.vs/SharePrices/v17/.suo differ diff --git a/Websites/SharePrices/SharePrices.v12.suo b/Websites/SharePrices/SharePrices.v12.suo index cc492c6..6d2302e 100644 Binary files a/Websites/SharePrices/SharePrices.v12.suo and b/Websites/SharePrices/SharePrices.v12.suo differ diff --git a/Websites/SharePrices/SharePrices/CreateDBSchema.sql b/Websites/SharePrices/SharePrices/CreateDBSchema.sql index 0c9fa2d..7ebfc46 100644 --- a/Websites/SharePrices/SharePrices/CreateDBSchema.sql +++ b/Websites/SharePrices/SharePrices/CreateDBSchema.sql @@ -168,6 +168,65 @@ GO GRANT EXECUTE ON TYPE::PriceDataType TO WebApp_Role GO +CREATE FUNCTION dbo.fn_GetExchangeRate(@Currency VARCHAR(3), @DT DATETIME) +RETURNS REAL +AS +BEGIN + DECLARE @Rate REAL + IF @Currency = 'GBp' COLLATE Latin1_General_CS_AS + BEGIN + SET @Rate = 100 + END + ELSE + BEGIN + IF @Currency = 'GBP' COLLATE Latin1_General_CS_AS + BEGIN + SET @Rate = 1 + END + ELSE + BEGIN + DECLARE @InstrumentID INT + SELECT @InstrumentID = InstrumentID FROM Instrument WHERE FullName = 'GBP/' + @Currency + IF @InstrumentID IS NULL + BEGIN + --Currency pair Instrument not found - return zero to indicate error + SET @Rate = 0 + END + ELSE + BEGIN + --Is there a rate for this exact datetime? + SELECT @Rate = ClosePrice FROM InstrumentHistory_Intraday WHERE InstrumentID = @InstrumentID AND HistoryDT = @DT + IF @Rate IS NULL + BEGIN + --No exect match + DECLARE @StartDT DATETIME, @EndDT DATETIME, @StartRate REAL, @EndRate REAL + --@StartID INT, @EndID INT, + SELECT TOP 1 @StartDT = HistoryDT, @StartRate = ClosePrice FROM InstrumentHistory_Intraday WHERE InstrumentID = @InstrumentID AND HistoryDT < @DT ORDER BY HistoryDT DESC + SELECT TOP 1 @EndDT = HistoryDT, @EndRate = OpenPrice FROM InstrumentHistory_Intraday WHERE InstrumentID = @InstrumentID AND HistoryDT > @DT ORDER BY HistoryDT + IF @StartDT IS NOT NULL AND @EndDT IS NOT NULL + BEGIN + --Work out how far between the 2 dates our DT is, and calculate rate accordingly + DECLARE @TotalSeconds BIGINT + DECLARE @Percent REAL + SELECT @TotalSeconds = DATEDIFF(second, @StartDT, @EndDT) + SET @Percent = CONVERT(REAL, DATEDIFF(second, @StartDT, @DT)) / @TotalSeconds * 100 + SET @Rate = @StartRate + ((@EndRate - @StartRate) / 100 * @Percent) + END + ELSE + BEGIN + --We don't have BOTH a start AND end price + --Return the non-NULL rate if we have one, zero otherwise + SET @Rate = COALESCE(@StartRate, @EndRate, 0) + END + END + END + END + END + + RETURN @Rate +END +GO + --CREATE PROCEDURE usp_InsertInstrument (@ExchangeShortName varchar(10), @Symbol varchar(7), @FullName varchar(100)) CREATE PROCEDURE usp_InsertInstrument (@Symbol varchar(8), @FullName varchar(100)) AS @@ -629,7 +688,8 @@ BEGIN h.PurchaseDate, h.NoUnits, h.PurchasePricePerUnit, - h.ActualExchangeRate, + ISNULL(h.ActualExchangeRate, dbo.fn_GetExchangeRate(i.Currency, h.PurchaseDate)) as [PurchaseExchangeRate], + h.NoUnits * h.PurchasePricePerUnit / ISNULL(h.ActualExchangeRate, dbo.fn_GetExchangeRate(i.Currency, h.PurchaseDate)) as [BookCostGBP], h.SoldDate FROM Holding h diff --git a/Websites/SharePrices/SharePrices/scripts/SharePrices.js b/Websites/SharePrices/SharePrices/scripts/SharePrices.js index a1bad47..a4ab09a 100644 --- a/Websites/SharePrices/SharePrices/scripts/SharePrices.js +++ b/Websites/SharePrices/SharePrices/scripts/SharePrices.js @@ -1,4 +1,5 @@ -'use strict'; +// @ts-check +'use strict'; 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 fetchIntervalIntraday = 4 * timespans.oneHour; @@ -507,6 +508,7 @@ function createSharesTableRow(instrument) { if (Instrument.Holdings.length > 0) { let totalUnits = 0; let totalPrice = 0; + let totalPriceGBP = 0; let noRows = 0; let t = ''; for (let i = 0; i < Instrument.Holdings.length; i++) { @@ -515,6 +517,7 @@ function createSharesTableRow(instrument) { noRows++; totalUnits += h.NoUnits; totalPrice += (h.NoUnits * h.PurchasePricePerUnit); + totalPriceGBP += h.BookCostGBP; t += '' + '' + '' + @@ -537,12 +540,15 @@ function createSharesTableRow(instrument) { if (totalUnits > 0) { Instrument.TotalUnitsHeld = totalUnits; Instrument.TotalHoldingsCost = totalPrice; + Instrument.TotalHoldingsCostGBP = totalPriceGBP; + //Instrument.BreakevenPrice = totalPrice / totalUnits; Instrument.BreakevenPrice = totalPrice / totalUnits; t += '' + '' + '' + //'' + '' + + //'' + '' + ''; } @@ -551,6 +557,7 @@ function createSharesTableRow(instrument) { } else { delete Instrument.TotalUnitsHeld; delete Instrument.TotalHoldingsCost; + delete Instrument.TotalHoldingsCostGBP; delete Instrument.BreakevenPrice; return ''; } @@ -579,6 +586,42 @@ function createSharesTableRow(instrument) { //'' }; +function getCurrentValueContent(Instrument) { + let currentValue = ''; + if (Instrument.CurrentPrice) { + if (Instrument.TotalUnitsHeld > 0) { + currentValue = '
'; + //currentValue += 'Price (' + Instrument.Currency + '): ' + (Instrument.BreakevenPrice < 0.1 ? Instrument.BreakevenPrice.toPrecision(3) : Instrument.BreakevenPrice.toFixed(2)) + ' -> ' + (Instrument.CurrentPrice < 0.1 ? Instrument.CurrentPrice.toPrecision(3) : Instrument.CurrentPrice.toFixed(2)) + ': ' + getProfitOrLoss(Instrument.BreakevenPrice, Instrument.CurrentPrice) + '
'; + currentValue += 'Price (' + Instrument.Currency + '): ' + (Instrument.BreakevenPrice < 0.1 ? Instrument.BreakevenPrice.autoScale() : Instrument.BreakevenPrice.toFixed(2)) + ' -> ' + (Instrument.CurrentPrice < 0.1 ? Instrument.CurrentPrice.autoScale() : Instrument.CurrentPrice.toFixed(2)) + ': ' + getProfitOrLoss(Instrument.BreakevenPrice, Instrument.CurrentPrice) + '
'; + //currentValue += 'Value: ' + (Instrument.TotalHoldingsCost).toFixed(2) + ' -> ' + (Instrument.TotalUnitsHeld * Instrument.CurrentPrice).toFixed(2) + ': ' + getProfitOrLoss(Instrument.TotalHoldingsCost, Instrument.TotalUnitsHeld * Instrument.CurrentPrice); + currentValue += 'Book Cost (GBP): ' + (Instrument.TotalHoldingsCostGBP).toFixed(2); + let exRate = Instruments.GetExchangeRate(Instrument.Currency, 'GBP'); + if (exRate) { + if (Instrument.Currency != 'GBp') { + currentValue += '
GBP/' + Instrument.Currency + ' = ' + exRate.toFixed(4); + } + currentValue += '
'; + //currentValue += 'Current value:
' + currencyFormatter.format(Instrument.TotalUnitsHeld * Instrument.CurrentPrice / exRate) + ': ' + getProfitOrLoss(Instrument.TotalHoldingsCostGBP / exRate, Instrument.TotalUnitsHeld * Instrument.CurrentPrice / exRate, 1); + currentValue += 'Current value:
' + currencyFormatter.format(Instrument.TotalUnitsHeld * Instrument.CurrentPrice / exRate) + ': ' + getProfitOrLoss(Instrument.TotalHoldingsCostGBP, Instrument.TotalUnitsHeld * Instrument.CurrentPrice / exRate, 1); + currentValue += '
'; + } else { + currentValue += '
' + Instrument.Currency; + } + } else { + //INDEX, CURRENCY, EQUITY + if (Instrument.InstrumentType == 'CURRENCY') { + currentValue = 'Exchange Rate: ' + Instrument.CurrentPrice.toFixed(4); + } else { + if (Instrument.InstrumentType == 'CRYPTOCURRENCY' && Math.abs(Instrument.CurrentPrice) < 0.001) { + currentValue = 'Price (' + Instrument.Currency + '): ' + Instrument.CurrentPrice.toExponential(); + } else { + currentValue = 'Price (' + Instrument.Currency + '): ' + Instrument.CurrentPrice.toFixed(2); + } + } + } + } + return currentValue; +} function moveInstrumentUp(symbol) { let i = Instruments.indexOfSymbol(symbol); if (i > 0) { @@ -2341,40 +2384,6 @@ function getYahooIntradayData(Instrument) { failure: function (response) { console.error("getYahooIntradayData error:"); console.info(response); } }); } -function getCurrentValueContent(Instrument) { - let currentValue = ''; - if (Instrument.CurrentPrice) { - if (Instrument.TotalUnitsHeld > 0) { - currentValue = '
'; - //currentValue += 'Price (' + Instrument.Currency + '): ' + (Instrument.BreakevenPrice < 0.1 ? Instrument.BreakevenPrice.toPrecision(3) : Instrument.BreakevenPrice.toFixed(2)) + ' -> ' + (Instrument.CurrentPrice < 0.1 ? Instrument.CurrentPrice.toPrecision(3) : Instrument.CurrentPrice.toFixed(2)) + ': ' + getProfitOrLoss(Instrument.BreakevenPrice, Instrument.CurrentPrice) + '
'; - currentValue += 'Price (' + Instrument.Currency + '): ' + (Instrument.BreakevenPrice < 0.1 ? Instrument.BreakevenPrice.autoScale() : Instrument.BreakevenPrice.toFixed(2)) + ' -> ' + (Instrument.CurrentPrice < 0.1 ? Instrument.CurrentPrice.autoScale() : Instrument.CurrentPrice.toFixed(2)) + ': ' + getProfitOrLoss(Instrument.BreakevenPrice, Instrument.CurrentPrice) + '
'; - currentValue += 'Value: ' + (Instrument.TotalHoldingsCost).toFixed(2) + ' -> ' + (Instrument.TotalUnitsHeld * Instrument.CurrentPrice).toFixed(2) + ': ' + getProfitOrLoss(Instrument.TotalHoldingsCost, Instrument.TotalUnitsHeld * Instrument.CurrentPrice); - let exRate = Instruments.GetExchangeRate(Instrument.Currency, 'GBP'); - if (exRate) { - if (Instrument.Currency != 'GBp') { - currentValue += '
GBP/' + Instrument.Currency + ' = ' + exRate.toFixed(4); - } - currentValue += '
'; - currentValue += 'Current value:
' + currencyFormatter.format(Instrument.TotalUnitsHeld * Instrument.CurrentPrice / exRate) + ': ' + getProfitOrLoss(Instrument.TotalHoldingsCost / exRate, Instrument.TotalUnitsHeld * Instrument.CurrentPrice / exRate, 1); - currentValue += '
'; - } else { - currentValue += '
' + Instrument.Currency; - } - } else { - //INDEX, CURRENCY, EQUITY - if (Instrument.InstrumentType == 'CURRENCY') { - currentValue = 'Exchange Rate: ' + Instrument.CurrentPrice.toFixed(4); - } else { - if (Instrument.InstrumentType == 'CRYPTOCURRENCY' && Math.abs(Instrument.CurrentPrice) < 0.001) { - currentValue = 'Price (' + Instrument.Currency + '): ' + Instrument.CurrentPrice.toExponential(); - } else { - currentValue = 'Price (' + Instrument.Currency + '): ' + Instrument.CurrentPrice.toFixed(2); - } - } - } - } - return currentValue; -} function getYahooSingleDayData(Instrument) { //console.info('getYahooSingleDayData: ' + Instrument.Symbol); Instrument.lastFetchedSingleDay = new Date().getTime(); @@ -2966,8 +2975,10 @@ function createHoldingsTable() { if (h.SoldDate == null) { aggUnits += h.NoUnits; aggBookCost += h.NoUnits * h.PurchasePricePerUnit; - aggBookCostGBP += h.NoUnits * h.PurchasePricePerUnit / exchangeRate; - bookCostPerAccount.addCategoryAmount(h.AccountName, (h.NoUnits * h.PurchasePricePerUnit) / exchangeRate); + //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; } @@ -2990,7 +3001,8 @@ function createHoldingsTable() { aggCurrentValue = aggUnits * (i.Currency == 'GBp' ? i.CurrentPrice / 100 : i.CurrentPrice); if (exchangeRate) { aggCurrentValueGBP = (aggUnits * i.CurrentPrice) / exchangeRate; - aggGainGBP = ((i.CurrentPrice - aggPurchasePricePerUnit) * aggUnits) / exchangeRate; + //aggGainGBP = ((i.CurrentPrice - aggPurchasePricePerUnit) * aggUnits) / exchangeRate; + aggGainGBP = aggCurrentValueGBP - aggBookCostGBP; aggSingleDayGainPercent = ((i.CurrentPrice / i.SingleDayPreviousClose) - 1) * 100; aggSingleDayProfitOrLoss = aggSingleDayGainGBP < 0 ? 'loss' : 'profit'; } @@ -3013,7 +3025,8 @@ function createHoldingsTable() { bookCostGBP: aggBookCostGBP, currentValue: aggCurrentValue, gain: (i.Currency == 'GBp' ? (i.CurrentPrice - aggPurchasePricePerUnit) / 100 : (i.CurrentPrice - aggPurchasePricePerUnit)) * aggUnits, - gainPercent: (i.CurrentPrice - aggPurchasePricePerUnit) / aggPurchasePricePerUnit * 100, + //gainPercent: (i.CurrentPrice - aggPurchasePricePerUnit) / aggPurchasePricePerUnit * 100, + gainPercent: aggGainGBP / aggBookCostGBP * 100, currentValueGBP: aggCurrentValueGBP, gainGBP: aggGainGBP, singleDayGainGBP: aggSingleDayGainGBP, @@ -3087,10 +3100,12 @@ function createHoldingsTable() { if (agg.noUnits > 0) { r.push(agg); - holdingCurrencies.addHoldingCurrency(i.Currency, agg.noUnits * agg.purchasePricePerUnit); + //holdingCurrencies.addHoldingCurrency(i.Currency, agg.noUnits * agg.purchasePricePerUnit); + holdingCurrencies.addHoldingCurrency(i.Currency.toUpperCase(), agg.noUnits * agg.purchasePricePerUnit / (i.Currency == "GBp" ? 100 : 1)); if (i.CurrentPrice) { if (agg.currentValueGBP > maxCurrentValueGBP) { maxCurrentValueGBP = agg.currentValueGBP }; - holdingCurrenciesCurrentValue.addHoldingCurrency(i.Currency, agg.noUnits * i.CurrentPrice); + //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; @@ -3107,7 +3122,7 @@ function createHoldingsTable() { let h = i.Holdings[hn]; if (h.SoldDate == null) { let holdingBookCost = h.NoUnits * h.PurchasePricePerUnit; - + let currentHoldingValue = h.NoUnits * (i.Currency == 'GBp' ? i.CurrentPrice / 100 : i.CurrentPrice); let holdingCurrentValueGBP = 0, @@ -3120,7 +3135,8 @@ function createHoldingsTable() { if (exchangeRate) { let holdingCurrentValueGBP = (h.NoUnits * i.CurrentPrice) / exchangeRate; - let holdingGainGBP = ((i.CurrentPrice - h.PurchasePricePerUnit) * h.NoUnits) / 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; @@ -3145,7 +3161,8 @@ function createHoldingsTable() { 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: (i.CurrentPrice - h.PurchasePricePerUnit) / h.PurchasePricePerUnit * 100, + gainPercent: holdingGainGBP / h.BookCostGBP * 100, currentValueGBP: holdingCurrentValueGBP, gainGBP: holdingGainGBP, singleDayGainGBP: singleDayGainGBP, @@ -3163,7 +3180,8 @@ function createHoldingsTable() { currentValuePerAccount.addCategoryAmount(h.AccountName, holdingCurrentValueGBP); bookCostPerAccount.addCategoryAmount(h.AccountName, holdingBookCost / exchangeRate); - totalBookCostGBP += holdingBookCost / exchangeRate; + //totalBookCostGBP += holdingBookCost / exchangeRate; + totalBookCostGBP += holdingBookCostGBP; totalValueGBP += holdingCurrentValueGBP; totalGainGBP += holdingGainGBP; todaysGainGBP += (marketIsOpen == 0 ? 0 : singleDayGainGBP); @@ -3401,19 +3419,21 @@ function createAccountsTables() { noUnits: holding.NoUnits, cost: holding.NoUnits * holding.PurchasePricePerUnit } - if (exchangeRate) { - h.costGBP = h.cost / exchangeRate; - a.totalCostGBP += h.costGBP; - } + //if (exchangeRate) { + //h.costGBP = h.cost / exchangeRate; + h.costGBP = holding.BookCostGBP; + a.totalCostGBP += holding.BookCostGBP; + //} if (instrument.CurrentPrice) { h.currentPrice = instrument.CurrentPrice; h.currentValue = holding.NoUnits * instrument.CurrentPrice; h.gain = h.currentValue - h.cost; if (exchangeRate) { h.currentValueGBP = h.currentValue / exchangeRate; - h.gainGBP = h.gain / exchangeRate; - a.totalValueGBP += h.currentValueGBP; + //h.gainGBP = h.gain / exchangeRate; + h.gainGBP = h.currentValueGBP - h.costGBP; a.totalGainGBP += h.gainGBP; + a.totalValueGBP += h.currentValueGBP; } } a.holdings.push(h); @@ -3487,6 +3507,7 @@ function createAccountsTables() { let h = a.holdings[hx]; accountAltRow = !accountAltRow; let holdingProfitOrLoss = h.gain < 0 ? 'loss' : 'profit'; + let holdingGBPProfitOrLoss = h.gainGBP < 0 ? 'loss' : 'profit'; accountsTables += '' + '' + '' + @@ -3499,8 +3520,9 @@ function createAccountsTables() { '' + '' + '' + - '' + - '' + + '' + + //'' + + '' + ''; } accountsTables += '
AccountBuy DatePriceUnitsTotal
×' + h.AccountName + '' + new Date(h.PurchaseDate).yyyymmdd() + '' + formatAmount(h.PurchasePricePerUnit, '', 2) + '
Total' + formatAmount(Instrument.BreakevenPrice, '', 2) + '' + formatAmount(Instrument.TotalUnitsHeld, '', 0) + '' + Instrument.TotalUnitsHeld.autoScale() + '' + formatAmount(Instrument.TotalHoldingsCost, '', 0) + '' + formatAmount(Instrument.TotalHoldingsCost, '', 0) + '
' + h.instrumentName + '' + h.symbol + '' + (h.currency == 'GBp' ? formatAmount(h.currentValue / 100, 'GBP', 2) : formatAmount(h.currentValue, h.currency, 2)) + '' + formatAmount(h.currentValueGBP, 'GBP', 2) + '' + formatAmount(h.gain, h.currency, 2) + '' + formatAmount(h.gainGBP, 'GBP', 2) + '' + ((h.gain / h.cost) * 100).toFixed(1) + '%' + formatAmount(h.gainGBP, 'GBP', 2) + '' + ((h.gain / h.cost) * 100).toFixed(1) + '%' + ((h.gainGBP / h.costGBP) * 100).toFixed(1) + '%
';