refresh fixes

This commit is contained in:
Emil Lerch 2026-03-11 11:04:05 -07:00
parent 32cc139ef1
commit c596d8c12f
Signed by: lobo
GPG key ID: A7B62D657EF764F8

View file

@ -261,7 +261,22 @@ fn upperDupe(allocator: std.mem.Allocator, s: []const u8) ![]u8 {
return d; return d;
} }
/// Format as percentage string for XML (e.g., 0.1234 -> "12.34000") fn printRateLimitWait(svc: *zfin.DataService, stdout: *std.Io.Writer) !void {
if (svc.estimateWaitSeconds()) |wait| {
if (wait > 0) {
try stdout.print("\n (rate limit -- waiting {d}s)\n ", .{wait});
try stdout.flush();
}
}
}
/// Mutual funds typically have 5-letter tickers ending in X.
/// These don't have quarterly earnings on Finnhub.
fn isMutualFund(symbol: []const u8) bool {
return symbol.len == 5 and symbol[4] == 'X';
}
/// Format as percentage (e.g., 0.1234 -> "12.34000"), or "null" if absent.
fn fmtPct(arena: std.mem.Allocator, value: ?f64) []const u8 { fn fmtPct(arena: std.mem.Allocator, value: ?f64) []const u8 {
if (value) |v| return std.fmt.allocPrint(arena, "{d:.5}", .{v * 100.0}) catch "null"; if (value) |v| return std.fmt.allocPrint(arena, "{d:.5}", .{v * 100.0}) catch "null";
return "null"; return "null";
@ -345,16 +360,11 @@ fn refresh(allocator: std.mem.Allocator) !void {
}; };
defer allocator.free(data); defer allocator.free(data);
const portfolio = zfin.cache.deserializePortfolio(allocator, data) catch { var portfolio = zfin.cache.deserializePortfolio(allocator, data) catch {
log.err("failed to parse portfolio", .{}); log.err("failed to parse portfolio", .{});
return error.ParseFailed; return error.ParseFailed;
}; };
defer { defer portfolio.deinit();
for (portfolio.lots) |*lot| {
if (lot.note) |n| allocator.free(n);
}
allocator.free(portfolio.lots);
}
var symbols = std.StringHashMap(void).init(allocator); var symbols = std.StringHashMap(void).init(allocator);
defer symbols.deinit(); defer symbols.deinit();
@ -382,21 +392,13 @@ fn refresh(allocator: std.mem.Allocator) !void {
var it = symbols.iterator(); var it = symbols.iterator();
while (it.next()) |entry| { while (it.next()) |entry| {
const sym = entry.key_ptr.*; const sym = entry.key_ptr.*;
// Check if we need to wait for rate limits
if (svc.estimateWaitSeconds()) |wait| {
if (wait > 0) {
try stdout.print(" (rate limit -- waiting {d}s)\n", .{wait});
try stdout.flush();
}
}
try stdout.print("{s}: ", .{sym}); try stdout.print("{s}: ", .{sym});
try stdout.flush(); try stdout.flush();
var sym_ok = true; var sym_ok = true;
// Candles // Candles
try printRateLimitWait(&svc, stdout);
if (svc.getCandles(sym)) |result| { if (svc.getCandles(sym)) |result| {
allocator.free(result.data); allocator.free(result.data);
try stdout.print("candles ok", .{}); try stdout.print("candles ok", .{});
@ -406,21 +408,27 @@ fn refresh(allocator: std.mem.Allocator) !void {
} }
// Dividends // Dividends
try printRateLimitWait(&svc, stdout);
if (svc.getDividends(sym)) |result| { if (svc.getDividends(sym)) |result| {
allocator.free(result.data); zfin.Dividend.freeSlice(allocator, result.data);
try stdout.print(", dividends ok", .{}); try stdout.print(", dividends ok", .{});
} else |err| { } else |err| {
try stdout.print(", dividends FAILED ({s})", .{@errorName(err)}); try stdout.print(", dividends FAILED ({s})", .{@errorName(err)});
sym_ok = false; sym_ok = false;
} }
// Earnings // Earnings (skip for mutual funds they don't report quarterly earnings)
if (svc.getEarnings(sym)) |result| { if (!isMutualFund(sym)) {
allocator.free(result.data); try printRateLimitWait(&svc, stdout);
try stdout.print(", earnings ok", .{}); if (svc.getEarnings(sym)) |result| {
} else |err| { allocator.free(result.data);
try stdout.print(", earnings FAILED ({s})", .{@errorName(err)}); try stdout.print(", earnings ok", .{});
sym_ok = false; } else |err| {
try stdout.print(", earnings FAILED ({s})", .{@errorName(err)});
sym_ok = false;
}
} else {
try stdout.print(", earnings skipped (fund)", .{});
} }
try stdout.print("\n", .{}); try stdout.print("\n", .{});