diff --git a/src/main.zig b/src/main.zig
index b4acff4..0980dd9 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -64,8 +64,13 @@ fn handleHelp(_: *App, _: *httpz.Request, res: *httpz.Response) !void {
\\ GET /{SYMBOL}/options Raw SRF cache file
\\ GET /symbols List of tracked symbols
\\
+ \\Returns fields:
+ \\ trailing{1,3,5,10}YearReturn Total return with dividend reinvestment
+ \\ price{1,3,5,10}YearReturn Price-only return (from adjusted close)
+ \\ volatility 3-year annualized volatility
+ \\
\\XML example (LibreCalc):
- \\ =FILTERXML(WEBSERVICE("http://host/AAPL/returns?fmt=xml"),"//trailing1YearReturn")
+ \\ =FILTERXML(WEBSERVICE("http://host/AAPL/returns?fmt=xml"),"//total10YearReturn")
\\
;
}
@@ -143,17 +148,42 @@ fn handleReturns(app: *App, req: *httpz.Request, res: *httpz.Response) !void {
return;
}
- const ret = zfin.performance.trailingReturns(candles);
+ // Price-only returns (from adjusted close)
+ const price_ret = zfin.performance.trailingReturns(candles);
var date_buf: [10]u8 = undefined;
const date_str = candles[candles.len - 1].date.format(&date_buf);
const risk = zfin.risk.trailingRisk(candles);
- const r1y = if (ret.one_year) |r| r.annualized_return else null;
- const r3y = if (ret.three_year) |r| r.annualized_return else null;
- const r5y = if (ret.five_year) |r| r.annualized_return else null;
- const r10y = if (ret.ten_year) |r| r.annualized_return else null;
+ const p1y = if (price_ret.one_year) |r| r.annualized_return else null;
+ const p3y = if (price_ret.three_year) |r| r.annualized_return else null;
+ const p5y = if (price_ret.five_year) |r| r.annualized_return else null;
+ const p10y = if (price_ret.ten_year) |r| r.annualized_return else null;
const vol = if (risk.three_year) |r| r.volatility else null;
+ // Total returns with dividend reinvestment (non-fatal if dividends unavailable).
+ // trailing* fields use total return when available, falling back to price-only.
+ var t1y: ?f64 = p1y;
+ var t3y: ?f64 = p3y;
+ var t5y: ?f64 = p5y;
+ var t10y: ?f64 = p10y;
+
+ if (app.svc.getDividends(symbol)) |div_result| {
+ defer zfin.Dividend.freeSlice(app.allocator, div_result.data);
+ const total = zfin.performance.trailingReturnsWithDividends(candles, div_result.data);
+ if (total.one_year) |r| {
+ t1y = r.annualized_return;
+ }
+ if (total.three_year) |r| {
+ t3y = r.annualized_return;
+ }
+ if (total.five_year) |r| {
+ t5y = r.annualized_return;
+ }
+ if (total.ten_year) |r| {
+ t10y = r.annualized_return;
+ }
+ } else |_| {}
+
// Check if XML requested
if (q.get("fmt")) |fmt| {
if (std.ascii.eqlIgnoreCase(fmt, "xml")) {
@@ -166,16 +196,24 @@ fn handleReturns(app: *App, req: *httpz.Request, res: *httpz.Response) !void {
\\ {s}
\\ {s}
\\ {s}
+ \\ {s}
+ \\ {s}
+ \\ {s}
+ \\ {s}
\\ {s}
\\
\\
, .{
symbol,
date_str,
- fmtPct(arena, r1y),
- fmtPct(arena, r3y),
- fmtPct(arena, r5y),
- fmtPct(arena, r10y),
+ fmtPct(arena, t1y),
+ fmtPct(arena, t3y),
+ fmtPct(arena, t5y),
+ fmtPct(arena, t10y),
+ fmtPct(arena, p1y),
+ fmtPct(arena, p3y),
+ fmtPct(arena, p5y),
+ fmtPct(arena, p10y),
fmtPct(arena, vol),
});
return;
@@ -184,14 +222,18 @@ fn handleReturns(app: *App, req: *httpz.Request, res: *httpz.Response) !void {
res.content_type = httpz.ContentType.JSON;
res.body = try std.fmt.allocPrint(arena,
- \\{{"ticker":"{s}","returnDate":"{s}","trailing1YearReturn":{s},"trailing3YearReturn":{s},"trailing5YearReturn":{s},"trailing10YearReturn":{s},"volatility":{s}}}
+ \\{{"ticker":"{s}","returnDate":"{s}","trailing1YearReturn":{s},"trailing3YearReturn":{s},"trailing5YearReturn":{s},"trailing10YearReturn":{s},"price1YearReturn":{s},"price3YearReturn":{s},"price5YearReturn":{s},"price10YearReturn":{s},"volatility":{s}}}
, .{
symbol,
date_str,
- fmtPct(arena, r1y),
- fmtPct(arena, r3y),
- fmtPct(arena, r5y),
- fmtPct(arena, r10y),
+ fmtPct(arena, t1y),
+ fmtPct(arena, t3y),
+ fmtPct(arena, t5y),
+ fmtPct(arena, t10y),
+ fmtPct(arena, p1y),
+ fmtPct(arena, p3y),
+ fmtPct(arena, p5y),
+ fmtPct(arena, p10y),
fmtPct(arena, vol),
});
}