switch earnings cache ttl to 30 days + day after earnings
This commit is contained in:
parent
78a15b89be
commit
6a680a2381
3 changed files with 22 additions and 6 deletions
|
|
@ -40,7 +40,7 @@ zfin aggregates data from multiple free-tier APIs. Each provider is used for the
|
||||||
| Dividends | Polygon | `POLYGON_API_KEY` | 5 req/min | 7 days |
|
| Dividends | Polygon | `POLYGON_API_KEY` | 5 req/min | 7 days |
|
||||||
| Splits | Polygon | `POLYGON_API_KEY` | 5 req/min | 7 days |
|
| Splits | Polygon | `POLYGON_API_KEY` | 5 req/min | 7 days |
|
||||||
| Options chains | CBOE | None required | ~30 req/min (self-imposed) | 1 hour |
|
| Options chains | CBOE | None required | ~30 req/min (self-imposed) | 1 hour |
|
||||||
| Earnings | Finnhub | `FINNHUB_API_KEY` | 60 req/min | 24 hours |
|
| Earnings | Finnhub | `FINNHUB_API_KEY` | 60 req/min | 30 days* |
|
||||||
| ETF profiles | Alpha Vantage | `ALPHAVANTAGE_API_KEY` | 25 req/day | 30 days |
|
| ETF profiles | Alpha Vantage | `ALPHAVANTAGE_API_KEY` | 25 req/day | 30 days |
|
||||||
|
|
||||||
### TwelveData
|
### TwelveData
|
||||||
|
|
@ -127,10 +127,12 @@ Cache files use [SRF](https://github.com/lobo/srf) (Simple Record Format), a lin
|
||||||
| Dividends | 7 days | Declared well in advance |
|
| Dividends | 7 days | Declared well in advance |
|
||||||
| Splits | 7 days | Rare corporate events |
|
| Splits | 7 days | Rare corporate events |
|
||||||
| Options | 1 hour | Prices change continuously during market hours |
|
| Options | 1 hour | Prices change continuously during market hours |
|
||||||
| Earnings | 24 hours | Quarterly events, estimates update periodically |
|
| Earnings | 30 days* | Quarterly events; smart refresh after announcements |
|
||||||
| ETF profiles | 30 days | Holdings/weights change slowly |
|
| ETF profiles | 30 days | Holdings/weights change slowly |
|
||||||
| Quotes | Never cached | Intended for live price checks |
|
| Quotes | Never cached | Intended for live price checks |
|
||||||
|
|
||||||
|
\* **Earnings smart refresh:** Even within the 30-day TTL, cached earnings are automatically re-fetched when an earnings date has passed but the cache still has no actual results for it. This ensures results appear promptly after an announcement without wasteful daily polling.
|
||||||
|
|
||||||
Manual refresh (`r` / `F5` in TUI) invalidates the cache for the current tab's data before re-fetching.
|
Manual refresh (`r` / `F5` in TUI) invalidates the cache for the current tab's data before re-fetching.
|
||||||
|
|
||||||
### Rate limiting
|
### Rate limiting
|
||||||
|
|
|
||||||
4
src/cache/store.zig
vendored
4
src/cache/store.zig
vendored
|
|
@ -28,8 +28,8 @@ pub const Ttl = struct {
|
||||||
pub const splits: i64 = 7 * 24 * 3600;
|
pub const splits: i64 = 7 * 24 * 3600;
|
||||||
/// Options chains refresh hourly
|
/// Options chains refresh hourly
|
||||||
pub const options: i64 = 3600;
|
pub const options: i64 = 3600;
|
||||||
/// Earnings refresh daily
|
/// Earnings refresh monthly, with smart refresh after announcements
|
||||||
pub const earnings: i64 = 24 * 3600;
|
pub const earnings: i64 = 30 * 24 * 3600;
|
||||||
/// ETF profiles refresh monthly
|
/// ETF profiles refresh monthly
|
||||||
pub const etf_profile: i64 = 30 * 24 * 3600;
|
pub const etf_profile: i64 = 30 * 24 * 3600;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -248,20 +248,34 @@ pub const DataService = struct {
|
||||||
|
|
||||||
/// Fetch earnings history for a symbol (5 years back, 1 year forward).
|
/// Fetch earnings history for a symbol (5 years back, 1 year forward).
|
||||||
/// Checks cache first; fetches from Finnhub if stale/missing.
|
/// Checks cache first; fetches from Finnhub if stale/missing.
|
||||||
|
/// Smart refresh: even if cache is fresh, re-fetches when a past earnings
|
||||||
|
/// date has no actual results yet (i.e. results just came out).
|
||||||
pub fn getEarnings(self: *DataService, symbol: []const u8) DataError!struct { data: []EarningsEvent, source: Source, timestamp: i64 } {
|
pub fn getEarnings(self: *DataService, symbol: []const u8) DataError!struct { data: []EarningsEvent, source: Source, timestamp: i64 } {
|
||||||
var s = self.store();
|
var s = self.store();
|
||||||
|
const today = todayDate();
|
||||||
|
|
||||||
const cached_raw = s.readRaw(symbol, .earnings) catch return DataError.CacheError;
|
const cached_raw = s.readRaw(symbol, .earnings) catch return DataError.CacheError;
|
||||||
if (cached_raw) |data| {
|
if (cached_raw) |data| {
|
||||||
defer self.allocator.free(data);
|
defer self.allocator.free(data);
|
||||||
if (cache.Store.isFreshData(data, self.allocator)) {
|
if (cache.Store.isFreshData(data, self.allocator)) {
|
||||||
const events = cache.Store.deserializeEarnings(self.allocator, data) catch null;
|
const events = cache.Store.deserializeEarnings(self.allocator, data) catch null;
|
||||||
if (events) |e| return .{ .data = e, .source = .cached, .timestamp = s.getMtime(symbol, .earnings) orelse std.time.timestamp() };
|
if (events) |e| {
|
||||||
|
// Check if any past/today earnings event is still missing actual results.
|
||||||
|
// If so, the announcement likely just happened — force a refresh.
|
||||||
|
const needs_refresh = for (e) |ev| {
|
||||||
|
if (ev.actual == null and !today.lessThan(ev.date)) break true;
|
||||||
|
} else false;
|
||||||
|
|
||||||
|
if (!needs_refresh) {
|
||||||
|
return .{ .data = e, .source = .cached, .timestamp = s.getMtime(symbol, .earnings) orelse std.time.timestamp() };
|
||||||
|
}
|
||||||
|
// Stale: free cached events and re-fetch below
|
||||||
|
self.allocator.free(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var fh = try self.getFinnhub();
|
var fh = try self.getFinnhub();
|
||||||
const today = todayDate();
|
|
||||||
const from = today.subtractYears(5);
|
const from = today.subtractYears(5);
|
||||||
const to = today.addDays(365);
|
const to = today.addDays(365);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue