Files
market_screener/finance.js
T
Kazuma cd74497de6 refactor: restructure to clean architecture
fix: restore ScoringConfig improvements lost in refactor commit

docs: rewrite README and CLAUDE.md to reflect current architecture

code-format

code fixes
2026-06-03 01:36:21 -04:00

83 lines
3.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// finance.js — Personal Finance Entry Point
//
// Runs independently of the news screener.
// Fetches your accounts from SimpleFIN, screens your portfolio holdings,
// and generates a finance-report.html with:
//
// 1. Net worth + account overview (SimpleFIN)
// 2. Portfolio positions with hold/sell/add advice (screener + crypto prices)
// 3. Spending breakdown (SimpleFIN)
//
// Usage:
// npm run finance
import 'dotenv/config';
import { readFileSync, existsSync } from 'fs';
import { SimpleFINClient } from './src/finance/SimpleFINClient.js';
import { PersonalFinanceAnalyzer } from './src/finance/PersonalFinanceAnalyzer.js';
import { PortfolioAdvisor } from './src/finance/PortfolioAdvisor.js';
import { ScreenerEngine } from './src/core/engine/ScreenerEngine.js';
import { FinanceReporter } from './src/reporters/FinanceReporter.js';
async function main() {
// ── 1. Load portfolio ──────────────────────────────────────────────────────
if (!existsSync('./portfolio.json')) {
console.error('portfolio.json not found. Edit it with your holdings and re-run.');
process.exit(1);
}
const { holdings } = JSON.parse(readFileSync('./portfolio.json', 'utf8'));
const byType = holdings.reduce((acc, h) => {
const t = h.type ?? 'stock';
acc[t] = (acc[t] ?? 0) + 1;
return acc;
}, {});
console.log(
`📋 Portfolio: ${holdings.length} positions — ${Object.entries(byType)
.map(([t, n]) => `${n} ${t}`)
.join(', ')}\n`,
);
// ── 2. Fetch SimpleFIN data ────────────────────────────────────────────────
let personalFinance = null;
if (process.env.SIMPLEFIN_ACCESS_URL || process.env.SIMPLEFIN_SETUP_TOKEN) {
try {
process.stdout.write('💰 Fetching accounts from SimpleFIN...');
const client = new SimpleFINClient();
await client.init();
const { accounts } = await client.getAccounts();
personalFinance = new PersonalFinanceAnalyzer().analyse(accounts);
process.stdout.write(` ${accounts.length} accounts loaded\n`);
} catch (err) {
process.stdout.write(` skipped — ${err.message}\n`);
}
} else {
console.log(
' SimpleFIN not configured — add SIMPLEFIN_SETUP_TOKEN to .env for account data\n',
);
}
// ── 3. Screen stocks + ETFs (crypto handled separately by PortfolioAdvisor) ─
const screenableTickers = holdings
.filter((h) => (h.type ?? 'stock') !== 'crypto')
.map((h) => h.ticker.toUpperCase());
let results = { STOCK: [], ETF: [], BOND: [], ERROR: [], marketContext: {} };
if (screenableTickers.length > 0) {
process.stdout.write(`📊 Screening ${screenableTickers.length} stock/ETF positions...`);
results = await new ScreenerEngine().screenTickers(screenableTickers);
process.stdout.write(' done\n');
}
// ── 4. Fetch crypto prices + generate advice ───────────────────────────────
process.stdout.write('💡 Generating advice...');
const advice = await new PortfolioAdvisor().advise(holdings, results);
process.stdout.write(' done\n');
// ── 5. Generate report ─────────────────────────────────────────────────────
const reportPath = new FinanceReporter().generate(advice, personalFinance, results.marketContext);
console.log(`\n✅ Finance report saved to: ${reportPath}\n`);
}
main().catch((err) => console.error('Failed:', err.message));