clean up history/enrich

This commit is contained in:
Emil Lerch 2026-03-20 09:07:47 -07:00
parent 1cdf228fc1
commit be42f9e15a
Signed by: lobo
GPG key ID: A7B62D657EF764F8
2 changed files with 36 additions and 42 deletions

View file

@ -3,6 +3,36 @@ const zfin = @import("../root.zig");
const cli = @import("common.zig");
const isCusipLike = @import("../models/portfolio.zig").isCusipLike;
const OverviewMeta = struct {
sector: []const u8,
geo: []const u8,
asset_class: []const u8,
};
/// Derive sector, geo, and asset_class from an Alpha Vantage company overview.
fn deriveMetadata(overview: zfin.CompanyOverview, sector_buf: []u8) OverviewMeta {
const sector_raw = overview.sector orelse "Unknown";
const sector_str = cli.fmt.toTitleCase(sector_buf, sector_raw);
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";
};
return .{ .sector = sector_str, .geo = geo_str, .asset_class = asset_class_str };
}
/// 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.
@ -50,31 +80,14 @@ fn enrichSymbol(allocator: std.mem.Allocator, svc: *zfin.DataService, sym: []con
if (overview.asset_type) |at| allocator.free(at);
}
const sector_raw = overview.sector orelse "Unknown";
var sector_buf: [64]u8 = undefined;
const sector_str = cli.fmt.toTitleCase(&sector_buf, sector_raw);
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";
};
const meta = deriveMetadata(overview, &sector_buf);
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,
sym, meta.sector, meta.geo, meta.asset_class,
});
}
@ -159,34 +172,15 @@ fn enrichPortfolio(allocator: std.mem.Allocator, svc: *zfin.DataService, file_pa
if (overview.asset_type) |at| allocator.free(at);
}
const sector_raw = overview.sector orelse "Unknown";
var sector_buf: [64]u8 = undefined;
const sector_str = cli.fmt.toTitleCase(&sector_buf, sector_raw);
const country_str = overview.country orelse "US";
const geo_str = if (std.mem.eql(u8, country_str, "USA")) "US" else country_str;
// Determine asset_class from asset type + market cap
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";
}
// For common stocks, infer from market cap
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";
};
const meta = deriveMetadata(overview, &sector_buf);
// Comment with the name for readability
if (overview.name) |name| {
try out.print("# {s}\n", .{name});
}
try out.print("symbol::{s},sector::{s},geo::{s},asset_class::{s}\n\n", .{
sym, sector_str, geo_str, asset_class_str,
sym, meta.sector, meta.geo, meta.asset_class,
});
success += 1;
}

View file

@ -6,7 +6,7 @@ const fmt = cli.fmt;
pub fn run(allocator: std.mem.Allocator, svc: *zfin.DataService, symbol: []const u8, color: bool, out: *std.Io.Writer) !void {
const result = svc.getCandles(symbol) catch |err| switch (err) {
zfin.DataError.NoApiKey => {
try cli.stderrPrint("Error: TWELVEDATA_API_KEY not set.\n");
try cli.stderrPrint("Error: No API key configured for candle data.\n");
return;
},
else => {
@ -46,7 +46,7 @@ pub fn display(candles: []const zfin.Candle, symbol: []const u8, color: bool, ou
for (candles) |candle| {
var db: [10]u8 = undefined;
var vb: [32]u8 = undefined;
try cli.setGainLoss(out, color, if (candle.close >= candle.open) @as(f64, 1) else @as(f64, -1));
try cli.setGainLoss(out, color, if (candle.close >= candle.open) 1.0 else -1.0);
try out.print("{s:>12} {d:>10.2} {d:>10.2} {d:>10.2} {d:>10.2} {s:>12}\n", .{
candle.date.format(&db), candle.open, candle.high, candle.low, candle.close, fmt.fmtIntCommas(&vb, candle.volume),
});