UNPKG

ynab_investment_tracking

Version:

Node module to keep track of investments value in YNAB. You Need Investment Tracking.

120 lines 6.7 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.run = void 0; const ynab_1 = require("ynab"); const yahoo_finance2_1 = __importDefault(require("yahoo-finance2")); var ClearedEnum = ynab_1.SaveTransaction.ClearedEnum; var FlagColorEnum = ynab_1.SaveTransaction.FlagColorEnum; const run = () => __awaiter(void 0, void 0, void 0, function* () { var _a, _b; console.log("TEST"); if (!((_a = process.env) === null || _a === void 0 ? void 0 : _a.YNAB_API_TOKEN) || !((_b = process.env) === null || _b === void 0 ? void 0 : _b.YNAB_BUDGET_ID)) { throw "Missing environment variables"; } else { const budgetID = process.env.YNAB_BUDGET_ID; const ynabAPI = new ynab_1.API(process.env.YNAB_API_TOKEN); console.log(`[INFO] Fetching budget...`); const { data: { budget } } = yield ynabAPI.budgets.getBudgetById(budgetID); console.log(`[INFO] Fetched.`); console.log(`[INFO] Fetching accounts...`); const { data: { accounts } } = yield ynabAPI.accounts.getAccounts(budget.id); console.log(`[INFO] Fetched ${accounts.length} accounts.`); const investmentAccounts = accounts.filter((account) => { var _a; return (_a = account === null || account === void 0 ? void 0 : account.note) === null || _a === void 0 ? void 0 : _a.includes("INVESTMENT_TO_TRACK"); }); console.log(`[INFO] ${investmentAccounts} investment accounts found.`); yield Promise.all(investmentAccounts.map(account => processAccount(ynabAPI, budgetID, account))); } }); exports.run = run; const processAccount = (ynabAPI, budgetID, account) => __awaiter(void 0, void 0, void 0, function* () { console.log(`[INFO] Processing account ${account.name}...`); console.log(`[INFO] Fetching transactions...`); const { data } = yield ynabAPI.transactions.getTransactionsByAccount(budgetID, account.id); console.log(`[INFO] Fetched ${data.transactions.length} transactions.`); const accountHoldings = buildAccountHoldingsFromTransactions(data); console.log(`[INFO] Current holdings:`); accountHoldings.forEach(({ count, value }, ticker) => { console.log(` [${ticker}] Shares: ${count} Value: $${value}`); }); console.log(`[INFO] Fetching quotes...`); const quotes = yield yahoo_finance2_1.default.quote(Array.from(accountHoldings.keys()), { fields: ["regularMarketPrice"], return: "map" }); console.log(`[INFO] Fetched quotes.`); const bulkTransactions = buildUpdateTransactionsFromQuotesAndHoldings(account, quotes, accountHoldings); if (bulkTransactions.transactions.length === 0) { console.log(`[INFO] No updates needed.`); } else { console.log(`[INFO] Updates:`); bulkTransactions.transactions.forEach((tx) => { console.log(` [${tx.payee_name}] $${tx.amount}`); }); console.log(`[INFO] Posting transactions to YNAB...`); yield ynabAPI.transactions.bulkCreateTransactions(budgetID, bulkTransactions); console.log(`[INFO] Posted transactions.`); } }); const buildAccountHoldingsFromTransactions = (data) => { const accountHoldings = new Map(); const transactions = data.transactions.map(t => { return Object.assign(Object.assign({}, t), { date: Date.parse(t.date) }); }); console.log(`[INFO] Processing transactions...`); transactions.forEach(t => { var _a, _b, _c; if ((t === null || t === void 0 ? void 0 : t.payee_name) && ((_a = t === null || t === void 0 ? void 0 : t.payee_name) === null || _a === void 0 ? void 0 : _a.startsWith("$"))) { const ticker = t.payee_name.replace("$", "").trim(); const holding = accountHoldings.get(ticker) || { count: 0, value: 0 }; holding.value = holding.value + t.amount; if ((t === null || t === void 0 ? void 0 : t.memo) && ((_b = t === null || t === void 0 ? void 0 : t.memo) === null || _b === void 0 ? void 0 : _b.startsWith("BUY"))) { holding.count = holding.count + +t.memo.split(" ")[1]; } if ((t === null || t === void 0 ? void 0 : t.memo) && ((_c = t === null || t === void 0 ? void 0 : t.memo) === null || _c === void 0 ? void 0 : _c.startsWith("SELL"))) { holding.count = holding.count - +t.memo.split(" ")[1]; } accountHoldings.set(ticker, holding); } }); console.log(`[INFO] Transactions processed.`); return accountHoldings; }; const buildUpdateTransactionsFromQuotesAndHoldings = (account, quotes, holdings) => { const bulkTransactions = { transactions: [] }; const currentDate = new Date(); const offset = currentDate.getTimezoneOffset(); holdings.forEach(({ count, value }, ticker) => { const quote = quotes.get(ticker); if (!(quote === null || quote === void 0 ? void 0 : quote.regularMarketPrice)) { console.log(`[WARNING] Failed to get quote for: ${ticker}`); } else { const difference = (quote.regularMarketPrice * count) - value; if (difference !== 0) { console.log(`[INFO] Creating update transaction for ${ticker} of $${difference} in ${account.name}`); bulkTransactions.transactions.push({ "account_id": account.id, "date": new Date(currentDate.getTime() - (offset * 60 * 1000)).toISOString().split('T')[0], "amount": difference, "payee_name": `$${ticker}`, "memo": "PRICE CHANGE", "cleared": ClearedEnum.Cleared, "approved": true, "flag_color": FlagColorEnum.Blue }); } } }); return bulkTransactions; }; //# sourceMappingURL=investmentTracker.js.map