const Date = @import("date.zig").Date; pub const ReportTime = enum { bmo, // before market open amc, // after market close dmh, // during market hours unknown, }; /// An earnings event (historical or upcoming). pub const EarningsEvent = struct { symbol: []const u8 = "", date: Date, /// Estimated EPS (analyst consensus) estimate: ?f64 = null, /// Actual reported EPS (null if upcoming) actual: ?f64 = null, /// Surprise amount (actual - estimate) surprise: ?f64 = null, /// Surprise percentage surprise_percent: ?f64 = null, /// Fiscal quarter (1-4) quarter: ?u8 = null, /// Fiscal year fiscal_year: ?i16 = null, /// Revenue actual revenue_actual: ?f64 = null, /// Revenue estimate revenue_estimate: ?f64 = null, /// When earnings are reported relative to market hours report_time: ReportTime = .unknown, pub fn isFuture(self: EarningsEvent) bool { return self.actual == null; } pub fn surpriseAmount(self: EarningsEvent) ?f64 { if (self.surprise) |s| return s; const act = self.actual orelse return null; const est = self.estimate orelse return null; return act - est; } pub fn surprisePct(self: EarningsEvent) ?f64 { if (self.surprise_percent) |s| return s; const act = self.actual orelse return null; const est = self.estimate orelse return null; if (est == 0) return null; return ((act - est) / @abs(est)) * 100.0; } }; const std = @import("std"); test "isFuture" { const future = EarningsEvent{ .symbol = "AAPL", .date = Date{ .days = 20000 }, .estimate = 1.5 }; try std.testing.expect(future.isFuture()); const past = EarningsEvent{ .symbol = "AAPL", .date = Date{ .days = 19000 }, .actual = 1.6, .estimate = 1.5 }; try std.testing.expect(!past.isFuture()); } test "surpriseAmount" { // With surprise field set directly const with_surprise = EarningsEvent{ .symbol = "AAPL", .date = Date{ .days = 19000 }, .surprise = 0.15 }; try std.testing.expectApproxEqAbs(@as(f64, 0.15), with_surprise.surpriseAmount().?, 0.001); // With actual and estimate (computed) const computed = EarningsEvent{ .symbol = "AAPL", .date = Date{ .days = 19000 }, .actual = 1.65, .estimate = 1.50 }; try std.testing.expectApproxEqAbs(@as(f64, 0.15), computed.surpriseAmount().?, 0.001); // Missing actual -> null const no_actual = EarningsEvent{ .symbol = "AAPL", .date = Date{ .days = 19000 }, .estimate = 1.50 }; try std.testing.expect(no_actual.surpriseAmount() == null); // Missing estimate -> null const no_estimate = EarningsEvent{ .symbol = "AAPL", .date = Date{ .days = 19000 }, .actual = 1.65 }; try std.testing.expect(no_estimate.surpriseAmount() == null); } test "surprisePct" { // With surprise_percent set directly const with_pct = EarningsEvent{ .symbol = "AAPL", .date = Date{ .days = 19000 }, .surprise_percent = 10.0 }; try std.testing.expectApproxEqAbs(@as(f64, 10.0), with_pct.surprisePct().?, 0.001); // Computed: actual=1.65, estimate=1.50 -> (0.15/1.50)*100 = 10% const computed = EarningsEvent{ .symbol = "AAPL", .date = Date{ .days = 19000 }, .actual = 1.65, .estimate = 1.50 }; try std.testing.expectApproxEqAbs(@as(f64, 10.0), computed.surprisePct().?, 0.001); // Estimate = 0 -> null (division by zero guard) const zero_est = EarningsEvent{ .symbol = "AAPL", .date = Date{ .days = 19000 }, .actual = 1.0, .estimate = 0.0 }; try std.testing.expect(zero_est.surprisePct() == null); // Negative surprise: actual < estimate const miss = EarningsEvent{ .symbol = "AAPL", .date = Date{ .days = 19000 }, .actual = 1.35, .estimate = 1.50 }; try std.testing.expect(miss.surprisePct().? < 0); }