This commit is contained in:
@@ -24,4 +24,14 @@ export const availableSymbols: Record<string, AvailableSymbol> = {
|
|||||||
mappedSymbol: "bpi-destino-ppr-2050",
|
mappedSymbol: "bpi-destino-ppr-2050",
|
||||||
provider: ProviderType.BPI,
|
provider: ProviderType.BPI,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Financial times
|
||||||
|
"0P0001P80D-FI": {
|
||||||
|
mappedSymbol: "PTBG2EHM0006",
|
||||||
|
provider: ProviderType.FT,
|
||||||
|
},
|
||||||
|
lu0203975437: {
|
||||||
|
mappedSymbol: "lu0203975437",
|
||||||
|
provider: ProviderType.FT,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -18,6 +18,10 @@ const providers: Record<ProviderType, Provider> = {
|
|||||||
baseUrl:
|
baseUrl:
|
||||||
"https://www.deco.proteste.pt/investe/investimentos/fundos/${symbol}",
|
"https://www.deco.proteste.pt/investe/investimentos/fundos/${symbol}",
|
||||||
},
|
},
|
||||||
|
[ProviderType.FT]: {
|
||||||
|
type: ProviderType.FT,
|
||||||
|
baseUrl: "https://markets.ft.com/data/funds/tearsheet/summary?s=${symbol}",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
interface SymbolConfig {
|
interface SymbolConfig {
|
||||||
@@ -67,6 +71,8 @@ async function getLatestPrice(symbol: string) {
|
|||||||
return await scrapeBPI(page, symbol, config);
|
return await scrapeBPI(page, symbol, config);
|
||||||
case ProviderType.DECO:
|
case ProviderType.DECO:
|
||||||
return await scrapeDeco(page, symbol, config);
|
return await scrapeDeco(page, symbol, config);
|
||||||
|
case ProviderType.FT:
|
||||||
|
return await scrapeFT(page, symbol, config);
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unsupported type: ${config.type}`);
|
throw new Error(`Unsupported type: ${config.type}`);
|
||||||
}
|
}
|
||||||
@@ -155,6 +161,43 @@ async function scrapeBPI(page: Page, symbol: string, config: SymbolConfig) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function scrapeFT(page: Page, symbol: string, config: SymbolConfig) {
|
||||||
|
// Interact with the DOM to retrieve the price and date
|
||||||
|
const price = await page.evaluate(() => {
|
||||||
|
const spanTags = document.getElementsByTagName("span");
|
||||||
|
const priceSearchText = "Price (EUR)";
|
||||||
|
let priceElementFound;
|
||||||
|
|
||||||
|
for (let i = 0; i < spanTags.length; i++) {
|
||||||
|
if (spanTags[i].textContent.trim() == priceSearchText) {
|
||||||
|
priceElementFound = spanTags[i];
|
||||||
|
if (priceElementFound) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!priceElementFound) {
|
||||||
|
throw new Error("Could not find price element");
|
||||||
|
}
|
||||||
|
|
||||||
|
return priceElementFound?.nextSibling?.textContent;
|
||||||
|
});
|
||||||
|
|
||||||
|
const marketPrice = parseFloat(
|
||||||
|
price?.replace("€", "").trim().replace(",", ".") || "0",
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
symbol,
|
||||||
|
name: config.name,
|
||||||
|
currency: "EUR",
|
||||||
|
price: marketPrice,
|
||||||
|
// date in FT is not available, so we use the current date as a placeholder
|
||||||
|
// yyyy-mm-dd format
|
||||||
|
date: new Date().toISOString().split("T")[0],
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// API endpoint to get latest price for a symbol
|
// API endpoint to get latest price for a symbol
|
||||||
app.get("/price/:symbol", async (req, res) => {
|
app.get("/price/:symbol", async (req, res) => {
|
||||||
const { symbol } = req.params;
|
const { symbol } = req.params;
|
||||||
@@ -186,6 +229,7 @@ app.get("/", (req, res) => {
|
|||||||
health: "/health",
|
health: "/health",
|
||||||
"price-bpi": "/price/bpi/:symbol",
|
"price-bpi": "/price/bpi/:symbol",
|
||||||
"price-deco": "/price/deco/:symbol",
|
"price-deco": "/price/deco/:symbol",
|
||||||
|
"price-ft": "/price/ft/:symbol",
|
||||||
},
|
},
|
||||||
example: `/price/bpi/BPIDEST2040`,
|
example: `/price/bpi/BPIDEST2040`,
|
||||||
});
|
});
|
||||||
@@ -194,5 +238,6 @@ app.get("/", (req, res) => {
|
|||||||
// Start the server
|
// Start the server
|
||||||
app.listen(PORT, () => {
|
app.listen(PORT, () => {
|
||||||
console.log(`Server running on http://localhost:${PORT}`);
|
console.log(`Server running on http://localhost:${PORT}`);
|
||||||
|
console.log("Available symbols:", Object.keys(availableSymbols).join(", "));
|
||||||
console.log(`Example: http://localhost:${PORT}/price/bpi/BPIDEST2040`);
|
console.log(`Example: http://localhost:${PORT}/price/bpi/BPIDEST2040`);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
export enum ProviderType {
|
export enum ProviderType {
|
||||||
BPI = "bpi",
|
BPI = "bpi",
|
||||||
DECO = "deco",
|
DECO = "deco",
|
||||||
|
FT = "ft",
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Provider {
|
export interface Provider {
|
||||||
|
|||||||
Reference in New Issue
Block a user