Skip to content

Instantly share code, notes, and snippets.

@icsAT
Last active October 23, 2021 21:02
Show Gist options
  • Save icsAT/200997f73a08d56d7b53d77edf463a47 to your computer and use it in GitHub Desktop.
Save icsAT/200997f73a08d56d7b53d77edf463a47 to your computer and use it in GitHub Desktop.
Crypto Portfolio & Activity Widget - Bitpanda Pro shows your portfolio, open orders and last transanctions in a Scriptable widget on your iPhone or iPad.
// Widget Basic Information
const basic = {
name: {
full: "Crypto Portfolio & Activity Widget"
,short: "CPAW"
}
,version: "v0.20"
,author: "icsAT"
,source: "https://gist.github.com/icsAT"
}
// Exchange Parameters
const exchangeModul = {
name: {
full: "Bitpanda Pro"
,short: "BPP"
}
,url: "https://api.exchange.bitpanda.com"
,version: "v1"
,timeout: "5000"
,apiKey: "Enter your Bitpanda Pro API Key here"
,methods: {
public: [ "market-ticker" ]
,private: [ "balances", "orders", "trades" ]
}
}
// Custom Parameters
const custom = {
debug: false
,referenceCurrency: "EUR"
,colorChangePercentage: 1.0
,header: {
font: "Menlo"
,fontSize: 12
}
,portfolio: {
nameLabel: "Name"
,nameWidth: 105
,volumeLabel: "Volume"
,volumenWidth: 105
,allocationLabel: "Alloc"
,allocationWidth: 45
,valueWidth: 45
,font: "Menlo"
,fontSize: 10
,maxLines: 13
,showSmallBalances: true
,smallBalancesLimit: 9.99
}
,orders: {
typeLabel: "Order Type"
,typeWidth: 70
,volumeLabel: "Volume"
,volumeWidth: 85
,priceLabel: "Price"
,priceWidth: 65
,valueLabel: "Value"
,valueWidth: 80
,font: "Menlo"
,fontSize: 10
,maxLines: 5
}
,trades: {
volumeLabel: "Volume"
,volumeWidth: 85
,priceLabel: "Price"
,priceWidth: 65
,valueLabel: "Value"
,valueWidth: 80
,feeLabel: "Fee"
,feeWidth: 70
,font: "Menlo"
,fontSize: 10
,maxLines: 3
}
,footer: {
font: "Menlo"
,fontSize: 12
}
}
// Currencies
const currencies = {
"1INCH": "1inch"
,"AAVE": "Aave"
,"ADA": "Cardano"
,"ALGO": "Algorand"
,"ANT": "Aragon"
,"ATOM": "Cosmos"
,"AVAX": "Avalanche"
,"AXS": "Axie Infinity Shard"
,"BAND": "Band Protocol"
,"BAT": "Basic Attention Token"
,"BCH": "Bitcoin Cash"
,"BEST": "Bitpanda Ecosystem Token"
,"BNB": "Binance Coin"
,"BTC": "Bitcoin"
,"BTT": "BitTorrent"
,"CAKE": "PancakeSwap"
,"CHZ": "Chiliz"
,"COMP": "Compound"
,"DASH": "Dash"
,"DGB": "DigiByte"
,"DOGE": "Doge"
,"DOT": "Polkadot"
,"EGLD": "Elrond"
,"EOS": "EOS"
,"ETC": "Ethereum Classic"
,"ETH": "Ethereum"
,"EUR": "Euro"
,"FIL": "Filecoin"
,"GRT": "The Graph"
,"ICP": "Internet Computer"
,"IOST": "IOST"
,"KLAY": "Klaytn"
,"KMD": "Komodo"
,"KNC": "Kyber Network"
,"KSM": "Kusama"
,"LINK": "Chainlink"
,"LSK": "Lisk"
,"LTC": "Litecoin"
,"LUNA": "Terra"
,"MANA": "Decentraland"
,"MATIC": "Polygon"
,"MIOTA": "IOTA"
,"MKR": "Maker"
,"NEO": "NEO"
,"OCEAN": "Ocean Protocol"
,"OMG": "OMG Network"
,"ONT": "Ontology"
,"PAN": "Pantos"
,"QTUM": "Qtum"
,"REN": "REN"
,"REP": "Augur v2"
,"SHIB": "SHIBA INU"
,"SNX": "Synthetix Network Token"
,"SOL": "Solana"
,"SUSHI": "SushiSwap"
,"THETA": "Theta Network"
,"TRX": "Tron"
,"UMA": "UMA"
,"UNI": "Uniswap"
,"USDC": "USD Coin"
,"USDT": "Tether"
,"VET": "Vechain"
,"WAVES": "Waves"
,"XEM": "NEM"
,"XLM": "Stellar"
,"XRP": "Ripple"
,"XTZ": "Tezos"
,"XYM": "Symbol"
,"YFI": "Yearn.Finance"
,"ZEC": "ZCash"
,"ZRX": "0x"
}
// Colors
const COLOR_RED = new Color('#FF0000')
const COLOR_GREEN = new Color('#008000')
const COLOR_BLACK = new Color('#000000')
const COLOR_WHITE = new Color('#FFFFFF')
const COLOR_GREY90 = new Color('#E6E6E6')
const COLOR_GREY70 = new Color('#B3B3B3')
const COLOR_GREY50 = new Color('#808080')
const COLOR_GREY30 = new Color('#4D4D4D')
const COLOR_GREY10 = new Color('#1a1a1a')
// running the script
config.widgetFamily = config.widgetFamily || 'large'
let widget = await createWidget()
if (config.runsInWidget) {
Script.setWidget(widget)
} else {
switch (config.widgetFamily) {
case 'small': await widget.presentSmall(); break;
case 'medium': await widget.presentMedium(); break;
case 'large': await widget.presentLarge(); break;
case 'extraLarge': await widget.presentExtraLarge(); break;
}
}
Script.complete()
// create the widget
async function createWidget() {
if (custom.debug) console.log("START function crateWidget()")
let portfolioData = await getPortfolioData()
if (custom.debug) console.log("portfolioData: " + JSON.stringify(portfolioData))
let openOrders = await getOpenOrders()
if (custom.debug) console.log("openOrders: " + JSON.stringify(openOrders))
let lastTransactions = await getLastTransactions()
if (custom.debug) console.log("lastTransactions: " + JSON.stringify(lastTransactions))
listWidget = new ListWidget()
listWidget.backgroundColor = Color.clear()
listWidget.centerAlignContent
let darkMode = !(Color.dynamic(Color.white(),Color.black()).red)
if (custom.debug) console.log("darkMode: " + darkMode)
if (config.widgetFamily == 'small') {
await smallTable(portfolioData, openOrders, lastTransactions)
}
if (config.widgetFamily == 'medium') {
await mediumTable(portfolioData, openOrders, lastTransactions)
}
if (config.widgetFamily == 'large') {
await largeTable(portfolioData, openOrders, lastTransactions)
}
if (config.widgetFamily == 'extraLarge') {
await extraLargeTable(portfolioData, openOrders, lastTransactions)
}
df = new DateFormatter()
df.dateFormat = "yyyy-MM-dd HH:mm"
lastUpdate = df.string(new Date())
stack.addSpacer(8)
footerLine = listWidget.addStack()
footerLine.addSpacer()
footerLineText = footerLine.addText("Last Update: " + lastUpdate)
footerLineText.font = Font.mediumSystemFont(8)
footerLineText.textColor = Color.dynamic(COLOR_GREY30, COLOR_GREY70)
footerLine.addSpacer()
if (custom.debug) console.log("END function crateWidget()")
return listWidget
}
// small widget
async function smallTable(portfolioData, openOrders, lastTransactions) {
errorLine = stack.addStack()
errorLine.layoutVertically()
errorText = errorLine.addText("Small widget is not set up yet. Please use a large widget!")
errorText.font = Font.boldSystemFont(15)
errorText.textColor = Color.red()
}
// medium widget
async function mediumTable(portfolioData, openOrders, lastTransactions) {
errorLine = stack.addStack()
errorLine.layoutVertically()
errorText = errorLine.addText("Medium widget is not set up yet. Please use a large widget!")
errorText.font = Font.boldSystemFont(15)
errorText.textColor = Color.red()
}
// large widget
async function largeTable(portfolioData, openOrders, lastTransactions) {
headLine = listWidget.addStack()
headLine.centerAlignContent
headLine.addSpacer()
headLineText = headLine.addText(basic.name.full + " - " + exchangeModul.name.full)
// headLineText = headLine.addText(basic.name.short + "-" + exchangeModul.name.short)
headLineText.font = new Font(custom.header.font, custom.header.fontSize)
headLineText.textColor = Color.dynamic(Color.black(), Color.white())
headLineText.centerAlignText()
headLine.addSpacer()
portfolioLine = listWidget.addStack()
portfolioLine.centerAlignContent
portfolioLine.addSpacer()
portfolioLineText = portfolioLine.addText(portfolioData.portfolioValue + " " + custom.referenceCurrency + " (" + portfolioData.portfolioChange + " " + custom.referenceCurrency + " / " + portfolioData.portfolioChangePercentage + " %)")
portfolioLineText.font = new Font(custom.header.font, custom.header.fontSize)
portfolioLineText.textColor = getTextColor(portfolioData.portfolioChangePercentage)
portfolioLineText.centerAlignText()
portfolioLine.addSpacer()
stack = listWidget.addStack()
stack.layoutVertically()
stack.centerAlignContent
stack.addSpacer(8)
SectionLine = stack.addStack()
SectionLine.layoutHorizontally()
SectionLine.centerAlignContent()
SectionLine.backgroundColor = Color.dynamic(Color.black(), Color.white())
SectionLine.cornerRadius = 5
SectionLine.addSpacer()
SectionLineStack = SectionLine.addStack()
SectionLineStack.size = new Size(custom.portfolio.nameWidth,0)
nameText = SectionLineStack.addText(custom.portfolio.nameLabel)
nameText.font = new Font(custom.portfolio.font, custom.portfolio.fontSize, 10)
nameText.textColor = Color.dynamic(Color.white(), Color.black())
nameText.leftAlignText()
SectionLineStack.addSpacer()
SectionLineStack = SectionLine.addStack()
SectionLineStack.size = new Size(custom.portfolio.volumenWidth,0)
SectionLineStack.addSpacer()
volumeText = SectionLineStack.addText(custom.portfolio.volumeLabel)
volumeText.font = new Font(custom.portfolio.font, custom.portfolio.fontSize)
volumeText.textColor = Color.dynamic(Color.white(), Color.black())
volumeText.rightAlignText()
SectionLineStack = SectionLine.addStack()
SectionLineStack.size = new Size(custom.portfolio.allocationWidth,0)
SectionLineStack.addSpacer()
allocationText = SectionLineStack.addText(custom.portfolio.allocationLabel)
allocationText.font = new Font(custom.portfolio.font, custom.portfolio.fontSize)
allocationText.textColor = Color.dynamic(Color.white(), Color.black())
allocationText.rightAlignText()
SectionLineStack = SectionLine.addStack()
SectionLineStack.size = new Size(custom.portfolio.valueWidth,0)
SectionLineStack.addSpacer()
valueText = SectionLineStack.addText(custom.referenceCurrency)
valueText.font = new Font(custom.portfolio.font, custom.portfolio.fontSize)
valueText.textColor = Color.dynamic(Color.white(), Color.black())
valueText.rightAlignText()
SectionLine.addSpacer()
lineCount = 1
for (coin of portfolioData.portfolio) {
if (custom.debug) console.log("coin: " + coin)
if (custom.debug) console.log("lineCount: " + lineCount)
if (custom.debug) console.log("custom.portfolio.maxLines: " + custom.portfolio.maxLines)
if (custom.portfolio.maxLines >= lineCount && (custom.portfolio.showSmallBalances || (parseFloat(coin.allocation) > 0.00 && parseFloat(coin.value) >= custom.portfolio.smallBalancesLimit))) {
coinLine = stack.addStack()
coinLine.layoutHorizontally()
coinLine.centerAlignContent()
coinLine.addSpacer()
if (lineCount % 2 == 0) {
coinLine.backgroundColor = Color.dynamic(COLOR_GREY90, COLOR_GREY10)
}
coinLineStack = coinLine.addStack()
coinLineStack.size = new Size(custom.portfolio.nameWidth,0)
nameText = coinLineStack.addText(coin.coinName)
nameText.font = new Font(custom.portfolio.font, custom.portfolio.fontSize)
nameText.leftAlignText()
coinLineStack.addSpacer()
coinLineStack = coinLine.addStack()
coinLineStack.size = new Size(custom.portfolio.volumenWidth,0)
coinLineStack.addSpacer()
volumeText = coinLineStack.addText(coin.volume + " " + coin.coin)
volumeText.font = new Font(custom.portfolio.font, custom.portfolio.fontSize)
volumeText.rightAlignText()
coinLineStack = coinLine.addStack()
coinLineStack.size = new Size(custom.portfolio.allocationWidth,0)
coinLineStack.addSpacer()
allocationText = coinLineStack.addText(coin.allocation)
allocationText.font = new Font(custom.portfolio.font, custom.portfolio.fontSize)
allocationText.rightAlignText()
coinLineStack = coinLine.addStack()
coinLineStack.size = new Size(custom.portfolio.valueWidth,0)
coinLineStack.addSpacer()
valueText = coinLineStack.addText(coin.value)
valueText.font = new Font(custom.portfolio.font, custom.portfolio.fontSize)
valueText.textColor = getTextColor(coin.priceChangePercentage)
valueText.rightAlignText()
coinLine.addSpacer()
lineCount++
}
}
stack = listWidget.addStack()
stack.layoutVertically()
stack.centerAlignContent
SectionLine = stack.addStack()
SectionLine.layoutHorizontally()
SectionLine.centerAlignContent()
SectionLine.backgroundColor = Color.dynamic(Color.black(), Color.white())
SectionLine.cornerRadius = 5
SectionLine.addSpacer()
SectionLineStack = SectionLine.addStack()
SectionLineStack.size = new Size(custom.orders.typeWidth,0)
typeText = SectionLineStack.addText(custom.orders.typeLabel)
typeText.font = new Font(custom.orders.font, custom.orders.fontSize)
typeText.textColor = Color.dynamic(Color.white(), Color.black())
typeText.leftAlignText()
SectionLineStack.addSpacer()
SectionLineStack = SectionLine.addStack()
SectionLineStack.size = new Size(custom.orders.volumeWidth,0)
SectionLineStack.addSpacer()
volumeText = SectionLineStack.addText(custom.orders.volumeLabel)
volumeText.font = new Font(custom.orders.font, custom.orders.fontSize)
volumeText.textColor = Color.dynamic(Color.white(), Color.black())
volumeText.rightAlignText()
SectionLineStack = SectionLine.addStack()
SectionLineStack.size = new Size(custom.orders.priceWidth,0)
SectionLineStack.addSpacer()
priceText = SectionLineStack.addText(custom.orders.priceLabel)
priceText.font = new Font(custom.orders.font, custom.orders.fontSize)
priceText.textColor = Color.dynamic(Color.white(), Color.black())
priceText.rightAlignText()
SectionLineStack = SectionLine.addStack()
SectionLineStack.size = new Size(custom.orders.valueWidth,0)
SectionLineStack.addSpacer()
valueText = SectionLineStack.addText(custom.orders.valueLabel)
valueText.font = new Font(custom.orders.font, custom.orders.fontSize)
valueText.textColor = Color.dynamic(Color.white(), Color.black())
valueText.rightAlignText()
SectionLine.addSpacer()
lineCount = 1
for (order of openOrders) {
if (custom.debug) console.log("order: " + order)
if (custom.debug) console.log("lineCount: " + lineCount)
if (custom.debug) console.log("custom.orderss.maxLines: " + custom.orders.maxLines)
if (custom.orders.maxLines >= lineCount) {
orderLine = stack.addStack()
orderLine.layoutHorizontally()
orderLine.centerAlignContent()
orderLine.addSpacer()
if (lineCount % 2 == 0) {
orderLine.backgroundColor = Color.dynamic(COLOR_GREY90, COLOR_GREY10)
}
orderLineStack = orderLine.addStack()
orderLineStack.size = new Size(custom.orders.typeWidth,0)
typeText = orderLineStack.addText(order.side + " " + order.type)
typeText.font = new Font(custom.orders.font, custom.orders.fontSize)
typeText.leftAlignText()
orderLineStack.addSpacer()
orderLineStack = orderLine.addStack()
orderLineStack.size = new Size(custom.orders.volumeWidth,0)
orderLineStack.addSpacer()
volumeText = orderLineStack.addText(order.volume + " " + order.volumeCurrency)
volumeText.font = new Font(custom.orders.font, custom.orders.fontSize)
volumeText.rightAlignText()
orderLineStack = orderLine.addStack()
orderLineStack.size = new Size(custom.orders.priceWidth,0)
orderLineStack.addSpacer()
priceText = orderLineStack.addText(order.price)
priceText.font = new Font(custom.orders.font, custom.orders.fontSize)
priceText.rightAlignText()
orderLineStack = orderLine.addStack()
orderLineStack.size = new Size(custom.orders.valueWidth,0)
orderLineStack.addSpacer()
valueText = orderLineStack.addText(order.value + " " + order.priceCurrency)
valueText.font = new Font(custom.orders.font, custom.orders.fontSize)
valueText.rightAlignText()
orderLine.addSpacer()
lineCount++
}
}
stack = listWidget.addStack()
stack.layoutVertically()
stack.centerAlignContent
SectionLine = stack.addStack()
SectionLine.layoutHorizontally()
SectionLine.centerAlignContent()
SectionLine.backgroundColor = Color.dynamic(Color.black(), Color.white())
SectionLine.cornerRadius = 5
SectionLine.addSpacer()
SectionLineStack = SectionLine.addStack()
SectionLineStack.size = new Size(custom.trades.volumeWidth,0)
volumeText = SectionLineStack.addText(custom.trades.volumeLabel)
volumeText.font = new Font(custom.trades.font, custom.trades.fontSize)
volumeText.textColor = Color.dynamic(Color.white(), Color.black())
volumeText.leftAlignText()
SectionLineStack.addSpacer()
SectionLineStack = SectionLine.addStack()
SectionLineStack.size = new Size(custom.trades.priceWidth,0)
SectionLineStack.addSpacer()
priceText = SectionLineStack.addText(custom.trades.priceLabel)
priceText.font = new Font(custom.trades.font, custom.trades.fontSize)
priceText.textColor = Color.dynamic(Color.white(), Color.black())
priceText.rightAlignText()
SectionLineStack = SectionLine.addStack()
SectionLineStack.size = new Size(custom.trades.valueWidth,0)
SectionLineStack.addSpacer()
valueText = SectionLineStack.addText(custom.trades.valueLabel)
valueText.font = new Font(custom.trades.font, custom.trades.fontSize)
valueText.textColor = Color.dynamic(Color.white(), Color.black())
valueText.rightAlignText()
SectionLineStack = SectionLine.addStack()
SectionLineStack.size = new Size(custom.trades.feeWidth,0)
SectionLineStack.addSpacer()
feeText = SectionLineStack.addText(custom.trades.feeLabel)
feeText.font = new Font(custom.trades.font, custom.trades.fontSize)
feeText.textColor = Color.dynamic(Color.white(), Color.black())
feeText.rightAlignText()
SectionLine.addSpacer()
lineCount = 1
for (trade of lastTransactions) {
if (custom.debug) console.log("trade: " + trade)
if (custom.debug) console.log("lineCount: " + lineCount)
if (custom.debug) console.log("custom.trades.maxLines: " + custom.trades.maxLines)
if (custom.trades.maxLines >= lineCount) {
tradeLine = stack.addStack()
tradeLine.layoutHorizontally()
tradeLine.centerAlignContent()
tradeLine.addSpacer()
if (lineCount % 2 == 0) {
tradeLine.backgroundColor = Color.dynamic(COLOR_GREY90, COLOR_GREY10)
}
tradeLineStack = tradeLine.addStack()
tradeLineStack.size = new Size(custom.trades.volumeWidth,0)
typeText = tradeLineStack.addText(trade.volume + " " + trade.volumeCurrency)
typeText.font = new Font(custom.trades.font, custom.trades.fontSize)
typeText.leftAlignText()
tradeLineStack.addSpacer()
tradeLineStack = tradeLine.addStack()
tradeLineStack.size = new Size(custom.trades.priceWidth,0)
tradeLineStack.addSpacer()
allocationText = tradeLineStack.addText(trade.price)
allocationText.font = new Font(custom.trades.font, custom.trades.fontSize)
allocationText.rightAlignText()
tradeLineStack = tradeLine.addStack()
tradeLineStack.size = new Size(custom.trades.valueWidth,0)
tradeLineStack.addSpacer()
allocationText = tradeLineStack.addText(trade.value + " " + trade.priceCurrency)
allocationText.font = new Font(custom.trades.font, custom.trades.fontSize)
allocationText.rightAlignText()
tradeLineStack = tradeLine.addStack()
tradeLineStack.size = new Size(custom.trades.feeWidth,0)
tradeLineStack.addSpacer()
valueText = tradeLineStack.addText(trade.fee + " " + trade.feeCurrency)
valueText.font = new Font(custom.trades.font, custom.trades.fontSize)
valueText.rightAlignText()
tradeLine.addSpacer()
lineCount++
}
}
}
// extraLarge widget
async function extraLargeTable(portfolioData, openOrders, lastTransactions) {
errorLine = stack.addStack()
errorLine.layoutVertically()
errorText = errorLine.addText("Extra Large widget is not set up yet. Please use a large widget!")
errorText.font = Font.boldSystemFont(15)
errorText.textColor = Color.red()
}
// portfolio
async function getPortfolioData() {
if (custom.debug) console.log("START function getPortfolioData()")
let portfolio = []
let portfolioValue = 0.00
let portfolioChange = 0.00
let portfolioData = await apiRequest("balances")
if (custom.debug) console.log("portfolioData: " + JSON.stringify(portfolioData))
if (custom.debug) console.log("portfolioData.balances.length: " + portfolioData.balances.length)
for (let balance of portfolioData.balances) {
if (custom.debug) console.log("balance: " + JSON.stringify(balance))
if (custom.debug) console.log("balance.locked: " + balance.locked)
if (custom.debug) console.log("balance.available: " + balance.available)
if (custom.debug) console.log("balance.currency_code: " + balance.currency_code)
let marketPrice = { }
if (balance.currency_code != custom.referenceCurrency) {
marketPrice = await apiRequest("market-ticker", balance.currency_code + "_" + custom.referenceCurrency)
if (custom.debug) console.log("marketPrice: " + JSON.stringify(marketPrice))
if (custom.debug) console.log("marketPrice.last_price: " + marketPrice.last_price)
if (custom.debug) console.log("marketPrice.price_change_percentage: " + marketPrice.price_change_percentage)
} else {
marketPrice = {
last_price: 1.00
,price_change_percentage: 0.00
}
}
portfolio.push(
{
"coin": balance.currency_code
,"coinName": currencies[balance.currency_code]
,"volume": (parseFloat(balance.locked) + parseFloat(balance.available)).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 8 })
,"allocation": 0.00
,"price": parseFloat(marketPrice.last_price).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 8 })
,"priceCurrency": custom.referenceCurrency
,"priceChangePercentage": parseFloat(marketPrice.price_change_percentage).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })
,"value": ((parseFloat(balance.locked) + parseFloat(balance.available)) * parseFloat(marketPrice.last_price)).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })
}
)
portfolioValue = portfolioValue + (parseFloat(balance.locked) + parseFloat(balance.available)) * parseFloat(marketPrice.last_price)
if (custom.debug) console.log("portfolioValue: " + portfolioValue)
portfolioChange = portfolioChange + (((parseFloat(balance.locked) + parseFloat(balance.available)) * parseFloat(marketPrice.last_price)) - (((parseFloat(balance.locked) + parseFloat(balance.available)) * parseFloat(marketPrice.last_price)) / (100 + parseFloat(marketPrice.price_change_percentage)) * 100))
if (custom.debug) console.log("portfolioChange: " + portfolioChange)
portfolioChangePercentage = (100 / (parseFloat(portfolioValue) - parseFloat(portfolioChange)) * parseFloat(portfolioValue)) - 100
if (custom.debug) console.log("portfolioChangePercentage: " + portfolioChangePercentage)
}
if (custom.debug) console.log("portfolioValue: " + portfolioValue)
for (let coin of portfolio) {
coin.allocation = (100 / parseFloat(portfolioValue) * parseFloat(coin.value)).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })
}
portfolio.sort(function(a, b) {
if (custom.debug) console.log(a.coin + ":" + a.allocation + " > " + b.coin + ":" + b.allocation + "? return -1 (move towards top)")
if (custom.debug) console.log(a.coin + ":" + a.value + " > " + b.coin + ":" + b.value + "? return -1 (move towards top)")
if (parseFloat(a.value) > parseFloat(b.value)) return -1
if (custom.debug) console.log(a.coin + ":" + a.value + " < " + b.coin + ":" + b.value + "? return 1 (move towards end)")
if (parseFloat(a.value) < parseFloat(b.value)) return 1
if (parseFloat(a.allocation) > parseFloat(b.allocation)) return -1
if (custom.debug) console.log(a.coin + ":" + a.allocation + " < " + b.coin + ":" + b.allocation + "? return 1 (move towards end)")
if (parseFloat(a.allocation) < parseFloat(b.allocation)) return 1
if (custom.debug) console.log("return 0 (no change)")
return 0
})
if (custom.debug) console.log("END function getPortfolioData()")
return {
"portfolio": portfolio
,"portfolioValue": parseFloat(portfolioValue).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })
,"portfolioChange": parseFloat(portfolioChange).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })
,"portfolioChangePercentage": parseFloat(portfolioChangePercentage).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })
,"numberOfCoins": portfolio.length
}
}
// open orders
async function getOpenOrders() {
if (custom.debug) console.log("START function getOpenOrders()")
let orders = [ ]
let openOrders = await apiRequest("orders")
if (custom.debug) console.log("openOrders: " + JSON.stringify(openOrders))
if (custom.debug) console.log("openOrders.order_history.length: " + openOrders.order_history.length)
for (let order of openOrders.order_history) {
if (custom.debug) console.log("order.order: " + JSON.stringify(order.order))
if (custom.debug) console.log("order.order.volume: " + order.order.amount)
if (custom.debug) console.log("order.order.filled_volume: " + order.order.filled_amount)
if (custom.debug) console.log("order.order.side: " + order.order.side)
if (custom.debug) console.log("order.order.price: " + order.order.price)
if (custom.debug) console.log("order.order.type: " + order.order.type)
if (custom.debug) console.log("order.order.instrument_code: " + order.order.instrument_code)
let instrumentCodes = order.order.instrument_code.split("_")
orders.push(
{
"pair": order.order.instrument_code
,"type": order.order.type
,"side": order.order.side
,"volume": (parseFloat(order.order.amount) - parseFloat(order.order.filled_amount)).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 8 })
,"volumeCurrency": instrumentCodes[0]
,"price": parseFloat(order.order.price).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 8 })
,"priceCurrency": instrumentCodes[1]
,"value": ((parseFloat(order.order.amount) - parseFloat(order.order.filled_amount)) * parseFloat(order.order.price)).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })
}
)
}
if (custom.debug) console.log("END function getOpenOrders()")
return orders
}
// last transactions
async function getLastTransactions() {
if (custom.debug) console.log("START function getLastTransactions()")
let trades = [ ]
let lastTransactions = await apiRequest("trades", "max_page_size=" + custom.trades.maxLines)
if (custom.debug) console.log("lastTransactions: " + JSON.stringify(lastTransactions))
if (custom.debug) console.log("lastTransactions.trade_history.length: " + lastTransactions.trade_history.length)
for (let trade of lastTransactions.trade_history) {
if (custom.debug) console.log("trade.fee: " + JSON.stringify(trade.fee))
if (custom.debug) console.log("trade.trade: " + JSON.stringify(trade.trade))
if (custom.debug) console.log("trade.trade.volume: " + trade.trade.amount)
if (custom.debug) console.log("trade.trade.instrument_code: " + trade.trade.instrument_code)
if (custom.debug) console.log("trade.trade.price: " + trade.trade.price)
if (custom.debug) console.log("trade.trade.time: " + trade.trade.time)
if (custom.debug) console.log("trade.trade.side: " + trade.trade.side)
if (custom.debug) console.log("trade.fee.fee_currency: " + trade.fee.fee_currency)
if (custom.debug) console.log("trade.fee.fee_volume: " + trade.fee.fee_amount)
let instrumentCodes = trade.trade.instrument_code.split("_")
trades.push(
{
"pair": trade.trade.instrument_code
,"volume": parseFloat(trade.trade.amount).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 8 })
,"volumeCurrency": instrumentCodes[0]
,"price": parseFloat(trade.trade.price).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 8 })
,"priceCurrency": instrumentCodes[1]
,"value": (parseFloat(trade.trade.amount) * parseFloat(trade.trade.price)).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })
,"fee": parseFloat(trade.fee.fee_amount).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })
,"feeCurrency": trade.fee.fee_currency
}
)
}
if (custom.debug) console.log("END function getLastTransactions()")
return trades
}
// Prpare for a public or a private API request
async function apiRequest(method, params) {
if (custom.debug) console.log("START function apiRequest(method, params)")
if (custom.debug) console.log("method: " + method)
if (custom.debug) console.log("params: " + params)
if (exchangeModul.methods.public.includes(method)) {
return await apiRequestPublic(method, params)
} else if (exchangeModul.methods.private.includes(method)) {
return await apiRequestPrivate(method, params)
} else {
console.error(method + "is not a valid API method.")
}
if (custom.debug) console.log("END function apiRequest(method, params)")
}
// Public API request
async function apiRequestPublic(method, params) {
if (custom.debug) console.log("START function apiRequestPublic(method, params)")
if (custom.debug) console.log("method: " + method)
if (params) console.log("params: " + params)
let path = '/public/' + exchangeModul.version + '/' + method
if (custom.debug) console.log("path: " + path)
let url = exchangeModul.url + path
if (params && params!=undefined) {
url = url + "/" + params
}
if (custom.debug) console.log("url: " + url )
let publicRequest = new Request(url)
let rawData = await publicRequest.loadJSON()
if (custom.debug) console.log("END function apiRequestPublic(method, params)")
return rawData
}
// Private API Request
async function apiRequestPrivate(method, params) {
if (custom.debug) console.log("START function apiRequestPrivate(method, params)")
if (custom.debug) console.log("method: " + method)
if (params) console.log("params: " + params)
let path = '/public/' + exchangeModul.version + '/account/' + method
if (custom.debug) console.log("path: " + path)
let url = exchangeModul.url + path
if (params && params!=undefined) {
url = url + "?" + params
}
if (custom.debug) console.log("url: " + url)
let header = {
"Accept": "application/json"
,"Authorization": "Bearer " + exchangeModul.apiKey
}
if (custom.debug) console.log("header: " + JSON.stringify(header))
let privateRequest = new Request(url)
privateRequest.headers = header
let rawData = await privateRequest.loadJSON()
if (custom.debug) console.log("END function apiRequestPrivate(method, params)")
return rawData
}
// text color
function getTextColor(value) {
if (custom.debug) console.log("START function getTextColor(" + value + ")")
let textColor = Color.dynamic(Color.black(), Color.white())
if (parseFloat(value) >= parseFloat(custom.colorChangePercentage)) {
textColor = Color.green()
} else if ((parseFloat(value) * -1) >= parseFloat(custom.colorChangePercentage)) {
textColor = Color.red()
}
if (custom.debug) console.log("textColor: "+ textColor)
if (custom.debug) console.log("END function getTextColor(" + value + ")")
return textColor
}
@icsAT
Copy link
Author

icsAT commented Oct 23, 2021

First off all you have to get your own API key and put it in the code.
!!! Be carefull with your API-Key. This Key gives access to your Bitpanda Pro account !!!
This script only uses reading methods. Make sure to to create and ues a API Key with only reading permission for this widget.

grafik

@icsAT
Copy link
Author

icsAT commented Oct 23, 2021

You may set your reference currency and the percentage of change when the values will turn red or green.
You also may set font and fontsize fpr the different parts (header, portfolio, open orders, trades and footer) of the widget.
In the main parts (portfolio, open orders and trades) you are also able to set the text of the section headlines, the width of each column and the maximun number of lines in this section.
For the portfolio section you can also decide wether to show small balances (true) or not (false) and also the limit for small balances.

grafik

@icsAT
Copy link
Author

icsAT commented Oct 23, 2021

Version 0.20 (10/23/2021)

  • This is a very first testing version, wich has no error handling and is not using cache so far. Feel free to try if this works for you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment