From 5d44fa3a3048b9c7d2fcbbbf8d54e10f63729ad2 Mon Sep 17 00:00:00 2001 From: Alfie Day Date: Wed, 21 Aug 2024 17:49:21 +0100 Subject: [PATCH] Tracking Bitcoin Price (#5) --- README.md | 26 ++++++++++++++++++ sync-bitcoin.js | 71 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 sync-bitcoin.js diff --git a/README.md b/README.md index 5afe4c5..5826f29 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ This is a collection of useful scripts to help you manage your Actual Budget. - [Tracking Home Prices (Zillow's Zestimate)](#tracking-home-prices-zillows-zestimate) - [Tracking Car Prices (Kelley Blue Book)](#tracking-car-prices-kelley-blue-book) - [Tracking Investment Accounts](#tracking-investment-accounts) + - [Tracking Bitcoin Price](#tracking-bitcoin-price) ## Requirements @@ -50,6 +51,11 @@ INVESTMENT_CATEGORY_NAME="Investment" # optional, for logging into SimpleFIN SIMPLEFIN_CREDENTIALS="" + +# optional, for retrieving Bitcoin Price (these default to Kraken USD) +BITCOIN_PRICE_URL="https://api.kraken.com/0/public/Ticker?pair=xbtusd" +BITCOIN_PRICE_JSON_PATH="result.XXBTZUSD.c[0]" +BITCOIN_PAYEE_NAME="Bitcoin Price Change" ``` ## Installation @@ -252,3 +258,23 @@ node track-investments.js It is recommended to run this script once per month. +### Tracking Bitcoin Price + +This script tracks the value of Bitcoin. It adds new transactions to keep the +account balance equal to the latest value. There is one tag you can set in the +account notes, BTC:X, where X is the number of Bitcoin you own, eg `BTC:0.01` +You can optionally change the API endpoint used to retrieve the Bitcoin price, +an example for retrieving the price in GBP is: + +``` +BITCOIN_PRICE_URL="https://api.kraken.com/0/public/Ticker?pair=xbtgbp" +BITCOIN_PRICE_JSON_PATH=".result.XXBTZGBP.c[0]" +``` + +To run: + +```console +node sync-bitcoin.js +``` + +It is recommended to run this script once per day or week. diff --git a/sync-bitcoin.js b/sync-bitcoin.js new file mode 100644 index 0000000..1ab5a3e --- /dev/null +++ b/sync-bitcoin.js @@ -0,0 +1,71 @@ +const { closeBudget, openBudget, getTransactions, getAccountNote, getAccountBalance, ensurePayee } = require('./utils'); +const api = require('@actual-app/api'); + +function getValueAtPath(obj, path) { + const keys = path.split('.').filter(Boolean); + + return keys.reduce((acc, key) => { + const match = key.match(/^([^\[\]]+)(\[(\d+)\])?$/); + + if (match) { + const property = match[1]; + const index = match[3]; + + acc = acc[property]; + + if (index !== undefined) { + acc = acc[parseInt(index, 10)]; + } + } else { + acc = acc[key]; + } + + return acc; + }, obj); +} + +async function getBitcoinPrice() { + const url = process.env.BITCOIN_PRICE_URL || "https://api.kraken.com/0/public/Ticker?pair=xbtusd" + const path = process.env.BITCOIN_PRICE_JSON_PATH || "result.XXBTZUSD.c[0]" + try { + response = await fetch(url); + const json = await response.json(); + return getValueAtPath(json, path); + } catch (error) { + return undefined; + } +} + +(async () => { + const bitcoinPrice = await getBitcoinPrice(); + if (!bitcoinPrice) { + throw new Error("Unable to retrieve Bitcoin price. Check your BITCOIN_PRICE_URL and BITCOIN_PRICE_JSON_PATH environment variables"); + } + await openBudget(); + const payeeId = await ensurePayee(process.env.BITCOIN_PAYEE_NAME || 'Bitcoin Price Change'); + const accounts = await api.getAccounts(); + for (const account of accounts) { + if (account.closed) { + continue; + } + const note = await getAccountNote(account); + if (!note || note.indexOf("BTC:") === -1) { + continue; + } + const btc_amount = note.split('BTC:')[1].split(' ')[0]; + const currentBalance = await getAccountBalance(account); + const targetBalance = Math.round(bitcoinPrice * btc_amount * 100); + const diff = currentBalance - targetBalance; + if (diff != 0) { + await api.importTransactions(account.id, [{ + date: new Date(), + payee: payeeId, + amount: diff, + cleared: true, + reconciled: true, + notes: "Updated Bitcoin Price", + }]) + } + } + await closeBudget(); +})();