Last active
July 4, 2024 06:20
-
-
Save andreasRedeker/51f35a841be868e7224da514381a2075 to your computer and use it in GitHub Desktop.
Parqet iOS Dividenden Widget
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Variables used by Scriptable. | |
// These must be at the very top of the file. Do not edit. | |
// icon-color: teal; icon-glyph: chart-bar; | |
// Script by Andreas Redeker <[email protected]> | |
let portfolioId; | |
const apiUrl = (portfolioId) => `https://api.parqet.com/v1/portfolios/${portfolioId}?timeframe=max&useInclude=true&include=xirr&include=dividend_growth&include=future_dividends`; | |
// Logos provided by Parqet: https://parqet.com/api | |
const imageUrl = (symbol) => `https://assets.parqet.com/logos/isin/${symbol}?format=png`; | |
const IMAGE_SIZE = 32; | |
const CORNER_RADIUS = 16; | |
const LIST_SPACING = 8; | |
let MAX_LIST_LENGTH = 8; | |
const HOLDING_FONT = Font.mediumSystemFont(16); | |
const DATE_FONT = Font.mediumSystemFont(10); | |
const DATE_COLOR = Color.dynamic(Color.gray(), Color.gray()); | |
const PRICE_FONT = Font.mediumSystemFont(18); | |
const PRICE_COLOR = Color.dynamic(Color.green(), Color.green()); | |
if (config.runsInWidget && args.widgetParameter) { | |
portfolioId = args.widgetParameter; | |
} else if (config.runsInWidget && !args.widgetParameter) { | |
throw Error("Bitte Portfolio-Id in den Widget Parametern einfügen"); | |
} | |
if (config.runsInApp) { | |
portfolioId = "5f55425d139fc90007978e75"; // parqet demo portfolio | |
} | |
let widget = await createWidget(); | |
if (config.runsInApp) { | |
await widget.presentLarge(); | |
} | |
async function createWidget() { | |
let widget = new ListWidget(); | |
widget.url = 'parqetapp://'; | |
if (config.widgetFamily == "medium") { | |
MAX_LIST_LENGTH = 3; | |
} | |
if (config.widgetFamily == "large") { | |
MAX_LIST_LENGTH = 8; | |
} | |
// refresh data after 12 hours | |
widget.refreshAfterDate = new Date(Date.now() + 43200); | |
let portfolio = await loadPortfolio(portfolioId); | |
if (portfolio.length < 8) { | |
let title = widget.addText("Angekündigte Dividenden"); | |
title.font = Font.boldSystemFont(16); | |
title.color = Color.gray(); | |
widget.addSpacer(12) | |
} | |
let vStack = widget.addStack(); | |
vStack.layoutVertically(); | |
vStack.spacing = LIST_SPACING; | |
for (let holding of portfolio.slice(0, MAX_LIST_LENGTH)) { | |
let hStack = vStack.addStack(); | |
hStack.layoutHorizontally(); | |
const logo = hStack.addStack(); | |
logo.size = new Size(IMAGE_SIZE, IMAGE_SIZE); | |
logo.centerAlignContent(); | |
logo.cornerRadius = CORNER_RADIUS; | |
logo.borderWidth = 1; | |
logo.borderColor = Color.dynamic(Color.lightGray(), new Color("374151")); | |
logo.backgroundColor = Color.dynamic(Color.white(), new Color("374151")); | |
try { | |
const image = await loadImage(imageUrl(holding.security)); | |
let img = logo.addImage(image); | |
img.imageSize = new Size(IMAGE_SIZE, IMAGE_SIZE); | |
img.cornerRadius = CORNER_RADIUS; | |
} catch (err) { | |
let labelChar = logo.addText(holding.name[0]); | |
labelChar.font = Font.boldRoundedSystemFont(20); | |
labelChar.textColor = Color.dynamic(Color.white(), Color.white()); | |
} | |
hStack.addSpacer(8); | |
let nameDateStack = hStack.addStack(); | |
nameDateStack.layoutVertically(); | |
let holdingName = nameDateStack.addText(holding.name); | |
holdingName.font = HOLDING_FONT; | |
holdingName.lineLimit = 1; | |
let dividendDate = nameDateStack.addText(toLocaleDateStringShort(holding.date)); | |
dividendDate.font = DATE_FONT; | |
dividendDate.textColor = DATE_COLOR; | |
hStack.addSpacer(); | |
let total = hStack.addText(toLocaleCurrencyString(holding.total)); | |
total.font = PRICE_FONT; | |
total.textColor = PRICE_COLOR; | |
} | |
return widget; | |
} | |
async function loadPortfolio(portfolioId) { | |
let portfolio = await new Request(apiUrl(portfolioId)).loadJSON(); | |
if (portfolio.statusCode == 401) { | |
throw Error("Portfolio ist nicht öffentlich"); | |
} | |
let holdings = portfolio.holdings; | |
let futureDividendsRawData = portfolio.futureDividends; | |
let futureDividends = []; | |
for (let fd of futureDividendsRawData) { | |
let holdingInfo = holdings.find((holding) => holding.security == fd.security); | |
futureDividends.push({ | |
security: fd.security, | |
date: fd.date, | |
total: fd.grossAmount, | |
shares: fd.exShares, | |
price: fd.price, | |
name: fd.asset.name, | |
logo: holdingInfo.logo, | |
}); | |
} | |
futureDividends.sort((a, b) => a.date > b.date); | |
return futureDividends; | |
} | |
function toLocaleCurrencyString(value) { | |
return value.toLocaleString("de-DE", { style: "currency", currency: "EUR" }); | |
} | |
function toLocaleDateStringShort(date) { | |
const options = { day: '2-digit', month: '2-digit', year: 'numeric' }; | |
return new Date(date).toLocaleDateString("de-DE", options); | |
} | |
async function loadImage(imgUrl) { | |
const response = new Request(imgUrl); | |
return await response.loadImage(); | |
} | |
async function showAlert(message, actions) { | |
let alert = new Alert(); | |
alert.message = message; | |
for (let action of actions) { | |
alert.addAction(action); | |
} | |
let response = await alert.presentAlert(); | |
return response; | |
} | |
Script.setWidget(widget); | |
Script.complete(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
iOS Scriptable Dividenden Widget für Parqet
Dein Portfolio muss öffentlich sein und die absoluten Werte anzeigen, damit das Widget funktioniert.
Installation
Logos provided by Parqet