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
|
//! Polygon.io API provider -- primary source for dividend/split reference data.
|
||||||
//! and secondary source for daily OHLCV bars.
|
|
||||||
//! API docs: https://polygon.io/docs
|
//! 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.
|
//! Dividends and splits are available for all history.
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const http = @import("../net/http.zig");
|
const http = @import("../net/http.zig");
|
||||||
const RateLimiter = @import("../net/RateLimiter.zig");
|
const RateLimiter = @import("../net/RateLimiter.zig");
|
||||||
const Date = @import("../models/date.zig").Date;
|
const Date = @import("../models/date.zig").Date;
|
||||||
const Candle = @import("../models/candle.zig").Candle;
|
|
||||||
const Dividend = @import("../models/dividend.zig").Dividend;
|
const Dividend = @import("../models/dividend.zig").Dividend;
|
||||||
const DividendType = @import("../models/dividend.zig").DividendType;
|
const DividendType = @import("../models/dividend.zig").DividendType;
|
||||||
const Split = @import("../models/split.zig").Split;
|
const Split = @import("../models/split.zig").Split;
|
||||||
|
|
@ -135,39 +133,6 @@ pub const Polygon = struct {
|
||||||
|
|
||||||
return parseSplitsResponse(allocator, response.body);
|
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 --
|
// -- JSON parsing --
|
||||||
|
|
@ -290,65 +255,6 @@ fn parseSplitsResponse(allocator: std.mem.Allocator, body: []const u8) ![]Split
|
||||||
return try splits.toOwnedSlice(allocator);
|
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 --
|
// -- Helpers --
|
||||||
|
|
||||||
fn appendApiKey(allocator: std.mem.Allocator, url: []const u8, api_key: []const u8) ![]const u8 {
|
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);
|
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