You can directly query the API which that page utilises:
Code: Select all
EnableExplicit
#LBMA_API_ENDPOINT_JSON = "https://prices.lbma.org.uk/json/"
; Describes the price for a good in each of: USD ($), GBP (£), and EUR (€).
;
; N.B. Floating point values are iffy for currency work. Fine for storing the price of
; something, but definitely avoid math on balances, etc., given FP error.
Structure MultiCurrencyPrice
usd.d
gbp.d
eur.d
EndStructure
; Describes a snapshot of prices in various currencies associated with a given date for
; morning and afternoon fixes.
; Note that afternoon prices may be zeroed in cases where only the morning fix has been
; done.
Structure DailyPriceSnapshot
; Prices for the date's morning fix.
morning.MultiCurrencyPrice
; Prices for the date's afternoon fix.
afternoon.MultiCurrencyPrice
EndStructure
; Describes raw data as returned by the LBMA prices API.
Structure _PriceRawDataEntry
is_cms_locked.b
; Date in text form. YYYY-MM-DD.
d.s
; Price tuple: [USD, GBP, EUR]
v.d[3]
EndStructure
; Parses pricing data from the given JSON string consisting of price entries for either
; the morning or afternoon fix, storing results in the supplied map.
;
; @param JSONText: Raw JSON to be parsed as a list of daily price entries for either the
; morning or afternoon fix.
; @param IsMorning: Whether or not the data correlates to the morning fix. When #False it
; is assumed to be the afternoon fix.
; @param OutPrices: Map which should receive pricing data. Keys are YYYY-MM-DD formatted
; dates.
;
; @returns Returns #True when successful. Otherwise returns #False.
Procedure.i ParsePricingJsonTextToMap(JSONText.s, IsMorning.i, Map OutPrices.DailyPriceSnapshot())
Protected pricingJson = ParseJSON(#PB_Any, JSONText)
If Not pricingJson
ProcedureReturn #False
EndIf
NewList rawPricingData._PriceRawDataEntry()
ExtractJSONList(JSONValue(pricingJson), rawPricingData())
FreeJSON(pricingJson)
ForEach rawPricingData()
Protected *snapshot.DailyPriceSnapshot = OutPrices(rawPricingData()\d)
Protected *outPrice.MultiCurrencyPrice = @*snapshot\morning
If Not IsMorning
*outPrice = @*snapshot\afternoon
EndIf
*outPrice\usd = rawPricingData()\v[0]
*outPrice\gbp = rawPricingData()\v[1]
*outPrice\eur = rawPricingData()\v[2]
Next
ProcedureReturn #True
EndProcedure
; Fetches precious metal pricing data from the LBMA API.
;
; @param PreciousMetalName: Name of the metal for which pricing data should be fetched.
; Expected to be one of: "gold", "silver", "platinum", or "palladium".
; @param OutPrices: Map which should receive pricing data. Keys are YYYY-MM-DD formatted
; dates.
; @param RetainMap: Whether or not the output pricing map should not be cleared before
; inserting fetched data into it.
;
; @returns Returns #True when successful. Otherwise returns #False.
Procedure.i FetchPricingData(PreciousMetalName.s, Map OutPrices.DailyPriceSnapshot(), RetainMap.i = #False)
If Not RetainMap
ClearMap(OutPrices())
EndIf
; Normalise metal name to lowercase.
PreciousMetalName = LCase(PreciousMetalName)
; Fetch and parse AM data...
Protected *rawJsonData = ReceiveHTTPMemory(#LBMA_API_ENDPOINT_JSON + PreciousMetalName + "_am.json")
If Not *rawJsonData
DebuggerWarning("Failed to fetch AM data for metal: " + PreciousMetalName)
ProcedureReturn #False
EndIf
If Not ParsePricingJsonTextToMap(PeekS(*rawJsonData, MemorySize(*rawJsonData), #PB_UTF8 | #PB_ByteLength), #True, OutPrices())
DebuggerWarning("Failed to parse AM data for metal: " + PreciousMetalName)
FreeMemory(*rawJsonData)
ProcedureReturn #False
EndIf
FreeMemory(*rawJsonData)
; Fetch and parse PM data...
*rawJsonData = ReceiveHTTPMemory(#LBMA_API_ENDPOINT_JSON + PreciousMetalName + "_pm.json")
If Not *rawJsonData
DebuggerWarning("Failed to fetch PM data for metal: " + PreciousMetalName)
ProcedureReturn #False
EndIf
If Not ParsePricingJsonTextToMap(PeekS(*rawJsonData, MemorySize(*rawJsonData), #PB_UTF8 | #PB_ByteLength), #False, OutPrices())
DebuggerWarning("Failed to parse PM data for metal: " + PreciousMetalName)
FreeMemory(*rawJsonData)
ProcedureReturn #False
EndIf
FreeMemory(*rawJsonData)
ProcedureReturn #True
EndProcedure
NewMap prices.DailyPriceSnapshot()
Debug "Fetching gold price data..."
If FetchPricingData("gold", prices())
Debug "Fetch OK!"
Debug ""
Debug "Data for 2023-09-22:"
Debug " AM: " + StrD(prices("2023-09-22")\morning\eur, 2) + "€"
Debug " PM: " + StrD(prices("2023-09-22")\afternoon\eur, 2) + "€"
EndIf
I'm not entirely sure of your use case, so I just crammed everything into a Map.
This could definitely be improved by using a data structure better for dynamic ordered/time-series data, but without knowing exact requirements, I've spared this complexity.
Disclaimer: I've no idea if they impose rate-limiting on this API, but it seems plausible. Since they return a great deal of data with each request, it's best to consume with some restraint. You'll definitely want to store and read from a cache instead of hitting their backend, where possible.
You could send AM + PM requests (and requests for more metals) asynchronously rather than in order, so loading occurs a bit faster. I'd probably avoid that and only do background fetch when prices update, saving into SQLite or similar as a cache, just to hammer the API less.