ai: enhance enrich command for incremental updates
This commit is contained in:
parent
6e78818f1c
commit
370cccbfcb
2 changed files with 93 additions and 9 deletions
|
|
@ -402,10 +402,14 @@ Cash and CD lots are automatically classified as "Cash & CDs" without needing me
|
|||
Use the `enrich` command to generate a starting `metadata.srf` from Alpha Vantage company overview data:
|
||||
|
||||
```bash
|
||||
# Enrich an entire portfolio (generates full metadata.srf)
|
||||
zfin enrich portfolio.srf > metadata.srf
|
||||
|
||||
# Enrich a single symbol and append to existing metadata.srf
|
||||
zfin enrich SCHD >> metadata.srf
|
||||
```
|
||||
|
||||
This fetches sector, country, and market cap for each stock symbol and generates classification entries. CUSIPs are skipped (fill in manually). Review and edit the output -- particularly for ETFs and funds where the auto-classification may be too generic.
|
||||
When given a file path, it fetches all stock symbols and outputs a complete SRF file with headers. When given a symbol, it outputs just the classification lines (no header), so you can append directly with `>>`.
|
||||
|
||||
## Account metadata (accounts.srf)
|
||||
|
||||
|
|
@ -455,7 +459,7 @@ Commands:
|
|||
etf <SYMBOL> ETF profile (expense ratio, holdings, sectors)
|
||||
portfolio [FILE] Portfolio summary (default: portfolio.srf)
|
||||
analysis [FILE] Portfolio analysis breakdowns (default: portfolio.srf)
|
||||
enrich <FILE> Generate metadata.srf from Alpha Vantage
|
||||
enrich <FILE|SYMBOL> Generate metadata.srf from Alpha Vantage
|
||||
lookup <CUSIP> CUSIP to ticker lookup via OpenFIGI
|
||||
cache stats Show cached symbols
|
||||
cache clear Delete all cached data
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ const usage =
|
|||
\\ etf <SYMBOL> Show ETF profile (holdings, sectors, expense ratio)
|
||||
\\ portfolio [FILE] Load and analyze a portfolio (default: portfolio.srf)
|
||||
\\ analysis [FILE] Show portfolio analysis (default: portfolio.srf)
|
||||
\\ enrich <FILE|SYMBOL> Bootstrap metadata.srf from Alpha Vantage (25 req/day limit)
|
||||
\\ lookup <CUSIP> Look up CUSIP to ticker via OpenFIGI
|
||||
\\ cache stats Show cache statistics
|
||||
\\ cache clear Clear all cached data
|
||||
|
|
@ -162,7 +163,7 @@ pub fn main() !void {
|
|||
if (args.len < 3) return try stderr_print("Error: 'cache' requires a subcommand (stats, clear)\n");
|
||||
try cmdCache(allocator, config, args[2]);
|
||||
} else if (std.mem.eql(u8, command, "enrich")) {
|
||||
if (args.len < 3) return try stderr_print("Error: 'enrich' requires a portfolio file path\n");
|
||||
if (args.len < 3) return try stderr_print("Error: 'enrich' requires a portfolio file path or symbol\n");
|
||||
try cmdEnrich(allocator, config, args[2]);
|
||||
} else if (std.mem.eql(u8, command, "analysis")) {
|
||||
// File path is first non-flag arg (default: portfolio.srf)
|
||||
|
|
@ -2148,8 +2149,92 @@ fn printBreakdownSection(out: anytype, items: []const zfin.analysis.BreakdownIte
|
|||
/// CLI `enrich` command: bootstrap a metadata.srf file from Alpha Vantage OVERVIEW data.
|
||||
/// Reads the portfolio, extracts stock symbols, fetches sector/industry/country for each,
|
||||
/// and outputs a metadata SRF file to stdout.
|
||||
fn cmdEnrich(allocator: std.mem.Allocator, config: zfin.Config, file_path: []const u8) !void {
|
||||
/// If the argument looks like a symbol (no path separators, no .srf extension), enrich just that symbol.
|
||||
fn cmdEnrich(allocator: std.mem.Allocator, config: zfin.Config, arg: []const u8) !void {
|
||||
// Check for Alpha Vantage API key
|
||||
const av_key = config.alphavantage_key orelse {
|
||||
try stderr_print("Error: ALPHAVANTAGE_API_KEY not set. Add it to .env\n");
|
||||
return;
|
||||
};
|
||||
|
||||
// Determine if arg is a symbol or a file path
|
||||
const is_file = std.mem.endsWith(u8, arg, ".srf") or
|
||||
std.mem.indexOfScalar(u8, arg, '/') != null or
|
||||
std.mem.indexOfScalar(u8, arg, '.') != null;
|
||||
|
||||
if (!is_file) {
|
||||
// Single symbol mode: enrich one symbol, output appendable SRF (no header)
|
||||
try cmdEnrichSymbol(allocator, av_key, arg);
|
||||
return;
|
||||
}
|
||||
|
||||
// Portfolio file mode: enrich all symbols
|
||||
try cmdEnrichPortfolio(allocator, config, av_key, arg);
|
||||
}
|
||||
|
||||
/// Enrich a single symbol and output appendable SRF lines to stdout.
|
||||
fn cmdEnrichSymbol(allocator: std.mem.Allocator, av_key: []const u8, sym: []const u8) !void {
|
||||
const AV = @import("zfin").AlphaVantage;
|
||||
var av = AV.init(allocator, av_key);
|
||||
defer av.deinit();
|
||||
|
||||
var buf: [32768]u8 = undefined;
|
||||
var writer = std.fs.File.stdout().writer(&buf);
|
||||
const out = &writer.interface;
|
||||
|
||||
{
|
||||
var msg_buf: [128]u8 = undefined;
|
||||
const msg = std.fmt.bufPrint(&msg_buf, " Fetching {s}...\n", .{sym}) catch " ...\n";
|
||||
try stderr_print(msg);
|
||||
}
|
||||
|
||||
const overview = av.fetchCompanyOverview(allocator, sym) catch {
|
||||
try stderr_print("Error: Failed to fetch data for symbol\n");
|
||||
try out.print("# {s} -- fetch failed\n", .{sym});
|
||||
try out.print("# symbol::{s},sector::TODO,geo::TODO,asset_class::TODO\n", .{sym});
|
||||
try out.flush();
|
||||
return;
|
||||
};
|
||||
defer {
|
||||
if (overview.name) |n| allocator.free(n);
|
||||
if (overview.sector) |s| allocator.free(s);
|
||||
if (overview.industry) |ind| allocator.free(ind);
|
||||
if (overview.country) |c| allocator.free(c);
|
||||
if (overview.market_cap) |mc| allocator.free(mc);
|
||||
if (overview.asset_type) |at| allocator.free(at);
|
||||
}
|
||||
|
||||
const sector_str = overview.sector orelse "Unknown";
|
||||
const country_str = overview.country orelse "US";
|
||||
const geo_str = if (std.mem.eql(u8, country_str, "USA")) "US" else country_str;
|
||||
|
||||
const asset_class_str = blk: {
|
||||
if (overview.asset_type) |at| {
|
||||
if (std.mem.eql(u8, at, "ETF")) break :blk "ETF";
|
||||
if (std.mem.eql(u8, at, "Mutual Fund")) break :blk "Mutual Fund";
|
||||
}
|
||||
if (overview.market_cap) |mc_str| {
|
||||
const mc = std.fmt.parseInt(u64, mc_str, 10) catch 0;
|
||||
if (mc >= 10_000_000_000) break :blk "US Large Cap";
|
||||
if (mc >= 2_000_000_000) break :blk "US Mid Cap";
|
||||
break :blk "US Small Cap";
|
||||
}
|
||||
break :blk "US Large Cap";
|
||||
};
|
||||
|
||||
if (overview.name) |name| {
|
||||
try out.print("# {s}\n", .{name});
|
||||
}
|
||||
try out.print("symbol::{s},sector::{s},geo::{s},asset_class::{s}\n", .{
|
||||
sym, sector_str, geo_str, asset_class_str,
|
||||
});
|
||||
try out.flush();
|
||||
}
|
||||
|
||||
/// Enrich all symbols from a portfolio file.
|
||||
fn cmdEnrichPortfolio(allocator: std.mem.Allocator, config: zfin.Config, av_key: []const u8, file_path: []const u8) !void {
|
||||
const AV = @import("zfin").AlphaVantage;
|
||||
_ = config;
|
||||
|
||||
// Load portfolio
|
||||
const file_data = std.fs.cwd().readFileAlloc(allocator, file_path, 10 * 1024 * 1024) catch {
|
||||
|
|
@ -2172,11 +2257,6 @@ fn cmdEnrich(allocator: std.mem.Allocator, config: zfin.Config, file_path: []con
|
|||
const syms = try portfolio.stockSymbols(allocator);
|
||||
defer allocator.free(syms);
|
||||
|
||||
// Check for Alpha Vantage API key
|
||||
const av_key = config.alphavantage_key orelse {
|
||||
try stderr_print("Error: ALPHAVANTAGE_API_KEY not set. Add it to .env\n");
|
||||
return;
|
||||
};
|
||||
var av = AV.init(allocator, av_key);
|
||||
defer av.deinit();
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue