remove unused polygon candle fetching
This commit is contained in:
parent
26b831631d
commit
497b1d3c2d
1 changed files with 2 additions and 118 deletions
|
|
@ -1,15 +1,13 @@
|
|||
//! Polygon.io API provider -- primary source for dividend/split reference data
|
||||
//! and secondary source for daily OHLCV bars.
|
||||
//! Polygon.io API provider -- primary source for dividend/split reference data.
|
||||
//! API docs: https://polygon.io/docs
|
||||
//!
|
||||
//! Free tier: 5 requests/min, unlimited daily, 2yr historical bars.
|
||||
//! Free tier: 5 requests/min, unlimited daily.
|
||||
//! Dividends and splits are available for all history.
|
||||
|
||||
const std = @import("std");
|
||||
const http = @import("../net/http.zig");
|
||||
const RateLimiter = @import("../net/RateLimiter.zig");
|
||||
const Date = @import("../models/date.zig").Date;
|
||||
const Candle = @import("../models/candle.zig").Candle;
|
||||
const Dividend = @import("../models/dividend.zig").Dividend;
|
||||
const DividendType = @import("../models/dividend.zig").DividendType;
|
||||
const Split = @import("../models/split.zig").Split;
|
||||
|
|
@ -135,39 +133,6 @@ pub const Polygon = struct {
|
|||
|
||||
return parseSplitsResponse(allocator, response.body);
|
||||
}
|
||||
|
||||
/// Fetch daily OHLCV bars. Polygon free tier: 2 years max history.
|
||||
/// Polygon endpoint: GET /v2/aggs/ticker/{ticker}/range/1/day/{from}/{to}
|
||||
pub fn fetchCandles(
|
||||
self: *Polygon,
|
||||
allocator: std.mem.Allocator,
|
||||
symbol: []const u8,
|
||||
from: Date,
|
||||
to: Date,
|
||||
) ![]Candle {
|
||||
self.rate_limiter.acquire();
|
||||
|
||||
var from_buf: [10]u8 = undefined;
|
||||
var to_buf: [10]u8 = undefined;
|
||||
const from_str = from.format(&from_buf);
|
||||
const to_str = to.format(&to_buf);
|
||||
|
||||
// Build URL manually since the path contains the date range
|
||||
const path = try std.fmt.allocPrint(
|
||||
allocator,
|
||||
"{s}/v2/aggs/ticker/{s}/range/1/day/{s}/{s}?adjusted=true&sort=asc&limit=5000",
|
||||
.{ base_url, symbol, from_str, to_str },
|
||||
);
|
||||
defer allocator.free(path);
|
||||
|
||||
const authed = try appendApiKey(allocator, path, self.api_key);
|
||||
defer allocator.free(authed);
|
||||
|
||||
var response = try self.client.get(authed);
|
||||
defer response.deinit();
|
||||
|
||||
return parseCandlesResponse(allocator, response.body);
|
||||
}
|
||||
};
|
||||
|
||||
// -- JSON parsing --
|
||||
|
|
@ -290,65 +255,6 @@ fn parseSplitsResponse(allocator: std.mem.Allocator, body: []const u8) ![]Split
|
|||
return try splits.toOwnedSlice(allocator);
|
||||
}
|
||||
|
||||
fn parseCandlesResponse(allocator: std.mem.Allocator, body: []const u8) ![]Candle {
|
||||
const parsed = std.json.parseFromSlice(std.json.Value, allocator, body, .{}) catch
|
||||
return error.ParseError;
|
||||
defer parsed.deinit();
|
||||
|
||||
const root = parsed.value.object;
|
||||
|
||||
if (root.get("status")) |s| {
|
||||
if (s == .string and std.mem.eql(u8, s.string, "ERROR"))
|
||||
return error.RequestFailed;
|
||||
}
|
||||
|
||||
const results = root.get("results") orelse {
|
||||
return try allocator.alloc(Candle, 0);
|
||||
};
|
||||
const items = switch (results) {
|
||||
.array => |a| a.items,
|
||||
else => {
|
||||
return try allocator.alloc(Candle, 0);
|
||||
},
|
||||
};
|
||||
|
||||
var candles: std.ArrayList(Candle) = .empty;
|
||||
errdefer candles.deinit(allocator);
|
||||
|
||||
for (items) |item| {
|
||||
const obj = switch (item) {
|
||||
.object => |o| o,
|
||||
else => continue,
|
||||
};
|
||||
|
||||
// Polygon returns timestamp in milliseconds
|
||||
const ts_ms = blk: {
|
||||
const v = obj.get("t") orelse continue;
|
||||
break :blk switch (v) {
|
||||
.integer => |i| i,
|
||||
.float => |f| @as(i64, @intFromFloat(f)),
|
||||
else => continue,
|
||||
};
|
||||
};
|
||||
const days: i32 = @intCast(@divFloor(ts_ms, 86400 * 1000));
|
||||
const date = Date{ .days = days };
|
||||
|
||||
const close = parseJsonFloat(obj.get("c"));
|
||||
|
||||
try candles.append(allocator, .{
|
||||
.date = date,
|
||||
.open = parseJsonFloat(obj.get("o")),
|
||||
.high = parseJsonFloat(obj.get("h")),
|
||||
.low = parseJsonFloat(obj.get("l")),
|
||||
.close = close,
|
||||
.adj_close = close, // Polygon adjusted=true gives adjusted values
|
||||
.volume = @intFromFloat(parseJsonFloat(obj.get("v"))),
|
||||
});
|
||||
}
|
||||
|
||||
return try candles.toOwnedSlice(allocator);
|
||||
}
|
||||
|
||||
// -- Helpers --
|
||||
|
||||
fn appendApiKey(allocator: std.mem.Allocator, url: []const u8, api_key: []const u8) ![]const u8 {
|
||||
|
|
@ -506,25 +412,3 @@ test "parseSplitsResponse empty results" {
|
|||
|
||||
try std.testing.expectEqual(@as(usize, 0), splits.len);
|
||||
}
|
||||
|
||||
test "parseCandlesResponse basic" {
|
||||
const body =
|
||||
\\{
|
||||
\\ "status": "OK",
|
||||
\\ "results": [
|
||||
\\ {"t": 1704067200000, "o": 185.5, "h": 186.2, "l": 184.1, "c": 185.8, "v": 52000000}
|
||||
\\ ]
|
||||
\\}
|
||||
;
|
||||
|
||||
const allocator = std.testing.allocator;
|
||||
const candles = try parseCandlesResponse(allocator, body);
|
||||
defer allocator.free(candles);
|
||||
|
||||
try std.testing.expectEqual(@as(usize, 1), candles.len);
|
||||
try std.testing.expectApproxEqAbs(@as(f64, 185.5), candles[0].open, 0.01);
|
||||
try std.testing.expectApproxEqAbs(@as(f64, 186.2), candles[0].high, 0.01);
|
||||
try std.testing.expectApproxEqAbs(@as(f64, 185.8), candles[0].close, 0.01);
|
||||
try std.testing.expectApproxEqAbs(candles[0].close, candles[0].adj_close, 0.01);
|
||||
try std.testing.expectEqual(@as(u64, 52000000), candles[0].volume);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue