ai: skip fetch only if price:: set but no ticker:: alias
This commit is contained in:
parent
6d9374ab77
commit
ea6ac524bb
5 changed files with 46 additions and 23 deletions
|
|
@ -498,4 +498,3 @@ test "as-of-date vs month-end -- different results from same data" {
|
|||
try std.testing.expect(me.one_year != null);
|
||||
try std.testing.expectApproxEqAbs(@as(f64, 0.15), me.one_year.?.total_return, 0.001);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@
|
|||
/// symbol::02315N600,asset_class::US Large Cap,pct:num:55
|
||||
/// symbol::02315N600,asset_class::International Developed,pct:num:20
|
||||
/// symbol::02315N600,asset_class::Bonds,pct:num:15
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
/// A single classification entry for a symbol.
|
||||
|
|
|
|||
|
|
@ -153,12 +153,17 @@ pub const Portfolio = struct {
|
|||
|
||||
/// Get unique symbols for stock/ETF lots only (skips options, CDs, cash).
|
||||
/// Returns the price symbol (ticker alias if set, otherwise raw symbol).
|
||||
/// Excludes manual-price-only lots (price:: set, no ticker::) since those
|
||||
/// have no API coverage and should never be fetched.
|
||||
pub fn stockSymbols(self: Portfolio, allocator: std.mem.Allocator) ![][]const u8 {
|
||||
var seen = std.StringHashMap(void).init(allocator);
|
||||
defer seen.deinit();
|
||||
|
||||
for (self.lots) |lot| {
|
||||
if (lot.lot_type == .stock) {
|
||||
// Skip lots that have a manual price but no ticker alias —
|
||||
// these are securities without API coverage (e.g. 401k CIT shares).
|
||||
if (lot.price != null and lot.ticker == null) continue;
|
||||
try seen.put(lot.priceSymbol(), {});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,19 +98,45 @@ pub const TwelveData = struct {
|
|||
return self.parsed.value.object;
|
||||
}
|
||||
|
||||
pub fn symbol(self: ParsedQuote) []const u8 { return jsonStr(self.root().get("symbol")); }
|
||||
pub fn name(self: ParsedQuote) []const u8 { return jsonStr(self.root().get("name")); }
|
||||
pub fn exchange(self: ParsedQuote) []const u8 { return jsonStr(self.root().get("exchange")); }
|
||||
pub fn datetime(self: ParsedQuote) []const u8 { return jsonStr(self.root().get("datetime")); }
|
||||
pub fn close(self: ParsedQuote) f64 { return parseJsonFloat(self.root().get("close")); }
|
||||
pub fn open(self: ParsedQuote) f64 { return parseJsonFloat(self.root().get("open")); }
|
||||
pub fn high(self: ParsedQuote) f64 { return parseJsonFloat(self.root().get("high")); }
|
||||
pub fn low(self: ParsedQuote) f64 { return parseJsonFloat(self.root().get("low")); }
|
||||
pub fn volume(self: ParsedQuote) u64 { return @intFromFloat(parseJsonFloat(self.root().get("volume"))); }
|
||||
pub fn previous_close(self: ParsedQuote) f64 { return parseJsonFloat(self.root().get("previous_close")); }
|
||||
pub fn change(self: ParsedQuote) f64 { return parseJsonFloat(self.root().get("change")); }
|
||||
pub fn percent_change(self: ParsedQuote) f64 { return parseJsonFloat(self.root().get("percent_change")); }
|
||||
pub fn average_volume(self: ParsedQuote) u64 { return @intFromFloat(parseJsonFloat(self.root().get("average_volume"))); }
|
||||
pub fn symbol(self: ParsedQuote) []const u8 {
|
||||
return jsonStr(self.root().get("symbol"));
|
||||
}
|
||||
pub fn name(self: ParsedQuote) []const u8 {
|
||||
return jsonStr(self.root().get("name"));
|
||||
}
|
||||
pub fn exchange(self: ParsedQuote) []const u8 {
|
||||
return jsonStr(self.root().get("exchange"));
|
||||
}
|
||||
pub fn datetime(self: ParsedQuote) []const u8 {
|
||||
return jsonStr(self.root().get("datetime"));
|
||||
}
|
||||
pub fn close(self: ParsedQuote) f64 {
|
||||
return parseJsonFloat(self.root().get("close"));
|
||||
}
|
||||
pub fn open(self: ParsedQuote) f64 {
|
||||
return parseJsonFloat(self.root().get("open"));
|
||||
}
|
||||
pub fn high(self: ParsedQuote) f64 {
|
||||
return parseJsonFloat(self.root().get("high"));
|
||||
}
|
||||
pub fn low(self: ParsedQuote) f64 {
|
||||
return parseJsonFloat(self.root().get("low"));
|
||||
}
|
||||
pub fn volume(self: ParsedQuote) u64 {
|
||||
return @intFromFloat(parseJsonFloat(self.root().get("volume")));
|
||||
}
|
||||
pub fn previous_close(self: ParsedQuote) f64 {
|
||||
return parseJsonFloat(self.root().get("previous_close"));
|
||||
}
|
||||
pub fn change(self: ParsedQuote) f64 {
|
||||
return parseJsonFloat(self.root().get("change"));
|
||||
}
|
||||
pub fn percent_change(self: ParsedQuote) f64 {
|
||||
return parseJsonFloat(self.root().get("percent_change"));
|
||||
}
|
||||
pub fn average_volume(self: ParsedQuote) u64 {
|
||||
return @intFromFloat(parseJsonFloat(self.root().get("average_volume")));
|
||||
}
|
||||
|
||||
pub fn fifty_two_week_low(self: ParsedQuote) f64 {
|
||||
const ftw = self.root().get("fifty_two_week") orelse return 0;
|
||||
|
|
|
|||
|
|
@ -138,8 +138,6 @@ pub const DataService = struct {
|
|||
const from = today.addDays(-365 * 10 - 60);
|
||||
|
||||
const fetched = td.fetchCandles(self.allocator, symbol, from, today) catch {
|
||||
// Write negative cache entry so we don't retry on next run
|
||||
s.writeNegative(symbol, .candles_daily);
|
||||
return DataError.FetchFailed;
|
||||
};
|
||||
|
||||
|
|
@ -171,7 +169,6 @@ pub const DataService = struct {
|
|||
|
||||
var pg = try self.getPolygon();
|
||||
const fetched = pg.fetchDividends(self.allocator, symbol, null, null) catch {
|
||||
s.writeNegative(symbol, .dividends);
|
||||
return DataError.FetchFailed;
|
||||
};
|
||||
|
||||
|
|
@ -202,7 +199,6 @@ pub const DataService = struct {
|
|||
|
||||
var pg = try self.getPolygon();
|
||||
const fetched = pg.fetchSplits(self.allocator, symbol) catch {
|
||||
s.writeNegative(symbol, .splits);
|
||||
return DataError.FetchFailed;
|
||||
};
|
||||
|
||||
|
|
@ -231,7 +227,6 @@ pub const DataService = struct {
|
|||
|
||||
var cboe = self.getCboe();
|
||||
const fetched = cboe.fetchOptionsChain(self.allocator, symbol) catch {
|
||||
s.writeNegative(symbol, .options);
|
||||
return DataError.FetchFailed;
|
||||
};
|
||||
|
||||
|
|
@ -266,7 +261,6 @@ pub const DataService = struct {
|
|||
const to = today.addDays(365);
|
||||
|
||||
const fetched = fh.fetchEarnings(self.allocator, symbol, from, to) catch {
|
||||
s.writeNegative(symbol, .earnings);
|
||||
return DataError.FetchFailed;
|
||||
};
|
||||
|
||||
|
|
@ -297,7 +291,6 @@ pub const DataService = struct {
|
|||
|
||||
var av = try self.getAlphaVantage();
|
||||
const fetched = av.fetchEtfProfile(self.allocator, symbol) catch {
|
||||
s.writeNegative(symbol, .etf_profile);
|
||||
return DataError.FetchFailed;
|
||||
};
|
||||
|
||||
|
|
@ -412,9 +405,10 @@ pub const DataService = struct {
|
|||
}
|
||||
|
||||
/// Read candles from cache only (no network fetch). Used by TUI for display.
|
||||
/// Returns null if no cached data exists.
|
||||
/// Returns null if no cached data exists or if the entry is a negative cache (fetch_failed).
|
||||
pub fn getCachedCandles(self: *DataService, symbol: []const u8) ?[]Candle {
|
||||
var s = self.store();
|
||||
if (s.isNegative(symbol, .candles_daily)) return null;
|
||||
const data = s.readRaw(symbol, .candles_daily) catch return null;
|
||||
if (data) |d| {
|
||||
defer self.allocator.free(d);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue