' ========================================================================= ' StockFunctions.bas ' Collabora / LibreOffice Basic port of the ONLYOFFICE custom functions. ' ' UPDATED 2026-07-23: STOCKPRICE / STOCKTIME (Finnhub) removed. ' Finnhub was dropped because it has no historical price coverage, which is ' why stockproxy (NAS08) was built in the first place. FUNDPRICE already ' works for any ticker stockproxy knows about, fund or stock, so it now ' doubles as the equity price function. FUNDTIME replaces STOCKTIME and ' returns stockproxy's "resolved_date" field. ' ' NOTE: resolved_date is a DATE (YYYY-MM-DD), not a time-of-day timestamp. ' Finnhub's old STOCKTIME gave true time-of-day resolution; this does not. ' If intraday precision is ever needed, stockproxy's /current endpoint ' would need a real timestamp field added on the backend side. ' ' HOW TO INSTALL IN COLLABORA: ' 1. Open the sheet in Collabora (collabora.kingdezigns.com). ' 2. Tools > Macros > Edit Macros (opens the Basic IDE). ' 3. In the tree, find "" (NOT "My Macros" - ' you want this stored IN the document so it travels with the file). ' 4. Right-click "Standard" under your document > Insert > Module. ' 5. Paste this entire file's contents into the new module. ' (Or, if StockFunctions already exists, replace its contents.) ' 6. Fill in PROXY_SECRET() below with the real value if not already set. ' 7. Save the document (Ctrl+S). Macro security must allow this ' document's macros to run - if prompted, allow them. ' ' USAGE IN CELLS: ' =FUNDPRICE(D4) ' =FUNDPRICE_HIST(D4, TEXT($B$3,"yyyy-mm-dd")) ' =FUNDPRICE(A5) <- replaces old =STOCKPRICE(A5) ' =FUNDTIME(A5) <- replaces old =STOCKTIME(A5) ' ========================================================================= Option Explicit ' ---- Config ------------------------------------------------------------- Function PROXY_BASE() As String PROXY_BASE = "https://stocks.kingdezigns.com" End Function Function PROXY_SECRET() As String PROXY_SECRET = "90c2528e9b5221c110f7c2c9cd6c65dc" ' <-- from Vaultwarden End Function ' ---- Public spreadsheet functions --------------------------------------- Function FUNDPRICE(ticker As String) As Variant Dim sUrl As String, sJson As String, vPrice As Variant sUrl = PROXY_BASE() & "/current?ticker=" & EncodeUrl(ticker) & "&key=" & PROXY_SECRET() sJson = HttpGetText(sUrl) If sJson = "" Then FUNDPRICE = "#ERROR" Exit Function End If vPrice = JsonNumber(sJson, "price") If IsNull(vPrice) Then FUNDPRICE = "#N/A" Else FUNDPRICE = vPrice End If End Function Function FUNDPRICE_HIST(ticker As String, dateStr As String) As Variant Dim sUrl As String, sJson As String, vPrice As Variant sUrl = PROXY_BASE() & "/historical?ticker=" & EncodeUrl(ticker) & _ "&date=" & EncodeUrl(dateStr) & "&key=" & PROXY_SECRET() sJson = HttpGetText(sUrl) If sJson = "" Then FUNDPRICE_HIST = "#ERROR" Exit Function End If vPrice = JsonNumber(sJson, "price") If IsNull(vPrice) Then FUNDPRICE_HIST = "#N/A" Else FUNDPRICE_HIST = vPrice End If End Function ' Replaces old STOCKTIME(ticker). Returns stockproxy's resolved_date ' (YYYY-MM-DD) — a date, not a time-of-day timestamp. See note at top. Function FUNDTIME(ticker As String) As Variant Dim sUrl As String, sJson As String, vDate As Variant sUrl = PROXY_BASE() & "/current?ticker=" & EncodeUrl(ticker) & "&key=" & PROXY_SECRET() sJson = HttpGetText(sUrl) If sJson = "" Then FUNDTIME = "#ERROR" Exit Function End If vDate = JsonString(sJson, "resolved_date") If IsNull(vDate) Then FUNDTIME = "#N/A" Else FUNDTIME = vDate End If End Function ' ---- Internal helpers ---------------------------------------------------- ' Synchronous HTTP GET, returns response body as text, "" on failure. Function HttpGetText(sUrl As String) As String Dim oSFA As Object, oStream As Object, oTextStream As Object Dim sResult As String On Error GoTo ErrHandler oSFA = createUnoService("com.sun.star.ucb.SimpleFileAccess") oStream = oSFA.openFileRead(sUrl) oTextStream = createUnoService("com.sun.star.io.TextInputStream") oTextStream.setInputStream(oStream) oTextStream.setEncoding("UTF-8") sResult = "" Do While Not oTextStream.isEOF() sResult = sResult & oTextStream.readLine() & Chr(10) Loop oTextStream.closeInput() HttpGetText = sResult Exit Function ErrHandler: HttpGetText = "" End Function ' Pulls a numeric value out of a flat JSON string for a given key. ' e.g. JsonNumber("{""price"":18.37}", "price") -> 18.37 ' Returns Null if the key isn't found or has no numeric value. Function JsonNumber(sJson As String, sKey As String) As Variant Dim iPos As Integer, iStart As Integer, iEnd As Integer Dim sNum As String, cChar As String iPos = InStr(sJson, Chr(34) & sKey & Chr(34)) If iPos = 0 Then JsonNumber = Null Exit Function End If iPos = InStr(iPos, sJson, ":") If iPos = 0 Then JsonNumber = Null Exit Function End If iStart = iPos + 1 Do While iStart <= Len(sJson) And Mid(sJson, iStart, 1) = " " iStart = iStart + 1 Loop iEnd = iStart Do While iEnd <= Len(sJson) cChar = Mid(sJson, iEnd, 1) If (cChar >= "0" And cChar <= "9") Or cChar = "." Or cChar = "-" Then iEnd = iEnd + 1 Else Exit Do End If Loop sNum = Mid(sJson, iStart, iEnd - iStart) If Len(sNum) = 0 Then JsonNumber = Null Else JsonNumber = CDbl(sNum) End If End Function ' Pulls a quoted STRING value out of a flat JSON string for a given key. ' e.g. JsonString("{""resolved_date"":""2026-07-23""}", "resolved_date") ' -> "2026-07-23" ' Returns Null if the key isn't found or has no quoted value. Function JsonString(sJson As String, sKey As String) As Variant Dim iPos As Integer, iColon As Integer, iQuoteStart As Integer, iQuoteEnd As Integer iPos = InStr(sJson, Chr(34) & sKey & Chr(34)) If iPos = 0 Then JsonString = Null Exit Function End If iColon = InStr(iPos, sJson, ":") If iColon = 0 Then JsonString = Null Exit Function End If iQuoteStart = InStr(iColon, sJson, Chr(34)) If iQuoteStart = 0 Then JsonString = Null Exit Function End If iQuoteEnd = InStr(iQuoteStart + 1, sJson, Chr(34)) If iQuoteEnd = 0 Then JsonString = Null Exit Function End If JsonString = Mid(sJson, iQuoteStart + 1, iQuoteEnd - iQuoteStart - 1) End Function ' Minimal percent-encoder - sufficient for tickers and yyyy-mm-dd strings. Function EncodeUrl(s As String) As String Dim i As Integer, c As String, sOut As String sOut = "" For i = 1 To Len(s) c = Mid(s, i, 1) If (c >= "A" And c <= "Z") Or (c >= "a" And c <= "z") Or _ (c >= "0" And c <= "9") Or c = "-" Or c = "_" Or c = "." Or c = "~" Then sOut = sOut & c Else sOut = sOut & "%" & Right("0" & Hex(Asc(c)), 2) End If Next i EncodeUrl = sOut End Function Sub RefreshPriceSnapshot Dim oDoc As Object, oSheet As Object Dim oSrcRange As Object, oDestRange As Object Dim oCell As Object Dim i As Integer oDoc = ThisComponent oSheet = oDoc.Sheets.getByIndex(0) ' adjust index/name if needed ' Copy P3:Q53 -> R3:S53 as values only For i = 2 To 4 ' rows 3 to 53 (0-indexed: row 3 = index 2) oSheet.getCellByPosition(17, i).setValue(oSheet.getCellByPosition(15, i).getValue()) ' P->R (col P=15, R=17) oSheet.getCellByPosition(18, i).setValue(oSheet.getCellByPosition(16, i).getValue()) ' Q->S (col Q=16, S=18) Next i MsgBox "Price snapshot refreshed." End Sub