2025-01-05

This commit is contained in:
Steve 2025-01-05 18:37:26 +00:00
parent 60f570c947
commit 566237e63f
22 changed files with 923 additions and 660 deletions

View File

@ -1,6 +1,6 @@
{
"Version": 1,
"WorkspaceRootPath": "C:\\Users\\Steve.OZDOMAIN\\source\\repos\\Steves_Code\\Websites\\SharePrices\\",
"WorkspaceRootPath": "\\\\OZHOST1\\d$\\Documents\\Visual Studio Projects\\SharePrices\\",
"Documents": [],
"DocumentGroupContainers": [
{

View File

@ -47,13 +47,15 @@ Public Class DataAccessLayer
DisposeSQLCommand(c)
End Sub
Public Shared Function AddHolding(AccountName As String, Symbol As String, NoUnits As Double, PurchasePricePerUnit As Double, PurchaseDate As Int64) As String
Public Shared Function AddHolding(AccountName As String, Symbol As String, NoUnits As Double, PurchasePricePerUnit As Double, PurchaseDate As Int64, PurchaseCurrency As String, BookCostInPurchaseCurrency As Double) As String
Dim c As SqlCommand = GetSQLCommand("usp_InsertHolding")
c.Parameters.AddWithValue("Account", AccountName)
c.Parameters.AddWithValue("Symbol", Symbol)
c.Parameters.AddWithValue("NoUnits", NoUnits)
c.Parameters.AddWithValue("PurchasePricePerUnit", PurchasePricePerUnit)
c.Parameters.AddWithValue("PurchaseDate", FromEpochTime(PurchaseDate))
c.Parameters.AddWithValue("PurchaseCurrency", PurchaseCurrency)
c.Parameters.AddWithValue("BookCostInPurchaseCurrency", BookCostInPurchaseCurrency)
AddHolding = DataReaderToJSONString(c.ExecuteReader())
DisposeSQLCommand(c)
End Function
@ -221,7 +223,8 @@ Public Class DataAccessLayer
End Function
Private Shared Function GetSQLConnection() As SqlConnection
Dim c As New SqlConnection("Server=OZHOST1\SQL2008;Database=SharePrices;Trusted_Connection=True;Application Name=Share Prices Web;")
'Dim c As New SqlConnection("Server=OZHOST1\SQL2008;Database=SharePrices;Trusted_Connection=True;Application Name=Share Prices Web;")
Dim c As New SqlConnection("Server=WinSrv1\INSTANCE1;Database=SharePrices;Trusted_Connection=True;Application Name=Share Prices Web;")
c.Open()
Return c
End Function

View File

@ -2,9 +2,11 @@ USE SharePrices
GO
/*
DROP VIEW vHoldingValueHistory
DROP VIEW vErroneousPrices_Intraday
DROP VIEW vErroneousPrices_Daily
DROP VIEW vHolding
DROP VIEW vCashBalances
DROP FUNCTION dbo.fn_GetExchangeRate
DROP FUNCTION dbo.fn_GetPriceAtDate
DROP PROCEDURE usp_SwapDisplayOrder
@ -24,10 +26,12 @@ DROP PROCEDURE usp_GetInstruments
DROP TYPE PriceDataType
DROP TABLE Holding
DROP TABLE Account
DROP TABLE AccountHolder
DROP TABLE InstrumentHistory_Daily
DROP TABLE InstrumentHistory_Intraday
DROP TABLE Instrument
DROP TABLE TotalHoldingsHistory_Daily
DROP TABLE AllDays
--DROP TABLE Exchange
*/
GO
@ -56,6 +60,24 @@ INSERT Exchange (ShortName, FullName)
GO
*/
CREATE TABLE AllDays (
DT date NOT NULL,
CONSTRAINT PK_AllDays PRIMARY KEY CLUSTERED (DT)
)
GO
CREATE TABLE PriceSource (
PriceSourceID tinyint IDENTITY NOT NULL,
PriceSourceName varchar(30) NOT NULL,
CONSTRAINT PK_PriceSource PRIMARY KEY CLUSTERED (PriceSourceID),
CONSTRAINT UC_PriceSource UNIQUE NONCLUSTERED (PriceSourceName)
)
GO
INSERT PriceSource (PriceSourceName) VALUES
('Yahoo Finance UK'),
('London Stock Exchange')
GO
CREATE TABLE Instrument (
InstrumentID smallint IDENTITY NOT NULL,
--ExchangeID tinyint NOT NULL,
@ -66,6 +88,8 @@ CREATE TABLE Instrument (
PostPandemicDilution money NOT NULL,
GMTOffset INT NULL,
Currency VARCHAR(3) NULL,
TrackPriceHistory BIT NOT NULL,
PriceSourceID tinyint NOT NULL,
--CurrentPrice money NULL,
CurrentPrice real NULL,
InstrumentType VARCHAR(15) NULL,
@ -76,9 +100,11 @@ CREATE TABLE Instrument (
LastUpdated datetime NULL,
CONSTRAINT PK_Instrument PRIMARY KEY CLUSTERED (InstrumentID),
CONSTRAINT UC_Instrument_Symbol UNIQUE NONCLUSTERED (Symbol),
CONSTRAINT FK_Instrument_PriceSource FOREIGN KEY (PriceSourceID) REFERENCES PriceSource (PriceSourceID),
--CONSTRAINT FK_Instrument_Exchange FOREIGN KEY (ExchangeID) REFERENCES Exchange (ExchangeID)
)
GO
--CREATE NONCLUSTERED INDEX IDX_Instrument_ExchangeID_Symbol ON Instrument (ExchangeID, Symbol)
--CREATE NONCLUSTERED INDEX IDX_Instrument_Symbol ON Instrument (Symbol)
CREATE NONCLUSTERED INDEX IDX_Instrument_DisplayOrder ON Instrument (DisplayOrder)
@ -118,11 +144,24 @@ CREATE TABLE InstrumentHistory_Intraday (
)
GO
CREATE TABLE AccountHolder (
AccountHolderID tinyint IDENTITY NOT NULL,
Name varchar(10) NOT NULL,
CONSTRAINT PK_AccountHolder PRIMARY KEY CLUSTERED (AccountHolderID),
CONSTRAINT UC_AccountHolder UNIQUE NONCLUSTERED (Name)
)
GO
INSERT AccountHolder (Name) VALUES ('Steve'), ('Steph')
GO
CREATE TABLE Account (
AccountID tinyint IDENTITY NOT NULL,
AccountHolderID tinyint NOT NULL,
ShortName varchar(20) NOT NULL,
IsTaxable bit NOT NULL,
CONSTRAINT PK_Account PRIMARY KEY CLUSTERED (AccountID),
CONSTRAINT UC_Account_ShortName UNIQUE NONCLUSTERED (ShortName)
CONSTRAINT UC_Account_ShortName UNIQUE NONCLUSTERED (ShortName),
CONSTRAINT FK_Account_AccountHolder FOREIGN KEY (AccountHolderID) REFERENCES AccountHolder (AccountHolderID)
)
GO
INSERT Account (ShortName) VALUES
@ -151,6 +190,8 @@ CREATE TABLE Holding (
--ActualExchangeRate real NULL,
ActualExchangeRate numeric(36, 12) NULL,
PurchaseDate datetime NOT NULL,
PurchaseCurrencyID smallint NULL,
BookCostInPurchaseCurrency numeric(36, 12) NULL,
SoldDate datetime NULL,
SoldCurrencyID smallint NULL,
SoldProceeds numeric(36, 12) NULL,
@ -161,6 +202,128 @@ CREATE TABLE Holding (
)
GO
CREATE TABLE TradeActionType (
ActionType VARCHAR(10) NOT NULL,
CONSTRAINT PK_TradeActionType PRIMARY KEY CLUSTERED (ActionType)
)
GO
INSERT TradeActionType VALUES ('Buy'), ('Sell'), ('Transfer'), ('Split')
GO
CREATE TABLE TradeLedger (
TradeLedgerID INT IDENTITY NOT NULL,
InstrumentID SMALLINT NOT NULL,
AccountID TINYINT NOT NULL,
TradeDT datetime NOT NULL,
ActionType VARCHAR(10) NOT NULL,
NoUnits NUMERIC(36, 12) NOT NULL,
TradeCurrencyID SMALLINT NOT NULL,
ActualExchangeRate NUMERIC(36, 12) NULL,
EstimatedExchangeRate NUMERIC(36, 12) NULL,
TradeValue MONEY NOT NULL,
TradeValueGBP MONEY NOT NULL,
--NewPoolNoUnits NUMERIC(36, 12) NOT NULL,
--NewPoolCostBaseGBP MONEY NOT NULL,
Notes VARCHAR(1000) NULL,
CONSTRAINT PK_TradeLedger PRIMARY KEY CLUSTERED (TradeLedgerID),
CONSTRAINT FK_TradeLedger_Instrument FOREIGN KEY (InstrumentID) REFERENCES Instrument (InstrumentID),
CONSTRAINT FK_TradeLedger_TradeActionType FOREIGN KEY (ActionType) REFERENCES TradeActionType (ActionType),
CONSTRAINT FK_TradeLedger_TradeCurrency FOREIGN KEY (TradeCurrencyID) REFERENCES Instrument (InstrumentID),
CONSTRAINT UC_TradeLedger UNIQUE NONCLUSTERED (InstrumentID, AccountID, TradeDT)
)
GO
/*
UPDATE ShareTrades_Steph_XLS SET Account = CASE Account WHEN 'AJ Bell' THEN 'Steph AJB'
WHEN 'AJ Bell - ISA' THEN 'Steph AJB ISA'
WHEN 'Hargreaves Lansdown' THEN 'Steph HL Trd'
WHEN 'Interactive Investor - ISA' THEN 'Steph ii ISA'
WHEN 'Interactive Investor - Trading Account' THEN 'Steph ii Trd'
ELSE Account END
UPDATE ShareTrades_Steve_XLS SET Account = CASE Account WHEN 'AJ Bell' THEN 'Steve AJB'
WHEN 'AJ Bell - ISA' THEN 'Steve AJB ISA'
WHEN 'Hargreaves Lansdown - Trading Account' THEN 'Steve HL Trd'
WHEN 'Hargreaves Lansdown - ISA' THEN 'Steve HL ISA'
WHEN 'Interactive Investor - ISA' THEN 'Steve ii ISA'
WHEN 'Interactive Investor - Trading Account' THEN 'Steve ii Trd'
ELSE Account END
INSERT TradeLedger (
[InstrumentID],
[AccountID],
[TradeDT],
[ActionType],
[NoUnits],
[TradeCurrencyID],
[ActualExchangeRate],
[EstimatedExchangeRate],
[TradeValue],
[TradeValueGBP],
[Notes]
)
SELECT
InstrumentID,
a.AccountID,
[Transaction Date (UK time)],
[Buy or Sell],
[No Units],
(SELECT InstrumentID FROM Instrument WHERE Currency = [Settlement Currency] AND InstrumentType = 'CURRENCY'),
CASE WHEN [Settlement Currency] = 'GBP' THEN 1 ELSE NULL END,
CASE WHEN [Settlement Currency] = 'GBP' THEN NULL ELSE dbo.fn_GetExchangeRate([Settlement Currency], [Transaction Date (UK time)]) END,
[Transaction Total],
ROUND([Transaction Total] / CASE WHEN [Settlement Currency] = 'GBP' THEN 1 ELSE dbo.fn_GetExchangeRate([Settlement Currency], [Transaction Date (UK time)]) END, 2),
Notes + ISNULL(' - [' + F9 + ']', '')
FROM
ShareTrades_Steph_XLS x
LEFT OUTER JOIN Account a
ON a.ShortName = x.Account
WHERE
[Buy or Sell] IN ('Buy', 'Sell')
ORDER BY [Transaction Date (UK time)]
INSERT TradeLedger (
[InstrumentID],
[AccountID],
[TradeDT],
[ActionType],
[NoUnits],
[TradeCurrencyID],
[ActualExchangeRate],
[EstimatedExchangeRate],
[TradeValue],
[TradeValueGBP],
[Notes]
)
SELECT
InstrumentID,
a.AccountID,
[Transaction Date (UK time)],
[Buy or Sell],
[No Units],
(SELECT InstrumentID FROM Instrument WHERE Currency = [Settlement Currency] AND InstrumentType = 'CURRENCY'),
CASE WHEN [Settlement Currency] = 'GBP' THEN 1 ELSE NULL END,
CASE WHEN [Settlement Currency] = 'GBP' THEN NULL ELSE dbo.fn_GetExchangeRate([Settlement Currency], [Transaction Date (UK time)]) END,
[Transaction Total],
ROUND([Transaction Total] / CASE WHEN [Settlement Currency] = 'GBP' THEN 1 ELSE dbo.fn_GetExchangeRate([Settlement Currency], [Transaction Date (UK time)]) END, 2),
Notes + ISNULL(' - [' + F9 + ']', '')
FROM
ShareTrades_Steve_XLS x
LEFT OUTER JOIN Account a
ON a.ShortName = x.Account
WHERE
[Buy or Sell] IN ('Buy', 'Sell')
ORDER BY [Transaction Date (UK time)]
GO
*/
/*
SELECT * FROM Account
SELECT * FROM Instrument WHERE symbol IN ('LMI3.L', '2BRK.L')
87 LMI3.L
93 2BRK.L
*/
CREATE TABLE TotalHoldingsHistory_Daily (
HistoryDate date NOT NULL,
[Open] int NOT NULL,
@ -188,18 +351,18 @@ GO
GRANT EXECUTE ON TYPE::PriceDataType TO WebApp_Role
GO
CREATE FUNCTION dbo.fn_GetExchangeRate(@Currency VARCHAR(3), @DT DATETIME)
CREATE OR ALTER FUNCTION dbo.fn_GetExchangeRate(@Currency VARCHAR(3), @DT DATETIME)
RETURNS REAL
AS
BEGIN
DECLARE @Rate REAL
IF @Currency = 'GBp' COLLATE Latin1_General_CS_AS
IF @Currency COLLATE Latin1_General_CS_AS = 'GBp' COLLATE Latin1_General_CS_AS
BEGIN
SET @Rate = 100
END
ELSE
BEGIN
IF @Currency = 'GBP' COLLATE Latin1_General_CS_AS
IF @Currency COLLATE Latin1_General_CS_AS = 'GBP' COLLATE Latin1_General_CS_AS
BEGIN
SET @Rate = 1
END
@ -247,7 +410,7 @@ BEGIN
END
GO
CREATE FUNCTION dbo.fn_GetPriceAtDate(@InstrumentID INT, @DT DATETIME)
CREATE OR ALTER FUNCTION dbo.fn_GetPriceAtDate(@InstrumentID INT, @DT DATETIME)
RETURNS REAL
AS
BEGIN
@ -289,7 +452,7 @@ 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))
CREATE OR ALTER PROCEDURE usp_InsertInstrument (@Symbol varchar(8), @FullName varchar(100))
AS
BEGIN
SET NOCOUNT ON
@ -301,7 +464,8 @@ BEGIN
SELECT @DisplayOrder = ISNULL(@DisplayOrder, 1)
--INSERT Instrument (ExchangeID, Symbol, FullName, DisplayOrder) SELECT @ExchangeID, @Symbol, @FullName, @DisplayOrder WHERE NOT EXISTS (SELECT NULL FROM Instrument WHERE ExchangeID = @ExchangeID AND Symbol = @Symbol)
INSERT Instrument (Symbol, FullName, DisplayOrder, PostPandemicDilution) SELECT @Symbol, @FullName, @DisplayOrder, 1
INSERT Instrument (Symbol, FullName, DisplayOrder, PostPandemicDilution, PriceSourceID, TrackPriceHistory) SELECT @Symbol, @FullName, @DisplayOrder, 1, 1, 1
SELECT
i.DisplayOrder,
i.FullName as [InstrumentName],
@ -363,7 +527,7 @@ delete Instrument where InstrumentID>31
*/
--CREATE PROCEDURE usp_UpdateInstrument (@Symbol varchar(8), @GMTOffset int, @Currency varchar(3), @CurrentPrice money, @InstrumentType varchar(15), @TradeDayStart datetime, @TradeDayEnd datetime)
CREATE PROCEDURE usp_UpdateInstrument (@Symbol varchar(8), @GMTOffset int, @Currency varchar(3), @CurrentPrice real, @InstrumentType varchar(15), @TradeDayStart datetime, @TradeDayEnd datetime)
CREATE OR ALTER PROCEDURE usp_UpdateInstrument (@Symbol varchar(8), @GMTOffset int, @Currency varchar(3), @CurrentPrice real, @InstrumentType varchar(15), @TradeDayStart datetime, @TradeDayEnd datetime)
AS
BEGIN
SET NOCOUNT ON
@ -389,7 +553,7 @@ GO
GRANT EXECUTE ON usp_UpdateInstrument TO WebApp_Role
GO
CREATE PROCEDURE usp_GetAccounts
CREATE OR ALTER PROCEDURE usp_GetAccounts
AS
BEGIN
SET NOCOUNT ON
@ -405,7 +569,7 @@ GO
GRANT EXECUTE ON usp_GetAccounts TO WebApp_Role
GO
CREATE PROCEDURE usp_GetInstruments
CREATE OR ALTER PROCEDURE usp_GetInstruments
AS
BEGIN
SET NOCOUNT ON
@ -418,6 +582,8 @@ BEGIN
[PostPandemicDilution],
[GMTOffset],
[Currency],
CASE [TrackPriceHistory] WHEN 1 THEN 1 ELSE 0 END as [TrackPriceHistory], --Javascript doesn't like True/False
[PriceSourceName] as [PriceSource],
[CurrentPrice],
[InstrumentType],
[ShowInMarquee],
@ -440,6 +606,8 @@ BEGIN
i.PostPandemicDilution as [PostPandemicDilution],
i.GMTOffset as [GMTOffset],
i.Currency as [Currency],
i.TrackPriceHistory as [TrackPriceHistory],
ps.PriceSourceName as [PriceSourceName],
i.CurrentPrice as [CurrentPrice],
i.InstrumentType as [InstrumentType],
i.ShowInMarquee as [ShowInMarquee],
@ -452,7 +620,9 @@ BEGIN
ISNULL((SELECT MAX(id.HistoryDT) FROM InstrumentHistory_Intraday id WHERE id.InstrumentID = i.InstrumentID), CONVERT(DATETIME, '1970-01-01')) as [MaxIntradayDate],
(SELECT MAX(h.SoldDate) FROM Holding h WHERE h.InstrumentID = i.InstrumentID AND h.SoldDate IS NOT NULL) as [LastSoldDate]
FROM
Instrument i) as dt
Instrument i
INNER JOIN PriceSource ps
ON ps.PriceSourceID = i.PriceSourceID) as dt
ORDER BY
DisplayOrder
END
@ -460,7 +630,7 @@ GO
GRANT EXECUTE ON usp_GetInstruments TO WebApp_Role
GO
CREATE PROCEDURE usp_GetDailyData (@Symbol varchar(8), @StartDate datetime, @EndDate datetime)
CREATE OR ALTER PROCEDURE usp_GetDailyData (@Symbol varchar(8), @StartDate datetime, @EndDate datetime)
AS
BEGIN
SELECT
@ -485,7 +655,7 @@ GO
GRANT EXECUTE ON usp_GetDailyData TO WebApp_Role
GO
CREATE PROCEDURE usp_GetIntradayData (@Symbol varchar(8), @StartDate datetime, @EndDate datetime)
CREATE OR ALTER PROCEDURE usp_GetIntradayData (@Symbol varchar(8), @StartDate datetime, @EndDate datetime)
AS
BEGIN
DECLARE @MAX_DAYS INT = 30
@ -517,7 +687,7 @@ GO
GRANT EXECUTE ON usp_GetIntradayData TO WebApp_Role
GO
CREATE PROCEDURE usp_InsertDailyData (@Symbol varchar(8), @PriceData PriceDataType READONLY)
CREATE OR ALTER PROCEDURE usp_InsertDailyData (@Symbol varchar(8), @PriceData PriceDataType READONLY)
AS
BEGIN
SET NOCOUNT ON
@ -581,7 +751,7 @@ GO
GRANT EXECUTE ON usp_InsertDailyData TO WebApp_Role
GO
CREATE PROCEDURE usp_InsertIntradayData (@Symbol varchar(8), @PriceData PriceDataType READONLY)
CREATE OR ALTER PROCEDURE usp_InsertIntradayData (@Symbol varchar(8), @PriceData PriceDataType READONLY)
AS
BEGIN
SET NOCOUNT ON
@ -644,7 +814,7 @@ GRANT EXECUTE ON usp_InsertIntradayData TO WebApp_Role
GO
--CREATE PROCEDURE usp_InsertHolding (@Account varchar(20), @Symbol varchar(8), @NoUnits int, @PurchasePricePerUnit money, @PurchaseDate datetime, @Solddate datetime = NULL)
CREATE PROCEDURE usp_InsertHolding (@Account varchar(20), @Symbol varchar(8), @NoUnits real, @PurchasePricePerUnit real, @PurchaseDate datetime, @Solddate datetime = NULL)
CREATE OR ALTER PROCEDURE usp_InsertHolding (@Account varchar(20), @Symbol varchar(8), @NoUnits real, @PurchasePricePerUnit real, @PurchaseDate datetime, @PurchaseCurrency varchar(8), @BookCostInPurchaseCurrency numeric(36, 12), @Solddate datetime = NULL)
AS
BEGIN
DECLARE @AccountID tinyint
@ -660,6 +830,8 @@ BEGIN
NoUnits,
PurchasePricePerUnit,
PurchaseDate,
PurchaseCurrencyID,
BookCostInPurchaseCurrency,
SoldDate)
SELECT
@AccountID,
@ -667,6 +839,8 @@ BEGIN
@NoUnits,
@PurchasePricePerUnit,
@PurchaseDate,
(SELECT InstrumentID FROM Instrument WHERE Symbol = @PurchaseCurrency),
@BookCostInPurchaseCurrency,
@SoldDate
SELECT
@ -760,7 +934,7 @@ GO
-- EXEC usp_InsertHolding 'Steve ii Trd', 'ARB.L', 11459, 261.7805, '2021-03-01 11:25', NULL
GO
CREATE PROCEDURE usp_GetHoldings
CREATE OR ALTER PROCEDURE usp_GetHoldings
AS
BEGIN
SELECT
@ -791,7 +965,7 @@ GO
GRANT EXECUTE ON usp_GetHoldings TO WebApp_Role
GO
CREATE PROCEDURE usp_GetHoldingsHistory
CREATE OR ALTER PROCEDURE usp_GetHoldingsHistory
AS
BEGIN
DECLARE @Dates TABLE (InstrumentID int, DT datetime)
@ -843,7 +1017,7 @@ GO
GRANT EXECUTE ON usp_DeleteHolding TO WebApp_Role
GO
*/
CREATE PROCEDURE usp_SoldHolding (@HoldingID int, @SoldDate datetime, @SoldCurrency varchar(8), @SoldProceeds numeric(36, 12))
CREATE OR ALTER PROCEDURE usp_SoldHolding (@HoldingID int, @SoldDate datetime, @SoldCurrency varchar(8), @SoldProceeds numeric(36, 12))
AS
BEGIN
UPDATE
@ -861,7 +1035,7 @@ GO
GRANT EXECUTE ON usp_SoldHolding TO WebApp_Role
GO
CREATE PROCEDURE usp_SetAccountCashValue (@AccountName varchar(20), @Currency varchar(8), @Value numeric(36, 12))
CREATE OR ALTER PROCEDURE usp_SetAccountCashValue (@AccountName varchar(20), @Currency varchar(8), @Value numeric(36, 12))
AS
BEGIN
DECLARE @AccountID int
@ -921,7 +1095,7 @@ GO
GRANT EXECUTE ON usp_SetAccountCashValue TO WebApp_Role
GO
CREATE PROCEDURE usp_SwapDisplayOrder (@Symbol1 varchar(8), @Symbol2 varchar(8))
CREATE OR ALTER PROCEDURE usp_SwapDisplayOrder (@Symbol1 varchar(8), @Symbol2 varchar(8))
AS
BEGIN
DECLARE @OldDisplayOrder1 TINYINT
@ -950,13 +1124,24 @@ GO
GRANT EXECUTE ON usp_SwapDisplayOrder TO WebApp_Role
GO
CREATE PROCEDURE usp_SetTotalHoldingsValue (@TotalValueGBP money)
CREATE OR ALTER PROCEDURE usp_SetTotalHoldingsValue (@TotalValueGBP money)
AS
BEGIN
DECLARE @Rounded INT = CONVERT(int, FLOOR(@TotalValueGBP))
DECLARE @EndOfPreviousWeek DATE = DATEADD(day, 1 - DATEPART(weekday, CURRENT_TIMESTAMP), convert(date, CURRENT_TIMESTAMP))
DECLARE @PreviousDay DATE = DATEADD(day, -1, CONVERT(DATE, CURRENT_TIMESTAMP))
--Maintain the AllDays table
;WITH cteAllDays AS
(SELECT CONVERT(datetime, CONVERT(date, MIN(PurchaseDate))) as DT FROM Holding
UNION ALL
SELECT DATEADD(day, 1, DT)
FROM cteAllDays
WHERE DATEADD(day, 1, DT) <= CONVERT(date, CURRENT_TIMESTAMP)
)
INSERT AllDays (DT) SELECT DT FROM cteAllDays c WHERE c.DT > (SELECT MAX(DT) FROM AllDays) AND c.DT <= CURRENT_TIMESTAMP OPTION (MAXRECURSION 20000)
IF DATENAME(WEEKDAY, CURRENT_TIMESTAMP) NOT IN ('Saturday', 'Sunday')
BEGIN
IF EXISTS (SELECT NULL FROM TotalHoldingsHistory_Daily WHERE HistoryDate = CONVERT(date, CURRENT_TIMESTAMP))
@ -1003,7 +1188,7 @@ GO
GRANT EXECUTE ON usp_SetTotalHoldingsValue TO WebApp_Role
GO
CREATE PROCEDURE usp_GetTotalHoldingsHistory
CREATE OR ALTER PROCEDURE usp_GetTotalHoldingsHistory
AS
BEGIN
SELECT
@ -1021,7 +1206,7 @@ GO
GRANT EXECUTE ON usp_GetTotalHoldingsHistory TO WebApp_Role
GO
CREATE VIEW vHolding
CREATE OR ALTER VIEW vHolding
AS
SELECT
h.HoldingID,
@ -1042,7 +1227,30 @@ AS
ON a.AccountID = h.AccountID
GO
CREATE VIEW vErroneousPrices_Daily
CREATE OR ALTER VIEW vCashBalances
AS
SELECT
a.AccountID,
h.HoldingID,
a.ShortName as [AccoutName],
i.Symbol,
h.NoUnits,
--i.DisplayName as [InstrumentName],
--i.CurrentPrice,
--h.NoUnits,
h.NoUnits * i.CurrentPrice as [Balance]
FROM
Holding h
INNER JOIN Instrument i
ON i.InstrumentID = h.InstrumentID
INNER JOIN Account a
ON a.AccountID = h.AccountID
WHERE
i.InstrumentType = 'CURRENCY'
AND h.SoldDate IS NULL
GO
CREATE OR ALTER VIEW vErroneousPrices_Daily
AS
SELECT
i.InstrumentID,
@ -1080,7 +1288,7 @@ WHERE
OR (p.ClosePrice/CASE WHEN d1.ClosePrice = 0 THEN 0.0000001 ELSE d1.ClosePrice END > 90 AND n.ClosePrice/CASE WHEN d1.ClosePrice = 0 THEN 0.0000001 ELSE d1.ClosePrice END > 90)
GO
CREATE VIEW vErroneousPrices_Intraday
CREATE OR ALTER VIEW vErroneousPrices_Intraday
AS
SELECT
i.InstrumentID,
@ -1118,7 +1326,7 @@ WHERE
OR (p.ClosePrice/CASE WHEN d1.ClosePrice = 0 THEN 0.0000001 ELSE d1.ClosePrice END > 90 AND n.ClosePrice/CASE WHEN d1.ClosePrice = 0 THEN 0.0000001 ELSE d1.ClosePrice END > 90)
GO
CREATE VIEW vDataQuality_Prices
CREATE OR ALTER VIEW vDataQuality_Prices
AS
SELECT
i.InstrumentID,
@ -1151,7 +1359,169 @@ FROM
AND n.HistoryDT = (SELECT MIN(HistoryDT) FROM InstrumentHistory_Daily WHERE InstrumentID = d1.InstrumentID AND HistoryDT > d1.HistoryDT)
GO
CREATE OR ALTER VIEW vHoldingValueHistory
AS
SELECT
hist.HoldingID,
a.ShortName as [AccountName],
i.Symbol,
i.Currency,
i.FullName as [InstrumentName],
h.PurchaseDate,
h.PurchasePricePerUnit * h.NoUnits as [CostBase],
(h.PurchasePricePerUnit * h.NoUnits) / dbo.fn_GetExchangeRate(Currency, DT) as [CostBaseGBP],
h.SoldDate,
DT as [ValuationDate],
h.NoUnits * dbo.fn_GetPriceAtDate(h.InstrumentID, DT) as [Value],
(h.NoUnits * dbo.fn_GetPriceAtDate(h.InstrumentID, DT) / dbo.fn_GetExchangeRate(Currency, DT)) as [ValueGBP],
h.NoUnits * dbo.fn_GetPriceAtDate(h.InstrumentID, DT) - (h.PurchasePricePerUnit * h.NoUnits) as [Gain],
(h.NoUnits * dbo.fn_GetPriceAtDate(h.InstrumentID, DT) - (h.PurchasePricePerUnit * h.NoUnits)) / dbo.fn_GetExchangeRate(Currency, DT) as [GainGBP]
FROM (
SELECT
HoldingID,
PurchaseDate as DT
FROM
Holding
UNION
SELECT
HoldingID,
ISNULL(SoldDate, CURRENT_TIMESTAMP) as DT
FROM
Holding
UNION
SELECT
HoldingID,
DT
FROM
Holding h
INNER JOIN allDays d
ON d.DT >= h.PurchaseDate
AND d.DT <= ISNULL(h.SoldDate, CURRENT_TIMESTAMP)) as hist
INNER JOIN Holding h
ON h.HoldingID = hist.HoldingID
INNER JOIN Account a
ON a.AccountID = h.AccountID
INNER JOIN Instrument i
ON i.InstrumentID = h.InstrumentID
GO
CREATE OR ALTER VIEW vTradeLedger
AS
SELECT
ah.Name + ' - ' + CASE WHEN a.IsTaxable = 0 THEN 'Non-' ELSE '' END + 'Taxable' as [TaxBucket],
a.ShortName as [AccountName],
a.IsTaxable,
i.Symbol,
--ah.Name + ' - ' + CASE WHEN a.IsTaxable = 0 THEN 'Non-' ELSE '' END + 'Taxable' + '|' + i.Symbol as [InvestmentGroup],
DENSE_RANK() OVER (PARTITION BY a.AccountHolderID, a.IsTaxable, l.InstrumentID ORDER BY TradeDT) as [InvestmentPoolOrder],
i.FullName as [Instrument],
i.DisplayName as [InstrumentDisplayName],
i.Currency as [InstrumentCurrency],
--l.[TradeLedgerID],
l.[InstrumentID],
--l.[AccountID],
l.[TradeDT],
l.[ActionType],
l.[NoUnits],
--l.[TradeCurrencyID],
--l.[ActualExchangeRate],
--l.[EstimatedExchangeRate],
--l.[TradeValue],
l.[TradeValueGBP],
l.[Notes]
FROM
TradeLedger l
INNER JOIN Account a
ON a.AccountID = l.AccountID
INNER JOIN AccountHolder ah
ON ah.AccountHolderID = a.AccountHolderID
INNER JOIN Instrument i
ON i.InstrumentID = l.InstrumentID
INNER JOIN Instrument tc
ON tc.InstrumentID = l.TradeCurrencyID
GO
CREATE OR ALTER VIEW vTradeHistory
AS
WITH cteInvestmentPoolHistory AS (
SELECT
l.*,
l.NoUnits as [NewPoolTotalUnits],
l.TradeValueGBP as [NewPoolBaseCostGBP],
CAST(0 as MONEY) as [RealisedGainGBP]
FROM
vTradeLedger l
WHERE
InvestmentPoolOrder = 1
UNION ALL
SELECT
l.*,
CAST(IIF(l.ActionType = 'Sell', c.NewPoolTotalUnits - l.NoUnits, c.NewPoolTotalUnits + l.NoUnits) as NUMERIC(36, 12)) as [NewPoolTotalUnits],
CASE l.ActionType
WHEN 'Sell' THEN CAST((1 - (l.NoUnits / c.NewPoolTotalUnits)) * c.NewPoolBaseCostGBP as MONEY) --The proportion of units remaining * previous avg cost base
WHEN 'Transfer' THEN IIF(l.NoUnits < 0, c.NewPoolBaseCostGBP - l.TradeValueGBP, c.NewPoolBaseCostGBP + l.TradeValueGBP) --Invert TradeValueGBP for transfers OUT
ELSE c.NewPoolBaseCostGBP + l.TradeValueGBP
END as [NewPoolBaseCostGBP],
CASE
WHEN l.ActionType = 'Sell' THEN CAST(l.TradeValueGBP - ((l.NoUnits / c.NewPoolTotalUnits) * c.NewPoolBaseCostGBP) as MONEY) --Trade Value - (The proportion of units sold * previous avg cost base)
ELSE 0
END as [RealisedGainGBP]
FROM
vTradeLedger l
INNER JOIN cteInvestmentPoolHistory c
ON c.TaxBucket = l.TaxBucket
AND c.Symbol = l.Symbol
AND c.InvestmentPoolOrder = l.InvestmentPoolOrder - 1
)
SELECT
*,
CAST(IIF(EXISTS (SELECT 1 FROM cteInvestmentPoolHistory n WHERE n.TaxBucket = c.TaxBucket AND n.Instrument = c.Instrument AND n.InvestmentPoolOrder > c.InvestmentPoolOrder), 0, 1) as BIT) as [IsFinalPosition]
FROM
cteInvestmentPoolHistory c
GO
/*
SELECT
Symbol,
Currency,
ValuationDate,
SUM(CostBase) as [TotalCostBase],
SUM(Value) as [TotalValue],
SUM(Gain) as [Gain]
FROM vHoldingValueHistory
--WHERE HoldingID = 169
WHERE Symbol = 'LMI3.L'
GROUP BY
Symbol,
Currency,
ValuationDate
ORDER BY Symbol, ValuationDate
SELECT
ValuationDate,
SUM(CostBaseGBP) as CostBase,
SUM(ValueGBP) as Value,
SUM(GainGBP) as Gain
FROM vHoldingValueHistory
WHERE ValuationDate >= '2024-02-01'
AND CONVERT(varchar(30), ValuationDate, 120) LIKE '%00:00:00%'
GROUP BY
ValuationDate
ORDER BY ValuationDate
SELECT
--Currency COLLATE Latin1_General_CS_AS,
ValuationDate,
--dbo.fn_GetExchangeRate(Currency, ValuationDate),
--CostBase / dbo.fn_GetExchangeRate(Currency, ValuationDate) as CostBaseGBP,
SUM(CostBase) as [TotalCostBase],
SUM(CostBase) / dbo.fn_GetExchangeRate(Currency, ValuationDate) as [TotalCostBaseGBP],
SUM(Value) as [TotalValue],
SUM(Value) / dbo.fn_GetExchangeRate(Currency, ValuationDate) as [TotalValueGBP],
SUM(Gain) / dbo.fn_GetExchangeRate(Currency, ValuationDate) as [Gain]
FROM vHoldingValueHistory
WHERE ValuationDate = '2024-02-24 00:00:00'
GROUP BY ValuationDate
select * from Instrument

View File

@ -39,7 +39,7 @@
<td class="navbarlink" style="width: 35%;">
<a id="navCharts" class="activenav" onclick="showTab(this)" data-div="chartDiv">Charts</a>
<a id="navCurrentHoldings" onclick="showTab(this);" data-div="holdingsDiv">Holdings</a>
<a id="navAccounts" onclick="showTab(this);" data-div="accoountsDiv">Accounts</a>
<a id="navAccounts" onclick="showTab(this);" data-div="accountsDiv">Accounts</a>
<a id="navAnal" onclick="showTab(this);" data-div="analDiv">Analysis</a>
<a id="navHist" onclick="showTab(this); refreshHistoryChart();" data-div="histDiv">History</a>
<a id="navLog" onclick="showTab(this);" data-div="logDiv">Log</a>
@ -94,8 +94,11 @@
</div>
</div>
<div class="tabParent">
<div class="tabContainer" id="accoountsDiv">
<div id="accoutsSummaryDiv"></div>
<div class="tabContainer" id="accountsDiv">
<div>
<label for="hideEmptyAccounts">Hide Empty Accounts</label><input type="checkbox" name="hideEmptyAccounts" id="hideEmptyAccounts" onclick="handleClick(this)"/>
</div>
<div id="accountsSummaryDiv"></div>
</div>
</div>
<div class="tabParent">
@ -119,7 +122,7 @@
</div>
</div>
<div id="modalChart" class="modal-chart">
<div style-"height: 70px;">&nbsp;</div>
<div style="height: 70px;">&nbsp;</div>
<div class="modal-chart-content">
<table style="height: 100%; width:100%; border-collapse: collapse;">
<tr>
@ -159,6 +162,9 @@
c = Cookies.get('groupBySymbol');
$("#groupBySymbol").prop('checked', (c == 'true') ? true : false);
c = Cookies.get('hideEmptyAccounts');
$("#hideEmptyAccounts").prop('checked', (c == 'true') ? true : false);
getInstruments();
}
function showTab(tab) {
@ -196,6 +202,8 @@
tablesUpdateTimings.updateNeeded = true;
tablesUpdateTimings.lastUpdate = 0;
break;
case 'hideEmptyAccounts':
break;
}
}
$(document).ready(initPage());

View File

@ -32,11 +32,11 @@ Public Class SharePrices
'End Sub
<WebMethod()>
Public Shared Sub AddHolding(AccountName As String, Symbol As String, NoUnits As Double, PurchasePricePerUnit As Double, PurchaseDate As Int64)
Public Shared Sub AddHolding(AccountName As String, Symbol As String, NoUnits As Double, PurchasePricePerUnit As Double, PurchaseDate As Int64, PurchaseCurrency As String, BookCostInPurchaseCurrency As Double)
'Public Shared Sub AddHolding(AccountName As String, Symbol As String, NoUnits As Integer, PurchasePricePerUnit As Double, PurchaseDate As Int64)
'Debug.Print(Now().ToString("yyyy-MM-dd HH:mm:ss") + " - SharePrices.AddHolding webmethod: " + AccountName + ", " + Symbol)
Dim responseText As String = DataAccessLayer.AddHolding(AccountName, Symbol, NoUnits, PurchasePricePerUnit, PurchaseDate)
Dim responseText As String = DataAccessLayer.AddHolding(AccountName, Symbol, NoUnits, PurchasePricePerUnit, PurchaseDate, PurchaseCurrency, BookCostInPurchaseCurrency)
SetResponseAndCompleteRequest(HttpContext.Current, "application/json", responseText)
End Sub
@ -146,27 +146,45 @@ Public Class SharePrices
SetResponseAndCompleteRequest(HttpContext.Current, "application/json", responseText)
End Sub
Private Shared Function DownloadYahooFinanceWebString(queryString As String) As String
Dim webClient As New System.Net.WebClient()
'webClient.Proxy = New System.Net.WebProxy("127.0.0.1", 8888)
'Set headers required by Yahoo Finance
webClient.Headers.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:123.0) Gecko/20100101 Firefox/123.0")
webClient.Headers.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8")
webClient.Headers.Add("Accept-Language", "en-GB,en;q=0.5")
'webClient.Headers.Add("Accept-Encoding", "gzip, deflate, br")
'Connection: keep -alive
webClient.Headers.Add("Upgrade-Insecure-Requests", "1")
webClient.Headers.Add("Sec-Fetch-Dest", "document()")
webClient.Headers.Add("Sec-Fetch-Mode", "navigate()")
webClient.Headers.Add("Sec-Fetch-Site", "none()")
webClient.Headers.Add("Sec-Fetch-User", "?1")
DownloadYahooFinanceWebString = webClient.DownloadString(queryString)
End Function
<WebMethod()>
Public Shared Sub FetchYahooFinanceIntraday(Symbol As String)
Debug.Print(Now().ToString("yyyy-MM-dd HH:mm:ss") + " - SharePrices.FetchYahooFinanceIntraday webmethod: " + Symbol)
Dim webClient As New System.Net.WebClient
Dim responseText As String = webClient.DownloadString("https://query1.finance.yahoo.com/v8/finance/chart/" + Symbol + "?region=GB&lang=en-GB&includePrePost=false&interval=1h&range=1mo&.tsrc=finance")
'Dim webClient As New System.Net.WebClient
'webClient.Headers.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36")
'webClient.Headers.Add("Cookie", "GUC=AQABCAFlwl1l7kIazgRS&s=AQAAAIC-LaMs&g=ZcEX6w; A1=d=AQABBKsFOF4CEKgz-h7IBcA5SkDmxV7-I5sFEgABCAFdwmXuZfbPb2UBAiAAAAcI0629W8qjNBE&S=AQAAAovZo-67b5pxvVWHK4xGTFo; A3=d=AQABBKsFOF4CEKgz-h7IBcA5SkDmxV7-I5sFEgABCAFdwmXuZfbPb2UBAiAAAAcI0629W8qjNBE&S=AQAAAovZo-67b5pxvVWHK4xGTFo; A1S=d=AQABBKsFOF4CEKgz-h7IBcA5SkDmxV7-I5sFEgABCAFdwmXuZfbPb2UBAiAAAAcI0629W8qjNBE&S=AQAAAovZo-67b5pxvVWHK4xGTFo; EuConsent=CP325IAP325IAAOACKENAqEgAAAAAAAAACiQAAAAAAAA; thamba=2; PRF=t%3DMSTR%252BSHIB-GBP%252BGBPAUD%253DX%252BCOIN%252BAMZN%252BRIOT%252BLMI3.L%252BFLT.AX%252BAAL%252BMKS.L%252BMSFT%252BLUNC-USD%252B3LMI.L%252BNCLH%252B3MIB.MI%26newChartbetateaser%3D1%26qct%3DtrendArea; cmp=t=1709739374&j=1&u=1---&v=16")
'Dim responseText As String = webClient.DownloadString("https://query1.finance.yahoo.com/v8/finance/chart/" + Symbol + "?region=GB&lang=en-GB&includePrePost=false&interval=1h&range=1mo&.tsrc=finance")
Dim responseText As String = DownloadYahooFinanceWebString("https://query1.finance.yahoo.com/v8/finance/chart/" + Symbol + "?region=GB&lang=en-GB&includePrePost=false&interval=1h&range=1mo&.tsrc=finance")
SetResponseAndCompleteRequest(HttpContext.Current, "application/json", responseText)
End Sub
<WebMethod()>
Public Shared Sub FetchYahooFinanceIntraday(Symbol As String, MinIntradayDT As Long)
Debug.Print(Now().ToString("yyyy-MM-dd HH:mm:ss") + " - SharePrices.FetchYahooFinanceIntraday webmethod: " + Symbol + ", " + MinIntradayDT.ToString() + " (" + FromEpochTime(MinIntradayDT).ToString("yyyy-MM-dd HH:mm") + ")")
'Dim webClient As New System.Net.WebClient
'Dim responseText As String = webClient.DownloadString("https://query1.finance.yahoo.com/v8/finance/chart/" + Symbol + "?region=GB&lang=en-GB&includePrePost=false&interval=1h&range=1mo&.tsrc=finance")
'SetResponseAndCompleteRequest(HttpContext.Current, "application/json", responseText)
Try
Dim queryString As String
'If Symbol = "BTC-USD" Or Symbol = "ARB.L" Then 'Request 15min interval data for Bitcoin/USD and Argo Blockchain
If MinIntradayDT <= ToEpochTime(Now().AddDays(-59)) Then 'Yahoo finance only provides 15min data for previous 60 days
queryString = "https://query1.finance.yahoo.com/v8/finance/chart/" + Symbol + "?region=GB&lang=en-GB&includePrePost=false&interval=15m&range=1mo&.tsrc=finance"
Else
@ -184,16 +202,15 @@ Public Class SharePrices
End If
queryString = "https://query1.finance.yahoo.com/v8/finance/chart/" + Symbol + "?period1=" + dateFrom.ToString() + "&period2=" + dateTo.ToString() + "&interval=15m&includePrePost=true&events=div|split|earn"
End If
'Else
'If MinIntradayDT <= ToEpochTime(Now().AddYears(-1)) Then
' queryString = "https://query1.finance.yahoo.com/v8/finance/chart/" + Symbol + "?region=GB&lang=en-GB&includePrePost=false&interval=1d&range=6mo&.tsrc=finance"
'Else
' queryString = "https://query1.finance.yahoo.com/v8/finance/chart/" + Symbol + "?region=GB&lang=en-GB&includePrePost=false&interval=1d&range=2y&.tsrc=finance"
'End If
'End If
Debug.Print(Strings.StrDup(22, " ") + queryString)
Dim responseText As String = New System.Net.WebClient().DownloadString(queryString)
'Dim webClient As New System.Net.WebClient
'webClient.Headers.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36")
'webClient.Headers.Add("Cookie", "GUC=AQABCAFlwl1l7kIazgRS&s=AQAAAIC-LaMs&g=ZcEX6w; A1=d=AQABBKsFOF4CEKgz-h7IBcA5SkDmxV7-I5sFEgABCAFdwmXuZfbPb2UBAiAAAAcI0629W8qjNBE&S=AQAAAovZo-67b5pxvVWHK4xGTFo; A3=d=AQABBKsFOF4CEKgz-h7IBcA5SkDmxV7-I5sFEgABCAFdwmXuZfbPb2UBAiAAAAcI0629W8qjNBE&S=AQAAAovZo-67b5pxvVWHK4xGTFo; A1S=d=AQABBKsFOF4CEKgz-h7IBcA5SkDmxV7-I5sFEgABCAFdwmXuZfbPb2UBAiAAAAcI0629W8qjNBE&S=AQAAAovZo-67b5pxvVWHK4xGTFo; EuConsent=CP325IAP325IAAOACKENAqEgAAAAAAAAACiQAAAAAAAA; thamba=2; PRF=t%3DMSTR%252BSHIB-GBP%252BGBPAUD%253DX%252BCOIN%252BAMZN%252BRIOT%252BLMI3.L%252BFLT.AX%252BAAL%252BMKS.L%252BMSFT%252BLUNC-USD%252B3LMI.L%252BNCLH%252B3MIB.MI%26newChartbetateaser%3D1%26qct%3DtrendArea; cmp=t=1709739374&j=1&u=1---&v=16")
'Dim responseText As String = webClient.DownloadString(queryString)
Dim responseText As String = DownloadYahooFinanceWebString(queryString)
SetResponseAndCompleteRequest(HttpContext.Current, "application/json", responseText)
Catch ex As Exception
@ -211,9 +228,12 @@ Public Class SharePrices
'End If
Try
Dim webClient As New System.Net.WebClient
Dim responseText As String = webClient.DownloadString("https://query1.finance.yahoo.com/v8/finance/chart/" + Symbol + "?region=GB&lang=en-GB&includePrePost=false&interval=1d&range=6mo&.tsrc=finance")
'Dim responseText As String = webClient.DownloadString("https://query1.finance.yahoo.com/v8/finance/chart/" + Symbol + "?region=GB&lang=en-GB&includePrePost=false&interval=1d&range=2y&.tsrc=finance")
'Dim webClient As New System.Net.WebClient
'webClient.Headers.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36")
'webClient.Headers.Add("Cookie", "GUC=AQABCAFlwl1l7kIazgRS&s=AQAAAIC-LaMs&g=ZcEX6w; A1=d=AQABBKsFOF4CEKgz-h7IBcA5SkDmxV7-I5sFEgABCAFdwmXuZfbPb2UBAiAAAAcI0629W8qjNBE&S=AQAAAovZo-67b5pxvVWHK4xGTFo; A3=d=AQABBKsFOF4CEKgz-h7IBcA5SkDmxV7-I5sFEgABCAFdwmXuZfbPb2UBAiAAAAcI0629W8qjNBE&S=AQAAAovZo-67b5pxvVWHK4xGTFo; A1S=d=AQABBKsFOF4CEKgz-h7IBcA5SkDmxV7-I5sFEgABCAFdwmXuZfbPb2UBAiAAAAcI0629W8qjNBE&S=AQAAAovZo-67b5pxvVWHK4xGTFo; EuConsent=CP325IAP325IAAOACKENAqEgAAAAAAAAACiQAAAAAAAA; thamba=2; PRF=t%3DMSTR%252BSHIB-GBP%252BGBPAUD%253DX%252BCOIN%252BAMZN%252BRIOT%252BLMI3.L%252BFLT.AX%252BAAL%252BMKS.L%252BMSFT%252BLUNC-USD%252B3LMI.L%252BNCLH%252B3MIB.MI%26newChartbetateaser%3D1%26qct%3DtrendArea; cmp=t=1709739374&j=1&u=1---&v=16")
'Dim responseText As String = webClient.DownloadString("https://query1.finance.yahoo.com/v8/finance/chart/" + Symbol + "?region=GB&lang=en-GB&includePrePost=false&interval=1d&range=6mo&.tsrc=finance")
Dim responseText As String = DownloadYahooFinanceWebString("https://query1.finance.yahoo.com/v8/finance/chart/" + Symbol + "?region=GB&lang=en-GB&includePrePost=false&interval=1d&range=6mo&.tsrc=finance")
'If Symbol = "RYA.L" Then
' Debug.Print(" FetchYahooFinanceDaily result: " + responseText)
@ -236,7 +256,12 @@ Public Class SharePrices
queryString = "https://query1.finance.yahoo.com/v8/finance/chart/" + Symbol + "?region=GB&lang=en-GB&includePrePost=false&interval=1d&range=2y&.tsrc=finance"
End If
Dim responseText As String = New System.Net.WebClient().DownloadString(queryString)
'Dim webClient As New System.Net.WebClient()
'webClient.Headers.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36")
'webClient.Headers.Add("Cookie", "GUC=AQABCAFlwl1l7kIazgRS&s=AQAAAIC-LaMs&g=ZcEX6w; A1=d=AQABBKsFOF4CEKgz-h7IBcA5SkDmxV7-I5sFEgABCAFdwmXuZfbPb2UBAiAAAAcI0629W8qjNBE&S=AQAAAovZo-67b5pxvVWHK4xGTFo; A3=d=AQABBKsFOF4CEKgz-h7IBcA5SkDmxV7-I5sFEgABCAFdwmXuZfbPb2UBAiAAAAcI0629W8qjNBE&S=AQAAAovZo-67b5pxvVWHK4xGTFo; A1S=d=AQABBKsFOF4CEKgz-h7IBcA5SkDmxV7-I5sFEgABCAFdwmXuZfbPb2UBAiAAAAcI0629W8qjNBE&S=AQAAAovZo-67b5pxvVWHK4xGTFo; EuConsent=CP325IAP325IAAOACKENAqEgAAAAAAAAACiQAAAAAAAA; thamba=2; PRF=t%3DMSTR%252BSHIB-GBP%252BGBPAUD%253DX%252BCOIN%252BAMZN%252BRIOT%252BLMI3.L%252BFLT.AX%252BAAL%252BMKS.L%252BMSFT%252BLUNC-USD%252B3LMI.L%252BNCLH%252B3MIB.MI%26newChartbetateaser%3D1%26qct%3DtrendArea; cmp=t=1709739374&j=1&u=1---&v=16")
'Dim responseText As String = webClient.DownloadString(queryString)
Dim responseText As String = DownloadYahooFinanceWebString(queryString)
SetResponseAndCompleteRequest(HttpContext.Current, "application/json", responseText)
Catch ex As Exception
@ -250,8 +275,9 @@ Public Class SharePrices
'Debug.Print(Now().ToString("yyyy-MM-dd HH:mm:ss") + " - SharePrices.FetchYahooFinanceSingleDay webmethod: " + Symbol)
Try
Dim webClient As New System.Net.WebClient
Dim responseText As String = webClient.DownloadString("https://query1.finance.yahoo.com/v8/finance/chart/" + Symbol + "?region=GB&lang=en-GB&includePrePost=false&interval=2m&range=1d&corsDomain=uk.finance.yahoo.com&.tsrc=finance")
'Dim webClient As New System.Net.WebClient
'Dim responseText As String = webClient.DownloadString("https://query1.finance.yahoo.com/v8/finance/chart/" + Symbol + "?region=GB&lang=en-GB&includePrePost=false&interval=2m&range=1d&corsDomain=uk.finance.yahoo.com&.tsrc=finance")
Dim responseText As String = DownloadYahooFinanceWebString("https://query1.finance.yahoo.com/v8/finance/chart/" + Symbol + "?region=GB&lang=en-GB&includePrePost=false&interval=2m&range=1d&corsDomain=uk.finance.yahoo.com&.tsrc=finance")
SetResponseAndCompleteRequest(HttpContext.Current, "application/json", responseText)
Catch ex As Exception
Debug.Print(Now().ToString("yyyy-MM-dd HH:mm:ss") + " - ERROR - SharePrices.FetchYahooFinanceSingleDay webmethod: " + Symbol + "): " + ex.Message)
@ -259,11 +285,89 @@ Public Class SharePrices
End Try
End Sub
Private Shared lseToken As String = ""
Private Shared lseTokenLastUpdated = New DateTime(2000, 1, 1)
Private Shared lseTokenUpdateIntervalMins = 5
Private Shared Function DownloadLseWebString(queryString As String) As String
If lseToken = "" Or lseTokenLastUpdated < Now().AddMinutes(0 - lseTokenUpdateIntervalMins) Then
Dim wc As New System.Net.WebClient()
wc.Headers.Add("Dnt", "1")
wc.Headers.Add("Sid", "98b738c5-9d73-4d18-9329-7e248a2c7d16")
'lseToken = wc.DownloadString("https://refinitiv-widgets.financial.com/auth/api/v1/tokens")
lseToken = wc.UploadString("https://refinitiv-widgets.financial.com/auth/api/v1/tokens", "POST", "")
lseTokenLastUpdated = Now()
Debug.Print("New LSE Token: " + lseToken)
Else
Debug.Print("Reusing LSE Token: " + lseToken)
End If
Dim webClient As New System.Net.WebClient()
'Set headers required by LondonStockExchange.com
'Dim Quote As String = Chr(34)
'webClient.Headers.Add(":authority", "refinitiv-widgets.financial.com")
'webClient.Headers.Add(":method", "GET")
'webClient.Headers.Add(":path", "/rest/api/timeseries/tns?ric=LMI3.L&fids=0,0,6,8993,178,6,tns.-103,44&count=100&__ts=1711471848806")
'webClient.Headers.Add(":scheme", "https")
'webClient.Headers.Add("Accept", "application/json")
'webClient.Headers.Add("Accept-Encoding", "gzip, deflate, br, zstd")
'webClient.Headers.Add("Accept-Language", "en-US,en;q=0.9")
'webClient.Headers.Add("Cache-Control", "no-cache")
webClient.Headers.Add("Dnt", "1")
'webClient.Headers.Add("Jwt", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsZ24iOiJXR19MU0VHIiwibmJmIjoxNzExNDcxODQzLCJwcnQiOiJ3bGliX0xTRUciLCJpc3MiOiJmaW5jb20iLCJwcm0iOlsiYTpzdHJlYW1pbmciLCJVU0VSIl0sImV0eSI6IldHX0xTRUciLCJleHAiOjE3MTE0NzIxNDgsImlhdCI6MTcxMTQ3MTg0OCwianRpIjoiODE4ZDQxZDktMThjZS00MGVjLWFkMzEtODk1NjRiYTlkMmY3IiwiZGlkIjoiV0dfTFNFRyIsInNlYSI6ImIwNTc5ZjczLWUxZjYtNDExZS1hYjk1LWNjZTYyNmY5OTJmZCJ9.cLv4uKzKLL4FmzMSljecOdN_0R5be1xOicOYw_WNvY0")
webClient.Headers.Add("Jwt", lseToken)
'webClient.Headers.Add("Origin", "https://www.londonstockexchange.com")
'webClient.Headers.Add("Pragma", "no-cache")
'webClient.Headers.Add("Referer", "https://www.londonstockexchange.com/")
'webClient.Headers.Add("Sec-Ch-Ua", Quote + "Google Chrome" + Quote + ";v=" + Quote + "123" + Quote + ", " + Quote + "Not:A-Brand" + Quote + ";v=" + Quote + "8" + Quote + ", " + Quote + "Chromium" + Quote + ";v=" + Quote + "123" + Quote)
'webClient.Headers.Add("Sec-Ch-Ua-Mobile", "?0")
'webClient.Headers.Add("Sec-Ch-Ua-Platform", Quote + "Windows" + Quote)
'webClient.Headers.Add("Sec-Fetch-Dest", "empty")
'webClient.Headers.Add("Sec-Fetch-Mode", "cors")
'webClient.Headers.Add("Sec-Fetch-Site", "cross-site")
webClient.Headers.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36")
DownloadLseWebString = webClient.DownloadString(queryString)
End Function
<WebMethod()>
Public Shared Sub FetchLseSingleDay(Symbol As String, FromDate As Long, ToDate As Long)
'https://refinitiv-widgets.financial.com/rest/api/timeseries/tns?ric=LMI3.L&fids=0,0,6,8993,178,6,tns.-103,44&fromDate=1711440000000&toDate=1711469220000&count=100&__ts=1711469273221
Debug.Print(Now().ToString("yyyy-MM-dd HH:mm:ss") + " - SharePrices.FetchLseSingleDay webmethod: " + Symbol + ", " + FromDate.ToString() + " (" + FromEpochTime(FromDate).ToString("yyyy-MM-dd HH:mm") + "), " + ToDate.ToString() + " (" + FromEpochTime(ToDate).ToString("yyyy-MM-dd HH:mm") + ")")
Try
Dim queryString As String
queryString = "https://refinitiv-widgets.financial.com/rest/api/timeseries/tns?ric=" + Symbol + "&fids=0,0,6,8993,178,6,tns.-103,44&fromDate=" + FromDate.ToString() + "&toDate=" + ToDate.ToString() + "&count=1000" '"&__ts=1711469273221"
Debug.Print(Strings.StrDup(22, " ") + queryString)
Dim responseText As String = DownloadLseWebString(queryString)
SetResponseAndCompleteRequest(HttpContext.Current, "application/json", responseText)
Catch ex As Exception
Debug.Print(Now().ToString("yyyy-MM-dd HH:mm:ss") + " - ERROR - SharePrices.FetchLseSingleDay webmethod: " + Symbol + ", " + FromDate.ToString() + " (" + FromEpochTime(FromDate).ToString("yyyy-MM-dd HH:mm") + "), " + ToDate.ToString() + " (" + FromEpochTime(ToDate).ToString("yyyy-MM-dd HH:mm") + "): " + ex.Message)
SetResponseAndCompleteRequest(HttpContext.Current, "application/json", "{""Result"":""Error"", ""MSG"":""" + ex.Message.Replace("""", "'") + """}")
End Try
End Sub
<WebMethod()>
Public Shared Sub SearchYahooFinanceShares(SearchString As String)
Debug.Print(Now().ToString("yyyy-MM-dd HH:mm:ss") + " - SharePrices.SearchYahooFinanceShares webmethod: " + SearchString)
Dim webClient As New System.Net.WebClient
webClient.Headers.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:123.0) Gecko/20100101 Firefox/123.0")
webClient.Headers.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8")
webClient.Headers.Add("Accept-Language", "en-GB,en;q=0.5")
'webClient.Headers.Add("Accept-Encoding", "gzip, deflate, br")
'Connection: keep -alive
webClient.Headers.Add("Upgrade-Insecure-Requests", "1")
webClient.Headers.Add("Sec-Fetch-Dest", "document()")
webClient.Headers.Add("Sec-Fetch-Mode", "navigate()")
webClient.Headers.Add("Sec-Fetch-Site", "none()")
webClient.Headers.Add("Sec-Fetch-User", "?1")
Dim responseText As String = webClient.DownloadString("https://query1.finance.yahoo.com/v1/finance/search?q=" + SearchString + "&quotesCount=6&newsCount=0&enableFuzzyQuery=false&quotesQueryId=tss_match_phrase_query&multiQuoteQueryId=multi_quote_single_token_query&newsQueryId=news_ss_symbols&enableCb=false&enableNavLinks=false&vespaNewsTimeoutMs=600")
SetResponseAndCompleteRequest(HttpContext.Current, "application/json", responseText)
End Sub

View File

@ -420,7 +420,9 @@
<DependentUpon>Web.config</DependentUpon>
</None>
</ItemGroup>
<ItemGroup />
<ItemGroup>
<Folder Include="My Project\PublishProfiles\" />
</ItemGroup>
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>

View File

@ -1,5 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<NameOfLastUsedPublishProfile>WinSrv1</NameOfLastUsedPublishProfile>
</PropertyGroup>
<ProjectExtensions>
<VisualStudio>
<FlavorProperties GUID="{349c5851-65df-11da-9384-00065b846f21}">

View File

@ -0,0 +1,9 @@
D:\Documents\Visual Studio Projects\SharePrices\SharePrices\bin\SharePrices.dll.config
D:\Documents\Visual Studio Projects\SharePrices\SharePrices\bin\SharePrices.dll
D:\Documents\Visual Studio Projects\SharePrices\SharePrices\bin\SharePrices.pdb
D:\Documents\Visual Studio Projects\SharePrices\SharePrices\bin\SharePrices.xml
D:\Documents\Visual Studio Projects\SharePrices\SharePrices\obj\Release\SharePrices.Resources.resources
D:\Documents\Visual Studio Projects\SharePrices\SharePrices\obj\Release\SharePrices.vbproj.GenerateResource.Cache
D:\Documents\Visual Studio Projects\SharePrices\SharePrices\obj\Release\SharePrices.dll
D:\Documents\Visual Studio Projects\SharePrices\SharePrices\obj\Release\SharePrices.xml
D:\Documents\Visual Studio Projects\SharePrices\SharePrices\obj\Release\SharePrices.pdb

View File

@ -0,0 +1,44 @@
<?xml version="1.0"?>
<doc>
<assembly>
<name>
SharePrices
</name>
</assembly>
<members>
<member name="F:SharePrices.HighchartsTest.form1">
<summary>
form1 control.
</summary>
<remarks>
Auto-generated field.
To modify move field declaration from designer file to code-behind file.
</remarks>
</member><member name="P:SharePrices.My.MyWebExtension.Computer">
<summary>
Returns information about the host computer.
</summary>
</member><member name="P:SharePrices.My.MyWebExtension.User">
<summary>
Returns information for the current Web user.
</summary>
</member><member name="P:SharePrices.My.MyWebExtension.Request">
<summary>
Returns Request object.
</summary>
</member><member name="P:SharePrices.My.MyWebExtension.Response">
<summary>
Returns Response object.
</summary>
</member><member name="P:SharePrices.My.MyWebExtension.Log">
<summary>
Returns the Asp log object.
</summary>
</member><member name="T:SharePrices.My.MyWebExtension">
<summary>
Module used to define the properties that are available in the My Namespace for Web projects.
</summary>
<remarks></remarks>
</member>
</members>
</doc>

View File

@ -10,6 +10,7 @@ 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 instrumentSearchResults = [];
var tablesUpdateTimings = { lastUpdate: 0, timeBetweenUpdates: 10000, timer: 0, updateNeeded: true };
@ -123,7 +124,7 @@ var Instruments = {
let lastFetchedDate = this.Data[0].lastFetchedSingleDay;
let lastFetchedIndex = 0;
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' && this.Data[i].TrackPriceHistory == 1) {
if (!this.Data[i].lastFetchedSingleDay) { this.Data[i].lastFetchedSingleDay = 0 };
if (lastFetchedDate > this.Data[i].lastFetchedSingleDay) {
lastFetchedDate = this.Data[i].lastFetchedSingleDay;
@ -173,41 +174,6 @@ var Instruments = {
}
}
},
zzzzz_MarketIsOpen: function (instrument) {
let currentDT = new Date().getTime();
let startOfDay = new Date().setHours(0, 0, 0);
if (instrument.SingleDayStartDate > (startOfDay + timespans.oneDay)) {
currentDT += timespans.oneDay;
}
//if (instrument.Symbol == 'QAN.AX') {
//console.info('Qantas');
//console.info(' Current local time: ' + new Date().getTime().toString() + new Date().yyyymmddhhmmss());
//console.info(' SingleDayStartDate: ' + new Date(instrument.SingleDayStartDate).getTime().toString() + new Date(instrument.SingleDayStartDate).yyyymmddhhmmss());
//console.info(' SingleDayEndDate: ' + new Date(instrument.SingleDayEndDate).getTime().toString() + new Date(instrument.SingleDayEndDate).yyyymmddhhmmss());
//}
if (currentDT < instrument.SingleDayStartDate) {
return 0; //Not open yet
} else {
if (currentDT > instrument.SingleDayEndDate) {
return 2; //Market closed (end of day)
} else {
return 1; //Market is currently open
}
}
},
zzzz_MarketIsOpen: function (instrument) {
let currentMarketDT = new Date().getTime() + instrument.GMTOffset;
if (currentMarketDT < instrument.SingleDayStartDate) {
return 0; //Not open yet
} else {
if (currentMarketDT > instrument.SingleDayEndDate) {
return 2; //Market closed (end of day)
} else {
return 1; //Market is currently open
}
}
},
MarketIsOpen: function (instrument, preOpenThreshold, postCloseThreshold) {
let currentMarketDT = new Date().getTime() + instrument.GMTOffset;
@ -515,11 +481,13 @@ function createSharesTable() {
//if (i.DisplayOrder) {
//if ((!excludeNoHoldings) || i.InstrumentType != 'EQUITY' || Instruments.GetCurrentHoldings(i).length > 0) {
if ((!excludeNoHoldings) || (i.InstrumentType != 'EQUITY' && i.InstrumentType != 'CRYPTOCURRENCY') || Instruments.GetCurrentHoldings(i).length > 0) {
if (i.Symbol != 'GBP') {
let cls = (rowNo % 2) ? 'shareRow altShareRow' : 'shareRow';
let tr = '<tr class="' + cls + '" id="shareRow' + i.DisplayOrder.toString() + '">' + createSharesTableRow(i) + '</tr>';
tbl += tr;
}
}
}
tbl += '</table>';
$('#chartsDiv').html(tbl);
//getAllLocalDailyData();
@ -801,7 +769,7 @@ function getAllLocalIntradayData() {
}
//fetchTimer = setTimeout(getNewRemoteData, 500);
//fetchTimer = setInterval(getNewRemoteData, 1250);
fetchTimer = setInterval(getNewRemoteData, 750);
fetchTimer = setInterval(getNewRemoteData, 1000);
},
failure: function (response) { console.error("getYTDData error:"); console.info(response); }
});
@ -1347,7 +1315,7 @@ function createChart(Instrument, startDate, endDate, chartContainerID, summaryCo
let seriesData = [];
let volumeData = [];
if (chartData.length > 1) {
if (chartData && chartData.length > 1) {
/*let transformedAndSummarisedData = transformAndSummariseChartData(chartData);
let summaryData = transformedAndSummarisedData.summaryData;
let basePrice = transformedAndSummarisedData.basePrice;
@ -2523,6 +2491,83 @@ function getYahooSingleDayData(Instrument) {
failure: function (response) { console.error("getYahooSingleDayData error:"); console.info(response); }
});
}
function getLseSingleDayData(Instrument) {
//console.info('getLseSingleDayData: ' + Instrument.Symbol);
Instrument.lastFetchedSingleDay = new Date().getTime();
let fromDate = new Date().setHours(0, 0, 0);
let toDate = new Date().getTime();
$.ajax({
type: "POST",
contentType: "application/json",
url: "SharePrices.aspx/FetchLseSingleDay?S=" + Instrument.Symbol,
dataType: "json",
data: JSON.stringify({ Symbol: Instrument.Symbol, FromDate: fromDate, ToDate: toDate }),
success: function (response) {
console.info("getLseSingleDayData: " + JSON.stringify(response));
/*
let newData = [];
if (response.chart && response.chart.result) {
let dataSeries = response.chart.result[0];
if (dataSeries.meta) {
Instrument.SingleDayPreviousClose = dataSeries.meta.previousClose;
Instrument.SingleDayStartDate = dataSeries.meta.currentTradingPeriod.regular.start * 1000;
Instrument.SingleDayEndDate = dataSeries.meta.currentTradingPeriod.regular.end * 1000;
Instrument.GMTOffset = dataSeries.meta.gmtoffset;
Instrument.InstrumentType = dataSeries.meta.instrumentType;
Instrument.Currency = dataSeries.meta.currency;
Instrument.CurrentPrice = dataSeries.meta.regularMarketPrice;
}
if (response.chart.result[0].timestamp) {
let ignoredEntries = 0;
for (let i = 0; i < dataSeries.timestamp.length; i++) {
let quote = dataSeries.indicators.quote[0];
if (quote.open[i] == null || quote.high[i] == null || quote.low[i] == null || quote.close[i] == null || quote.volume[i] == null) {
ignoredEntries++;
} else {
newData.push({ DT: dataSeries.timestamp[i] * 1000, open: quote.open[i], high: quote.high[i], low: quote.low[i], close: quote.close[i], volume: quote.volume[i] });
}
}
if (ignoredEntries > 0) {
//console.warn("Ignored " + ignoredEntries.toString() + " entries (" + newData.length.toString() + " OK) with null values for " + Instrument.Symbol);
}
if (newData.length > 0) {
lastSuccessfulFetch = new Date();
}
if (!Instrument.SingleDayData || Instrument.SingleDayData[0].DT != newData[0].DT || newData.length > Instrument.SingleDayData.length) {
Instrument.SingleDayData = newData;
createSingleDayChart(Instrument);
//updateHoldingsTable();
//updateAnalysisTable();
tablesUpdateTimings.updateNeeded = true;
}
Instrument.SingleDayOpenPrice = dataSeries.indicators.quote[0].open[0];
$('#divCurrentValue' + Instrument.DisplayOrder).html(getCurrentValueContent(Instrument));
if (Instrument.InstrumentType == "CURRENCY") {
Currencies.updateSymbol(Instrument.Symbol, Instrument.InstrumentName, Instrument.CurrentPrice);
}
} else {
//console.warn("No timestamp series received for symbol: " + Instrument.Symbol);
//logWarning("No timestamp series received for symbol: " + Instrument.Symbol);
logWarning({ MSG: "No timestamp series received for symbol: " + Instrument.Symbol, response: response });
}
} else {
//console.info("No data received for symbol: " + Instrument.Symbol);
//logInfo("No data received for symbol: " + Instrument.Symbol);
logWarning({ MSG: "No data received for symbol: " + Instrument.Symbol, response: response });
}
*/
},
failure: function (response) { console.error("getLseSingleDayData error:"); console.info(response); }
});
}
function submitNewDailyData(Instrument, NewData) {
function indexOfDT(DT) {
@ -2548,6 +2593,9 @@ function submitNewDailyData(Instrument, NewData) {
//Instrument.DailyData[x] = NewData[i];
} else {
//This is a new entry
if (!Instrument.DailyData) {
Instrument.DailyData = [];
}
Instrument.DailyData.push(NewData[i]);
if (NewData[i].DT <= currentMaxDT) {
resortRequired = true;
@ -2654,7 +2702,7 @@ function createHoldingsTable() {
for (let hn = 0; hn < i.Holdings.length; hn++) {
let h = i.Holdings[hn];
if (h.SoldDate == null) {
if (h.SoldDate == null && h.NoUnits != 0) {
aggUnits += h.NoUnits;
aggBookCost += h.NoUnits * h.PurchasePricePerUnit;
//aggBookCostGBP += h.NoUnits * h.PurchasePricePerUnit / exchangeRate;
@ -2695,6 +2743,7 @@ function createHoldingsTable() {
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],
@ -2777,7 +2826,7 @@ function createHoldingsTable() {
for (let n = 0; n < Instruments.Data.length; n++) {
let i = Instruments.Data[n];
//if (i.InstrumentType == 'EQUITY') {
if (i.InstrumentType == 'EQUITY' || i.InstrumentType == 'CRYPTOCURRENCY' || i.InstrumentType == 'CURRENCY') {
if (i.InstrumentType == 'EQUITY' || i.InstrumentType == 'ETF' || i.InstrumentType == 'CRYPTOCURRENCY' || i.InstrumentType == 'CURRENCY') {
//if (i.CurrentPrice) {
if (groupHoldings) {
let agg = aggregateHoldings(i);
@ -2843,6 +2892,7 @@ function createHoldingsTable() {
holdingNumber: hn,
instrumentType: i.InstrumentType,
instrumentName: i.InstrumentName,
displayName: i.DisplayName,
symbol: i.Symbol,
currency: i.Currency,
accountName: h.AccountName,
@ -3030,7 +3080,7 @@ function createHoldingsTable() {
let rowID = "holdingRow_" + h.displayOrder + "_" + h.holdingNumber;
let row = '<tr' + (altRow ? ' class="altShareRow"' : '') + ' id="' + rowID + '">' +
'<td class="' + openOrClosed + '">' + type + '</td>' +
'<td class="' + openOrClosed + '">' + h.instrumentName + '</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>' +
@ -3073,7 +3123,8 @@ function createHoldingsTable() {
'<td class="num">' + formatAmount(totalValueGBP, 'GBP', 0) + '</td>' + //Current Value
'<td>&nbsp;</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 / 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);
@ -3135,6 +3186,7 @@ function createHoldingsTable() {
//Update the daily holdings value table in the DB
if (totalValueGBP != lastTotalHoldingsValue) {
$.ajax({
type: "POST",
contentType: "application/json",
@ -3160,13 +3212,12 @@ function createHoldingsTable() {
$("#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) {
@ -3218,12 +3269,13 @@ function createAccountsTables() {
function addAccountHolding(instrument, exchangeRate, holding) {
let a = getAccount(holding.AccountName);
if (!a) {
a = { accountName: holding.AccountName, holdings: [], totalCostGBP: 0, totalValueGBP: 0, totalGainGBP: 0 };
a = { accountName: holding.AccountName, holdings: [], totalCostGBP: 0, totalValueGBP: 0, totalGainGBP: 0, totalCashGBP: 0 };
accounts.push(a);
}
let h = {
instrumentName: instrument.InstrumentName,
symbol: instrument.Symbol,
instrumentType: instrument.InstrumentType,
currency: instrument.Currency,
purchaseDate: holding.PurchaseDate,
purchasePricePerUnit: holding.PurchasePricePerUnit,
@ -3233,7 +3285,9 @@ function createAccountsTables() {
//if (exchangeRate) {
//h.costGBP = h.cost / exchangeRate;
h.costGBP = holding.BookCostGBP;
if (instrument.InstrumentType != 'CURRENCY') {
a.totalCostGBP += holding.BookCostGBP;
}
//}
if (instrument.CurrentPrice) {
h.currentPrice = instrument.CurrentPrice;
@ -3243,12 +3297,19 @@ function createAccountsTables() {
h.currentValueGBP = h.currentValue / exchangeRate;
//h.gainGBP = h.gain / exchangeRate;
h.gainGBP = h.currentValueGBP - h.costGBP;
if (instrument.InstrumentType != 'CURRENCY') {
a.totalGainGBP += h.gainGBP;
a.totalValueGBP += h.currentValueGBP;
}
}
}
if (instrument.InstrumentType == 'CURRENCY') {
//Don't add cash holdings - just add the cash value to the account
a.totalCashGBP += h.currentValueGBP;
} else {
a.holdings.push(h);
}
}
for (let ix = 0; ix < Instruments.Data.length; ix++) {
let i = Instruments.Data[ix];
@ -3266,9 +3327,6 @@ function createAccountsTables() {
let accounts = getAccounts();
//let eDT = new Date();
//console.info('createAccountsTable after getAccounts: ' + (eDT.getTime() - sDT.getTime()) + ' (' + sDT.yyyymmddhhmmss() + ' -> ' + eDT.yyyymmddhhmmss() + ')');
//Sort the accounts array by accountName
accounts.sort(function (a, b) {
if (a.accountName < b.accountName) return -1;
@ -3276,18 +3334,30 @@ function createAccountsTables() {
return 0;
});
//eDT = new Date();
//console.info('createAccountsTable after array sort: ' + (eDT.getTime() - sDT.getTime()) + ' (' + sDT.yyyymmddhhmmss() + ' -> ' + eDT.yyyymmddhhmmss() + ')');
//let summaryTable = "<table class='accounts' id='accountsSummaryTable'><thead><tr><th>Account Name</th><th>No. Holdings</th><th>Total Cost (GBP)</th><th>Current Value</th><th>Total Gain</th></tr></thead><tbody>";
let summaryTable = "<table class='accounts' id='accountsSummaryTable'><thead><tr><th>Account Name</th><th>No. Holdings</th><th>Total Cost (GBP)</th><th>Total Gains (GBP)</th><th>Total Losses (GBP)</th><th>Current Value</th><th>NET Gain</th></tr></thead><tbody>";
let summaryTable = "<table class='accounts' id='accountsSummaryTable'><thead><tr>" +
"<th>Account Name</th>" +
"<th>No. Holdings</th>" +
"<th>Total Cost (GBP)</th>" +
"<th>Total Gains (GBP)</th>" +
"<th>Total Losses (GBP)</th>" +
"<th>Current Investements Value</th>" +
"<th>NET Gain</th>" +
"<th>Cash Balance GBP</th>" +
"<th>Total Value GBP</th>" +
"</tr></thead><tbody>";
let accountsTables = ''
let tableIDs = ['accountsSummaryTable'];
let hideEmptyAccounts = $('#hideEmptyAccounts').prop('checked');
let altRow = 1;
for (let ax = 0; ax < accounts.length; ax++) {
let a = accounts[ax];
altRow = !altRow;
let accountGainsGBP = 0;
let accountLossesGBP = 0;
let accountValueGBP = 0;
let profitOrLoss = a.totalGainGBP < 0 ? 'loss' : 'profit';
if (a.holdings.length > 0) {
altRow = !altRow;
/*summaryTable += "<tr" + (altRow ? ' class="altShareRow"' : '') + "><td>" + a.accountName + "</td>" +
"<td>" + a.holdings.length + "</td>" +
"<td class='num'>" + formatAmount(a.totalCostGBP || 0, 'GBP', 2) + "</td>" +
@ -3315,9 +3385,9 @@ function createAccountsTables() {
'<th>Gain £</th>' +
'<th>Gain %</th>' +
'</tr></thead><tbody>';
let accountGainsGBP = 0;
let accountLossesGBP = 0;
let accountValueGBP = 0;
//let accountGainsGBP = 0;
//let accountLossesGBP = 0;
//let accountValueGBP = 0;
let accountAltRow = 1;
for (let hx = 0; hx < a.holdings.length; hx++) {
let h = a.holdings[hx];
@ -3371,8 +3441,10 @@ function createAccountsTables() {
'<td class="num">&nbsp;</td>' +
'</tr>';
accountsTables += '</tbody></table>';
}
//Add an entry to the summary table
if (a.totalValueGBP + a.totalCashGBP > 0 || !hideEmptyAccounts) {
summaryTable += "<tr" + (altRow ? ' class="altShareRow"' : '') + "><td>" + a.accountName + "</td>" +
"<td>" + a.holdings.length + "</td>" +
"<td class='num'>" + formatAmount(a.totalCostGBP || 0, 'GBP', 2) + "</td>" +
@ -3380,10 +3452,13 @@ function createAccountsTables() {
"<td class='num'>" + formatAmount(accountLossesGBP, 'GBP', 2) + "</td>" +
"<td class='num'>" + formatAmount(a.totalValueGBP || 0, 'GBP', 2) + "</td>" +
"<td class='num " + profitOrLoss + "'>" + formatAmount(a.totalGainGBP || 0, 'GBP', 2) + "</td>" +
"<td class='num'>" + formatAmount(a.totalCashGBP || 0, 'GBP', 2) + "</td>" +
"<td class='num'>" + formatAmount(a.totalValueGBP + a.totalCashGBP || 0, 'GBP', 2) + "</td>" +
"</tr>";
}
}
summaryTable += "</tbody></table>";
$("#accoountsDiv").html(summaryTable + '<br>' + accountsTables);
$("#accountsSummaryDiv").html(summaryTable + '<br>' + accountsTables);
}
function createAnalysisTable() {
function getHighestPriceBeforeDate(Instrument, maxDT) {
@ -3740,6 +3815,16 @@ function showModalDialog_AddHolding(symbol) {
dd += '</select>';
return dd;
}
function GetCurrencyDropdown() {
let dd = '<select id="newHoldingPurchaseCurrency"><option value="">Currency</option>';
for (let i = 0; i < Instruments.Data.length; i++) {
if (Instruments.Data[i].InstrumentType == 'CURRENCY') {
dd += '<option value="' + Instruments.Data[i].Symbol + '">' + Instruments.Data[i].DisplayName + '</option>';
}
}
dd += '</select>';
return dd;
}
//let symbol = $(span).attr('data-symbol');
let i = Instruments.GetSymbol(symbol);
@ -3753,7 +3838,9 @@ function showModalDialog_AddHolding(symbol) {
'<tr><th>Current price</th><td>' + (i.CurrentPrice ? formatAmount(i.CurrentPrice, i.Currency, 2) : '') + '</td></tr>' +
'<tr><th>Purchase price / unit</th><td><input type="text" id="newHoldingPrice" value="' + (i.CurrentPrice ? i.CurrentPrice.toString() : '') + '" /></td></tr>' +
'<tr><th>No units</th><td><input type="text" id="newHoldingNoUnits" /></td></tr>' +
'<tr><th>Total cost</th><td><input type="text" id="newHoldingTotalCost" /></td></tr>' +
'<tr><th>Total cost in instrument currency</th><td><input type="text" id="newHoldingTotalCost" /></td></tr>' +
'<tr><th>Purchase Currency</th><td>' + GetCurrencyDropdown() + '</td></tr>>' +
'<tr><th>Book cost in purchase currency</th><td><input type="text" id="newHoldingBookCost" /></td></tr>' +
'<tr><th>Purchase date</th><td><input type="text" id="newHoldingDate" value="' + new Date().yyyymmdd() + '" />' + GetTimeDropdowns() + '</td></tr>' +
'</table></div><br>' +
'<div><span id="newHoldingOKButton" class="ok-button-disabled">OK</span></div>';
@ -3803,6 +3890,12 @@ function showModalDialog_AddHolding(symbol) {
$("#addHoldingTimeMinute").bind('input', function () {
ValidateNewHolding(symbol);
});
$("#newHoldingPurchaseCurrency").bind('input', function () {
ValidateNewHolding(symbol);
});
$("#newHoldingBookCost").bind('input', function () {
ValidateNewHolding(symbol);
});
$("#addHoldingAccountName").bind('input', function () {
ValidateNewHolding(symbol);
});
@ -3821,16 +3914,18 @@ function ValidateNewHolding(symbol) {
let purchaseDate = $("#newHoldingDate").val();
let purchaseHour = $("#addHoldingTimeHour").val();
let purchaseMinute = $("#addHoldingTimeMinute").val();
let purchaseCurrency = $("#newHoldingPurchaseCurrency").val();
let bookCost = $("#newHoldingBookCost").val();
if (accountName != '' && accountName != 'Select account...' && noUnits > 0 && price > 0 && purchaseDate != '' && purchaseHour != '' && purchaseHour != 'Hour' && purchaseMinute != '' && purchaseMinute != 'Min') {
if (accountName != '' && accountName != 'Select account...' && noUnits > 0 && price > 0 && purchaseDate != '' && purchaseHour != '' && purchaseHour != 'Hour' && purchaseMinute != '' && purchaseMinute != 'Min' && purchaseCurrency != 'Currency' && bookCost > 0) {
let purchaseDT = new Date(purchaseDate + ' ' + purchaseHour + ':' + purchaseMinute);
if (purchaseDT <= new Date()) {
$("#newHoldingOKButton").removeClass("ok-button-disabled").addClass("ok-button").unbind().bind('click', function () { $("#modalDialog").css("display", "none"); AddHolding(accountName, symbol, noUnits, price, purchaseDT) });
$("#newHoldingOKButton").removeClass("ok-button-disabled").addClass("ok-button").unbind().bind('click', function () { $("#modalDialog").css("display", "none"); AddHolding(accountName, symbol, noUnits, price, purchaseDT, purchaseCurrency, bookCost) });
}
}
} catch (err) { }
}
function AddHolding (accountName, symbol, noUnits, purchasePricePerUnit, purchaseDate) {
function AddHolding(accountName, symbol, noUnits, purchasePricePerUnit, purchaseDate, purchaseCurrency, bookCost) {
console.info("AddHolding: " + accountName + ", " + symbol + ", " + noUnits.toString() + ", " + purchasePricePerUnit.toFixed(4) + ", " + new Date(purchaseDate).yyyymmdd());
$.ajax({
@ -3838,7 +3933,7 @@ function AddHolding (accountName, symbol, noUnits, purchasePricePerUnit, purchas
contentType: "application/json",
url: "SharePrices.aspx/AddHolding",
dataType: "json",
data: JSON.stringify({ AccountName: accountName, Symbol: symbol, NoUnits: noUnits, PurchasePricePerUnit: purchasePricePerUnit, PurchaseDate: new Date(purchaseDate).getTime() }),
data: JSON.stringify({ AccountName: accountName, Symbol: symbol, NoUnits: noUnits, PurchasePricePerUnit: purchasePricePerUnit, PurchaseDate: new Date(purchaseDate).getTime(), PurchaseCurrency: purchaseCurrency, BookCostInPurchaseCurrency: bookCost }),
success: function (response) {
if (response.HoldingID) {
let i = Instruments.GetSymbol(symbol);
@ -3856,6 +3951,35 @@ function AddHolding (accountName, symbol, noUnits, purchasePricePerUnit, purchas
},
failure: function (response) { console.error("addInstrument AJAX error: " + JSON.stringify(response)); }
});
//Update the account cash balance to reflect the purchase
let balance = 0 - bookCost;
let curr = Instruments.GetAccountCurrencyHolding(accountName, Instruments.GetSymbol(purchaseCurrency));
if (curr) {
balance += curr.NoUnits;
}
$.ajax({
type: "POST",
contentType: "application/json",
url: "SharePrices.aspx/SetAccountCashValue",
dataType: "json",
data: JSON.stringify({ AccountName: accountName, Currency: purchaseCurrency, Value: balance }),
success: function (response) {
console.info("SetAccountCashValue success: " + JSON.stringify(response));
let c = Instruments.GetSymbol(response.Symbol);
let ch = Instruments.GetAccountCurrencyHolding(response.AccountName, c);
if (ch) {
ch.NoUnits = response.NoUnits;
} else {
c.Holdings.push(response);
}
},
failure: function (response) { console.error("SetAccountCashValue error: " + JSON.stringify(response)); }
});
}
function showModalDialog_SoldHolding(holdingID) {
@ -3951,6 +4075,7 @@ function soldHolding(holdingID, soldDate, accountName, soldCurrency, soldProceed
h.SoldDate = soldDate;
//Mark the holding as sold in the database
$.ajax({
type: "POST",
contentType: "application/json",
@ -3973,6 +4098,7 @@ function soldHolding(holdingID, soldDate, accountName, soldCurrency, soldProceed
balance += curr.NoUnits;
}
//Update the account cash value to reflect the sale
$.ajax({
type: "POST",
contentType: "application/json",
@ -4137,6 +4263,18 @@ function refreshHistoryChart() {
lineWidth: 4*/
}],
xAxis: [{
lineColor: '#505050',
tickColor: '#505050',
labels: { style: { color: '#b0b0b0'/*, fontSize: 'x-small'*/ } }
}],
yAxis: [{
//title: { text: undefined },
gridLineColor: '#505050',
labels: { style: { color: '#b0b0b0'/*, fontSize: 'x-small'*/ } },
offset: 40
}],
plotOptions: {
ohlc: {
color: '#ff4141',
@ -4151,421 +4289,3 @@ function refreshHistoryChart() {
});
}
/*function createIndexMarquee() {
let t = [];
for (var ix = 0; ix < Instruments.Data.length; ix++) {
let i = Instruments.Data[ix];
//if (i.InstrumentType == 'INDEX' || i.InstrumentType == 'FUTURE') {
if (i.ShowInMarquee == 'A') {
let openOrClosed = Instruments.MarketIsOpen(i) == 1 ? 'open' : 'closed';
if (i.SingleDayPreviousClose) {
let percentChange = ((i.CurrentPrice / i.SingleDayPreviousClose) - 1) * 100;
if (percentChange < 0) {
t.push('<font class="' + openOrClosed + '">' + i.DisplayName + ': ' + i.CurrentPrice + ' </font><font class="loss">' + percentChange.toFixed(1) + '%</font>');
} else {
t.push('<font class="' + openOrClosed + '">' + i.DisplayName + ': ' + i.CurrentPrice + ' </font><font class="profit">+' + percentChange.toFixed(1) + '%</font>');
}
} else {
t.push('<font class="' + openOrClosed + '">' + i.DisplayName + ': ' + i.CurrentPrice + '</font>');
}
}
}
if (t.length > 0) {
t.push(new Date().yyyymmddhhmmss());
let newContent = t.join("&emsp;&emsp;");
indexMarquee.setContent(newContent);
}
}*/
/*
function zzz_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) {
aggUnits += h.NoUnits;
aggBookCost += h.NoUnits * h.PurchasePricePerUnit;
aggBookCostGBP += h.NoUnits * h.PurchasePricePerUnit / exchangeRate;
if (h.PurchaseDate < minPurchaseData) { minPurchaseData = h.PurchaseDate; }
if (h.PurchaseDate > maxPurchaseDate) { maxPurchaseDate = h.PurchaseDate; }
if (!accountNames.includes(h.AccountName)) { accountNames.push(h.AccountName); }
aggSingleDayGainGBP += ((i.CurrentPrice - (h.PurchaseDate > i.SingleDayStartDate ? h.PurchasePricePerUnit : i.SingleDayPreviousClose)) * h.NoUnits) / exchangeRate;
bookCostPerAccount.addCategoryAmount(h.AccountName, (h.NoUnits * h.PurchasePricePerUnit) / exchangeRate);
currentValuePerAccount.addCategoryAmount(h.AccountName, (h.NoUnits * i.CurrentPrice) / exchangeRate);
}
}
let aggPurchasePricePerUnit = aggBookCost / aggUnits;
let aggCurrentValue = aggUnits * (i.Currency == 'GBp' ? i.CurrentPrice / 100 : i.CurrentPrice);
let aggCurrentValueGBP = (aggUnits * i.CurrentPrice) / exchangeRate;
let aggGainGBP = ((i.CurrentPrice - aggPurchasePricePerUnit) * aggUnits) / exchangeRate;
//let aggSingleDayGainGBP = ((i.CurrentPrice - i.SingleDayPreviousClose) * aggUnits) / exchangeRate;
let aggSingleDayGainPercent = ((i.CurrentPrice / i.SingleDayPreviousClose) - 1) * 100;
//let aggSingleDayProfitOrLoss = i.CurrentPrice < i.SingleDayPreviousClose ? 'loss' : 'profit';
let aggSingleDayProfitOrLoss = aggSingleDayGainGBP < 0 ? 'loss' : 'profit';
return {
displayOrder: i.DisplayOrder,
holdingNumber: 0,
instrumentName: i.InstrumentName,
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,
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') });
}
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 = [];
for (let n = 0; n < Instruments.Data.length; n++) {
let i = Instruments.Data[n];
if (i.InstrumentType == 'EQUITY') {
if (i.CurrentPrice) {
if (groupHoldings) {
let agg = aggregateHoldings(i);
if (agg.noUnits > 0) {
r.push(agg);
if (agg.currentValueGBP > maxCurrentValueGBP) { maxCurrentValueGBP = agg.currentValueGBP };
holdingCurrencies.addHoldingCurrency(i.Currency, agg.noUnits * agg.purchasePricePerUnit);
holdingCurrenciesCurrentValue.addHoldingCurrency(i.Currency, agg.noUnits * i.CurrentPrice);
totalBookCostGBP += agg.bookCostGBP;
totalValueGBP += agg.currentValueGBP;
totalGainGBP += agg.gainGBP;
todaysGainGBP += (agg.marketIsOpen == 0 ? 0 : agg.singleDayGainGBP);
}
} else {
let marketIsOpen = Instruments.MarketIsOpen(i);
let exchangeRate = Instruments.GetExchangeRate(i.Currency, 'GBP');
for (let hn = 0; hn < i.Holdings.length; hn++) {
let h = i.Holdings[hn];
if (h.SoldDate == null) {
let currentHoldingValue = h.NoUnits * (i.Currency == 'GBp' ? i.CurrentPrice / 100 : i.CurrentPrice);
let holdingBookCost = h.NoUnits * h.PurchasePricePerUnit;
let holdingCurrentValueGBP = (h.NoUnits * i.CurrentPrice) / exchangeRate;
let holdingGainGBP = ((i.CurrentPrice - h.PurchasePricePerUnit) * h.NoUnits) / exchangeRate;
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,
instrumentName: i.InstrumentName,
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,
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);
holdingCurrenciesCurrentValue.addHoldingCurrency(i.Currency, h.NoUnits * i.CurrentPrice);
bookCostPerAccount.addCategoryAmount(h.AccountName, holdingBookCost / exchangeRate);
currentValuePerAccount.addCategoryAmount(h.AccountName, holdingCurrentValueGBP);
totalBookCostGBP += holdingBookCost / exchangeRate;
totalValueGBP += holdingCurrentValueGBP;
totalGainGBP += holdingGainGBP;
todaysGainGBP += (marketIsOpen == 0 ? 0 : singleDayGainGBP);
}
}
}
}
}
}
return {
totalBookCostGBP: totalBookCostGBP,
totalValueGBP: totalValueGBP,
totalGainGBP: totalGainGBP,
todaysGainGBP: todaysGainGBP,
maxCurrentValueGBP: maxCurrentValueGBP,
holdings: r,
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>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&apos;s<br>Gain £</th>' +
'<th>Today&apos;s<br>Gain %</th>' +
'</tr></thead><tbody id="tbMainHoldings"></tbody><tfoot id="tfMainHoldings"><tr><td colspan="17">&nbsp;</td></tr></tfoot></table>';
//$('#holdingsDiv').html(tbl);
$('#holdingsTableDiv').html(tbl);
$("#tblMainHoldings").tablesorter();
}
}
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;">&nbsp;</div>' +
'<div class="pcLabel" style="width:' + cellWidth + 'px">' + labelText + '</div>' +
'</td>';
}
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 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 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 + '">' + 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>' + new Date(h.purchaseDateMin).yyyymmdd() + '</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 + '">' + formatAmount(h.purchasePricePerUnit, h.currency, 2) + '</td>' + //Buy Price
'<td class="num ' + openOrClosed + '">' + formatAmount(h.currentPrice, h.currency, 2) + '</td>' + //Current Price //'<td class="num">' + formatAmount(i.CurrentPrice, i.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 + '">' + formatAmount(h.currentValue, h.currency == 'GBp' ? 'GBP' : h.currency, 0) + '</td>' + //Current Value
'<td class="num ' + profitOrLoss + '">' + formatAmount(h.gain, h.currency == 'GBp' ? 'GBP' : h.currency, 0) + '</td>' + //Gain
'<td class="num ' + profitOrLoss + '">' + formatAmount(h.gainGBP, 'GBP', 0) + '</td>' + //Gain £
'<td class="num ' + profitOrLoss + '">' + h.gainPercent.toFixed(1) + '%</td>' + //Gain %
'<td class="num ' + openOrClosed + '">' + formatAmount(h.currentValueGBP, 'GBP', 2) + '</td>' + //Current Value £
//'<td class="num">' + formatAmount(h.currentValueGBP / totalValueGBP * 100, '', 1) + '%</td>' + //Allocation
getPercentCell('#3f2d95', 70, scaledAllocation, allocationPercent.toFixed(1) + '%') + //Allocation
(h.marketIsOpen == 0 ? '<td/>' : '<td class="num ' + h.singleDayProfitOrLoss + '">' + formatAmount(h.singleDayGainGBP, 'GBP', 0) + '</td>') + //Today's Gain £
(h.marketIsOpen == 0 ? '<td/>' : '<td class="num ' + h.singleDayProfitOrLoss + '">' + h.singleDayGainPercent.toFixed(1) + '%</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 row = '<tr id="tfHoldingsTotals"><td id="holdingsLastUpdated" colspan="5">Updated: ' + new Date().yyyymmddhhmmss() + (lastSuccessfulFetch ? ' - Last Successful Fetch: ' + lastSuccessfulFetch.yyyymmddhhmmss() : '') + '</td><td></td><td></td>' +
'<td class="num">' + formatAmount(totalBookCostGBP, 'GBP', 2) + '</td>' +
'<td></td><td></td>' +
'<td class="num ' + profitOrLoss + '">' + formatAmount(totalGainGBP, 'GBP', 2) + '</td>' +
'<td class="num ' + profitOrLoss + '">' + formatAmount((((totalValueGBP / totalBookCostGBP) - 1) * 100), '', 1) + '%</td>' +
'<td class="num">' + formatAmount(totalValueGBP, 'GBP', 2) + '</td>' +
'<td>&nbsp;</td>' + //Allocation
'<td class="num ' + todaysProfitOrLoss + '">' + formatAmount(todaysGainGBP, 'GBP', 2) + '</td>' +
'<td class="num ' + todaysProfitOrLoss + '">' + formatAmount(((todaysGainGBP / totalBookCostGBP) * 100), '', 1) + '%</td>' +
'</tr>';
addTableRow("tfHoldingsTotals", row, "tfMainHoldings", false);
//Flash the last updated cell
$("#holdingsLastUpdated").addClass("highlighted");
//Remove the highlight from all rows/cells
$("#tblMainHoldings").find(".highlighted").removeClass("highlighted", 1200);
//Tell tablesorter that the table has been updated
var resort = true;
$("#tblMainHoldings").trigger("update", [resort]);
//Update the total holdings span in the site banner
$("#spnTotalHoldings").html("Total holdings: " + formatAmount(totalValueGBP, "GBP", 2));
//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: { 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);
}
}
*/