ynab_investment_tracking
Version:
Node module to keep track of investments value in YNAB. You Need Investment Tracking.
120 lines • 6.7 kB
JavaScript
;
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