diff --git a/src/commands/enrich.zig b/src/commands/enrich.zig index 88340e4..62b1ecf 100644 --- a/src/commands/enrich.zig +++ b/src/commands/enrich.zig @@ -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(§or_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, §or_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(§or_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, §or_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; } diff --git a/src/commands/history.zig b/src/commands/history.zig index d6e2a7d..7c04d15 100644 --- a/src/commands/history.zig +++ b/src/commands/history.zig @@ -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), });