From 543be9733e1dea5e5fdfa3a9930a669236308918 Mon Sep 17 00:00:00 2001 From: Emil Lerch Date: Tue, 10 Mar 2026 13:40:06 -0700 Subject: [PATCH] finish service.zig cleanup --- src/format.zig | 2 +- src/net/RateLimiter.zig | 7 +++++++ src/service.zig | 25 ++++++++++--------------- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/format.zig b/src/format.zig index 13adeea..5c83934 100644 --- a/src/format.zig +++ b/src/format.zig @@ -281,7 +281,7 @@ pub fn fmtLargeNum(val: f64) [15]u8 { /// Get today's date. pub fn todayDate() Date { const ts = std.time.timestamp(); - const days: i32 = @intCast(@divFloor(ts, 86400)); + const days: i32 = @intCast(@divFloor(ts, std.time.s_per_day)); return .{ .days = days }; } diff --git a/src/net/RateLimiter.zig b/src/net/RateLimiter.zig index ba74ab6..a9b55c2 100644 --- a/src/net/RateLimiter.zig +++ b/src/net/RateLimiter.zig @@ -59,6 +59,13 @@ pub fn acquire(self: *RateLimiter) void { } } +/// Sleep until a token is likely available, with a minimum 2-second floor. +/// Use after receiving a server-side 429 to wait before retrying. +pub fn backoff(self: *RateLimiter) void { + const wait_ns: u64 = @max(self.estimateWaitNs(), 2 * std.time.ns_per_s); + std.Thread.sleep(wait_ns); +} + /// Returns estimated wait time in nanoseconds until a token is available. /// Returns 0 if a token is available now. pub fn estimateWaitNs(self: *RateLimiter) u64 { diff --git a/src/service.zig b/src/service.zig index 05f48e4..160da3b 100644 --- a/src/service.zig +++ b/src/service.zig @@ -26,6 +26,7 @@ const Cboe = @import("providers/cboe.zig").Cboe; const AlphaVantage = @import("providers/alphavantage.zig").AlphaVantage; const alphavantage = @import("providers/alphavantage.zig"); const OpenFigi = @import("providers/openfigi.zig"); +const fmt = @import("format.zig"); const performance = @import("analytics/performance.zig"); pub const DataError = error{ @@ -219,7 +220,7 @@ pub const DataService = struct { /// the entire history. pub fn getCandles(self: *DataService, symbol: []const u8) DataError!FetchResult(Candle) { var s = self.store(); - const today = todayDate(); + const today = fmt.todayDate(); // Check candle metadata for freshness (tiny file, no candle deserialization) const meta_result = s.readCandleMeta(symbol); @@ -325,7 +326,7 @@ pub const DataService = struct { /// date has no actual results yet (i.e. results just came out). pub fn getEarnings(self: *DataService, symbol: []const u8) DataError!FetchResult(EarningsEvent) { var s = self.store(); - const today = todayDate(); + const today = fmt.todayDate(); if (s.read(EarningsEvent, symbol, earningsPostProcess, .fresh_only)) |cached| { // Check if any past/today earnings event is still missing actual results. @@ -420,7 +421,7 @@ pub const DataService = struct { const c = candle_result.data; if (c.len == 0) return DataError.FetchFailed; - const today = todayDate(); + const today = fmt.todayDate(); // As-of-date (end = last candle) const asof_price = performance.trailingReturns(c); @@ -706,18 +707,12 @@ pub const DataService = struct { // ── Utility ────────────────────────────────────────────────── /// Sleep before retrying after a rate limit error. - /// Uses the provider's rate limiter estimate if available, otherwise a fixed 10s backoff. + /// Uses the provider's rate limiter if available, otherwise a fixed 10s backoff. fn rateLimitBackoff(self: *DataService) void { - const wait_ns: u64 = if (self.td) |*td| - @max(td.rate_limiter.estimateWaitNs(), 2 * std.time.ns_per_s) - else - 10 * std.time.ns_per_s; - std.Thread.sleep(wait_ns); - } - - fn todayDate() Date { - const ts = std.time.timestamp(); - const days: i32 = @intCast(@divFloor(ts, std.time.s_per_day)); - return .{ .days = days }; + if (self.td) |*td| { + td.rate_limiter.backoff(); + } else { + std.Thread.sleep(10 * std.time.ns_per_s); + } } };