Last active
October 23, 2021 21:02
-
-
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.
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
// 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 | |
} |
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.
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
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.