root.zig review
This commit is contained in:
parent
3e24d37040
commit
f936531721
7 changed files with 135 additions and 97 deletions
|
|
@ -3,7 +3,7 @@ const zfin = @import("../root.zig");
|
||||||
const cli = @import("common.zig");
|
const cli = @import("common.zig");
|
||||||
const fmt = cli.fmt;
|
const fmt = cli.fmt;
|
||||||
|
|
||||||
pub fn run(allocator: std.mem.Allocator, svc: *zfin.DataService, config: zfin.Config, symbol: []const u8, color: bool, out: *std.Io.Writer) !void {
|
pub fn run(allocator: std.mem.Allocator, svc: *zfin.DataService, symbol: []const u8, color: bool, out: *std.Io.Writer) !void {
|
||||||
const result = svc.getDividends(symbol) catch |err| switch (err) {
|
const result = svc.getDividends(symbol) catch |err| switch (err) {
|
||||||
zfin.DataError.NoApiKey => {
|
zfin.DataError.NoApiKey => {
|
||||||
try cli.stderrPrint("Error: POLYGON_API_KEY not set. Get a free key at https://polygon.io\n");
|
try cli.stderrPrint("Error: POLYGON_API_KEY not set. Get a free key at https://polygon.io\n");
|
||||||
|
|
@ -18,21 +18,11 @@ pub fn run(allocator: std.mem.Allocator, svc: *zfin.DataService, config: zfin.Co
|
||||||
|
|
||||||
if (result.source == .cached) try cli.stderrPrint("(using cached dividend data)\n");
|
if (result.source == .cached) try cli.stderrPrint("(using cached dividend data)\n");
|
||||||
|
|
||||||
// Fetch current price for yield calculation
|
// Fetch current price for yield calculation via DataService
|
||||||
var current_price: ?f64 = null;
|
var current_price: ?f64 = null;
|
||||||
if (config.twelvedata_key) |td_key| {
|
if (svc.getQuote(symbol)) |q| {
|
||||||
var td = zfin.TwelveData.init(allocator, td_key);
|
current_price = q.close;
|
||||||
defer td.deinit();
|
|
||||||
if (td.fetchQuote(allocator, symbol)) |qr_val| {
|
|
||||||
var qr = qr_val;
|
|
||||||
defer qr.deinit();
|
|
||||||
if (qr.parse(allocator)) |q_val| {
|
|
||||||
var q = q_val;
|
|
||||||
defer q.deinit();
|
|
||||||
current_price = q.close();
|
|
||||||
} else |_| {}
|
} else |_| {}
|
||||||
} else |_| {}
|
|
||||||
}
|
|
||||||
|
|
||||||
try display(result.data, symbol, current_price, color, out);
|
try display(result.data, symbol, current_price, color, out);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,7 @@ const cli = @import("common.zig");
|
||||||
/// Reads the portfolio, extracts stock symbols, fetches sector/industry/country for each,
|
/// Reads the portfolio, extracts stock symbols, fetches sector/industry/country for each,
|
||||||
/// and outputs a metadata SRF file to stdout.
|
/// and outputs a metadata SRF file to stdout.
|
||||||
/// If the argument looks like a symbol (no path separators, no .srf extension), enrich just that symbol.
|
/// If the argument looks like a symbol (no path separators, no .srf extension), enrich just that symbol.
|
||||||
pub fn run(allocator: std.mem.Allocator, config: zfin.Config, arg: []const u8, out: *std.Io.Writer) !void {
|
pub fn run(allocator: std.mem.Allocator, svc: *zfin.DataService, arg: []const u8, out: *std.Io.Writer) !void {
|
||||||
// Check for Alpha Vantage API key
|
|
||||||
const av_key = config.alphavantage_key orelse {
|
|
||||||
try cli.stderrPrint("Error: ALPHAVANTAGE_API_KEY not set. Add it to .env\n");
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Determine if arg is a symbol or a file path
|
// Determine if arg is a symbol or a file path
|
||||||
const is_file = std.mem.endsWith(u8, arg, ".srf") or
|
const is_file = std.mem.endsWith(u8, arg, ".srf") or
|
||||||
std.mem.indexOfScalar(u8, arg, '/') != null or
|
std.mem.indexOfScalar(u8, arg, '/') != null or
|
||||||
|
|
@ -20,27 +14,27 @@ pub fn run(allocator: std.mem.Allocator, config: zfin.Config, arg: []const u8, o
|
||||||
|
|
||||||
if (!is_file) {
|
if (!is_file) {
|
||||||
// Single symbol mode: enrich one symbol, output appendable SRF (no header)
|
// Single symbol mode: enrich one symbol, output appendable SRF (no header)
|
||||||
try enrichSymbol(allocator, av_key, arg, out);
|
try enrichSymbol(allocator, svc, arg, out);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Portfolio file mode: enrich all symbols
|
// Portfolio file mode: enrich all symbols
|
||||||
try enrichPortfolio(allocator, av_key, arg, out);
|
try enrichPortfolio(allocator, svc, arg, out);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Enrich a single symbol and output appendable SRF lines to stdout.
|
/// Enrich a single symbol and output appendable SRF lines to stdout.
|
||||||
fn enrichSymbol(allocator: std.mem.Allocator, av_key: []const u8, sym: []const u8, out: *std.Io.Writer) !void {
|
fn enrichSymbol(allocator: std.mem.Allocator, svc: *zfin.DataService, sym: []const u8, out: *std.Io.Writer) !void {
|
||||||
const AV = zfin.AlphaVantage;
|
|
||||||
var av = AV.init(allocator, av_key);
|
|
||||||
defer av.deinit();
|
|
||||||
|
|
||||||
{
|
{
|
||||||
var msg_buf: [128]u8 = undefined;
|
var msg_buf: [128]u8 = undefined;
|
||||||
const msg = std.fmt.bufPrint(&msg_buf, " Fetching {s}...\n", .{sym}) catch " ...\n";
|
const msg = std.fmt.bufPrint(&msg_buf, " Fetching {s}...\n", .{sym}) catch " ...\n";
|
||||||
try cli.stderrPrint(msg);
|
try cli.stderrPrint(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
const overview = av.fetchCompanyOverview(allocator, sym) catch {
|
const overview = svc.getCompanyOverview(sym) catch |err| {
|
||||||
|
if (err == zfin.DataError.NoApiKey) {
|
||||||
|
try cli.stderrPrint("Error: ALPHAVANTAGE_API_KEY not set. Add it to .env\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
try cli.stderrPrint("Error: Failed to fetch data for symbol\n");
|
try cli.stderrPrint("Error: Failed to fetch data for symbol\n");
|
||||||
try out.print("# {s} -- fetch failed\n", .{sym});
|
try out.print("# {s} -- fetch failed\n", .{sym});
|
||||||
try out.print("# symbol::{s},sector::TODO,geo::TODO,asset_class::TODO\n", .{sym});
|
try out.print("# symbol::{s},sector::TODO,geo::TODO,asset_class::TODO\n", .{sym});
|
||||||
|
|
@ -84,8 +78,7 @@ fn enrichSymbol(allocator: std.mem.Allocator, av_key: []const u8, sym: []const u
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Enrich all symbols from a portfolio file.
|
/// Enrich all symbols from a portfolio file.
|
||||||
fn enrichPortfolio(allocator: std.mem.Allocator, av_key: []const u8, file_path: []const u8, out: *std.Io.Writer) !void {
|
fn enrichPortfolio(allocator: std.mem.Allocator, svc: *zfin.DataService, file_path: []const u8, out: *std.Io.Writer) !void {
|
||||||
const AV = zfin.AlphaVantage;
|
|
||||||
|
|
||||||
// Load portfolio
|
// Load portfolio
|
||||||
const file_data = std.fs.cwd().readFileAlloc(allocator, file_path, 10 * 1024 * 1024) catch {
|
const file_data = std.fs.cwd().readFileAlloc(allocator, file_path, 10 * 1024 * 1024) catch {
|
||||||
|
|
@ -108,9 +101,6 @@ fn enrichPortfolio(allocator: std.mem.Allocator, av_key: []const u8, file_path:
|
||||||
const syms = try portfolio.stockSymbols(allocator);
|
const syms = try portfolio.stockSymbols(allocator);
|
||||||
defer allocator.free(syms);
|
defer allocator.free(syms);
|
||||||
|
|
||||||
var av = AV.init(allocator, av_key);
|
|
||||||
defer av.deinit();
|
|
||||||
|
|
||||||
try out.print("#!srfv1\n", .{});
|
try out.print("#!srfv1\n", .{});
|
||||||
try out.print("# Portfolio classification metadata\n", .{});
|
try out.print("# Portfolio classification metadata\n", .{});
|
||||||
try out.print("# Generated from Alpha Vantage OVERVIEW data\n", .{});
|
try out.print("# Generated from Alpha Vantage OVERVIEW data\n", .{});
|
||||||
|
|
@ -152,7 +142,7 @@ fn enrichPortfolio(allocator: std.mem.Allocator, av_key: []const u8, file_path:
|
||||||
try cli.stderrPrint(msg);
|
try cli.stderrPrint(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
const overview = av.fetchCompanyOverview(allocator, sym) catch {
|
const overview = svc.getCompanyOverview(sym) catch {
|
||||||
try out.print("# {s} -- fetch failed\n", .{sym});
|
try out.print("# {s} -- fetch failed\n", .{sym});
|
||||||
try out.print("# symbol::{s},sector::TODO,geo::TODO,asset_class::TODO\n\n", .{sym});
|
try out.print("# symbol::{s},sector::TODO,geo::TODO,asset_class::TODO\n\n", .{sym});
|
||||||
failed += 1;
|
failed += 1;
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ pub fn run(allocator: std.mem.Allocator, svc: *zfin.DataService, cusip: []const
|
||||||
try cli.stderrPrint("Looking up via OpenFIGI...\n");
|
try cli.stderrPrint("Looking up via OpenFIGI...\n");
|
||||||
|
|
||||||
// Try full batch lookup for richer output
|
// Try full batch lookup for richer output
|
||||||
const results = zfin.OpenFigi.lookupCusips(allocator, &.{cusip}, svc.config.openfigi_key) catch {
|
const results = svc.lookupCusips(&.{cusip}) catch {
|
||||||
try cli.stderrPrint("Error: OpenFIGI request failed (network error)\n");
|
try cli.stderrPrint("Error: OpenFIGI request failed (network error)\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
@ -38,7 +38,7 @@ pub fn run(allocator: std.mem.Allocator, svc: *zfin.DataService, cusip: []const
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn display(result: zfin.OpenFigi.FigiResult, cusip: []const u8, color: bool, out: *std.Io.Writer) !void {
|
pub fn display(result: zfin.CusipResult, cusip: []const u8, color: bool, out: *std.Io.Writer) !void {
|
||||||
if (result.ticker) |ticker| {
|
if (result.ticker) |ticker| {
|
||||||
try cli.setBold(out, color);
|
try cli.setBold(out, color);
|
||||||
try out.print("{s}", .{cusip});
|
try out.print("{s}", .{cusip});
|
||||||
|
|
@ -78,7 +78,7 @@ pub fn display(result: zfin.OpenFigi.FigiResult, cusip: []const u8, color: bool,
|
||||||
test "display shows ticker mapping" {
|
test "display shows ticker mapping" {
|
||||||
var buf: [4096]u8 = undefined;
|
var buf: [4096]u8 = undefined;
|
||||||
var w: std.Io.Writer = .fixed(&buf);
|
var w: std.Io.Writer = .fixed(&buf);
|
||||||
const result: zfin.OpenFigi.FigiResult = .{
|
const result: zfin.CusipResult = .{
|
||||||
.ticker = "AAPL",
|
.ticker = "AAPL",
|
||||||
.name = "Apple Inc",
|
.name = "Apple Inc",
|
||||||
.security_type = "Common Stock",
|
.security_type = "Common Stock",
|
||||||
|
|
@ -96,7 +96,7 @@ test "display shows ticker mapping" {
|
||||||
test "display shows no-ticker message" {
|
test "display shows no-ticker message" {
|
||||||
var buf: [4096]u8 = undefined;
|
var buf: [4096]u8 = undefined;
|
||||||
var w: std.Io.Writer = .fixed(&buf);
|
var w: std.Io.Writer = .fixed(&buf);
|
||||||
const result: zfin.OpenFigi.FigiResult = .{
|
const result: zfin.CusipResult = .{
|
||||||
.ticker = null,
|
.ticker = null,
|
||||||
.name = "Some Fund",
|
.name = "Some Fund",
|
||||||
.security_type = null,
|
.security_type = null,
|
||||||
|
|
@ -112,7 +112,7 @@ test "display shows no-ticker message" {
|
||||||
test "display no ANSI without color" {
|
test "display no ANSI without color" {
|
||||||
var buf: [4096]u8 = undefined;
|
var buf: [4096]u8 = undefined;
|
||||||
var w: std.Io.Writer = .fixed(&buf);
|
var w: std.Io.Writer = .fixed(&buf);
|
||||||
const result: zfin.OpenFigi.FigiResult = .{
|
const result: zfin.CusipResult = .{
|
||||||
.ticker = "MSFT",
|
.ticker = "MSFT",
|
||||||
.name = null,
|
.name = null,
|
||||||
.security_type = null,
|
.security_type = null,
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ pub const QuoteData = struct {
|
||||||
date: zfin.Date,
|
date: zfin.Date,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn run(allocator: std.mem.Allocator, config: zfin.Config, svc: *zfin.DataService, symbol: []const u8, color: bool, out: *std.Io.Writer) !void {
|
pub fn run(allocator: std.mem.Allocator, svc: *zfin.DataService, symbol: []const u8, color: bool, out: *std.Io.Writer) !void {
|
||||||
// Fetch candle data for chart and history
|
// Fetch candle data for chart and history
|
||||||
const candle_result = svc.getCandles(symbol) catch |err| switch (err) {
|
const candle_result = svc.getCandles(symbol) catch |err| switch (err) {
|
||||||
zfin.DataError.NoApiKey => {
|
zfin.DataError.NoApiKey => {
|
||||||
|
|
@ -29,30 +29,19 @@ pub fn run(allocator: std.mem.Allocator, config: zfin.Config, svc: *zfin.DataSer
|
||||||
defer allocator.free(candle_result.data);
|
defer allocator.free(candle_result.data);
|
||||||
const candles = candle_result.data;
|
const candles = candle_result.data;
|
||||||
|
|
||||||
// Fetch real-time quote
|
// Fetch real-time quote via DataService
|
||||||
var quote: ?QuoteData = null;
|
var quote: ?QuoteData = null;
|
||||||
|
if (svc.getQuote(symbol)) |q| {
|
||||||
if (config.twelvedata_key) |key| {
|
|
||||||
var td = zfin.TwelveData.init(allocator, key);
|
|
||||||
defer td.deinit();
|
|
||||||
if (td.fetchQuote(allocator, symbol)) |qr_val| {
|
|
||||||
var qr = qr_val;
|
|
||||||
defer qr.deinit();
|
|
||||||
if (qr.parse(allocator)) |q_val| {
|
|
||||||
var q = q_val;
|
|
||||||
defer q.deinit();
|
|
||||||
quote = .{
|
quote = .{
|
||||||
.price = q.close(),
|
.price = q.close,
|
||||||
.open = q.open(),
|
.open = q.open,
|
||||||
.high = q.high(),
|
.high = q.high,
|
||||||
.low = q.low(),
|
.low = q.low,
|
||||||
.volume = q.volume(),
|
.volume = q.volume,
|
||||||
.prev_close = q.previous_close(),
|
.prev_close = q.previous_close,
|
||||||
.date = if (candles.len > 0) candles[candles.len - 1].date else fmt.todayDate(),
|
.date = if (candles.len > 0) candles[candles.len - 1].date else fmt.todayDate(),
|
||||||
};
|
};
|
||||||
} else |_| {}
|
} else |_| {}
|
||||||
} else |_| {}
|
|
||||||
}
|
|
||||||
|
|
||||||
try display(allocator, candles, quote, symbol, color, out);
|
try display(allocator, candles, quote, symbol, color, out);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -114,7 +114,7 @@ pub fn main() !u8 {
|
||||||
try cli.stderrPrint("Error: 'quote' requires a symbol argument\n");
|
try cli.stderrPrint("Error: 'quote' requires a symbol argument\n");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
try commands.quote.run(allocator, config, &svc, args[2], color, out);
|
try commands.quote.run(allocator, &svc, args[2], color, out);
|
||||||
} else if (std.mem.eql(u8, command, "history")) {
|
} else if (std.mem.eql(u8, command, "history")) {
|
||||||
if (args.len < 3) {
|
if (args.len < 3) {
|
||||||
try cli.stderrPrint("Error: 'history' requires a symbol argument\n");
|
try cli.stderrPrint("Error: 'history' requires a symbol argument\n");
|
||||||
|
|
@ -126,7 +126,7 @@ pub fn main() !u8 {
|
||||||
try cli.stderrPrint("Error: 'divs' requires a symbol argument\n");
|
try cli.stderrPrint("Error: 'divs' requires a symbol argument\n");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
try commands.divs.run(allocator, &svc, config, args[2], color, out);
|
try commands.divs.run(allocator, &svc, args[2], color, out);
|
||||||
} else if (std.mem.eql(u8, command, "splits")) {
|
} else if (std.mem.eql(u8, command, "splits")) {
|
||||||
if (args.len < 3) {
|
if (args.len < 3) {
|
||||||
try cli.stderrPrint("Error: 'splits' requires a symbol argument\n");
|
try cli.stderrPrint("Error: 'splits' requires a symbol argument\n");
|
||||||
|
|
@ -196,7 +196,7 @@ pub fn main() !u8 {
|
||||||
try cli.stderrPrint("Error: 'enrich' requires a portfolio file path or symbol\n");
|
try cli.stderrPrint("Error: 'enrich' requires a portfolio file path or symbol\n");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
try commands.enrich.run(allocator, config, args[2], out);
|
try commands.enrich.run(allocator, &svc, args[2], out);
|
||||||
} else if (std.mem.eql(u8, command, "analysis")) {
|
} else if (std.mem.eql(u8, command, "analysis")) {
|
||||||
// File path is first non-flag arg (default: portfolio.srf)
|
// File path is first non-flag arg (default: portfolio.srf)
|
||||||
var analysis_file: []const u8 = "portfolio.srf";
|
var analysis_file: []const u8 = "portfolio.srf";
|
||||||
|
|
|
||||||
104
src/root.zig
104
src/root.zig
|
|
@ -3,62 +3,108 @@
|
||||||
//! Fetches, caches, and analyzes US equity/ETF financial data from
|
//! Fetches, caches, and analyzes US equity/ETF financial data from
|
||||||
//! multiple free-tier API providers (Twelve Data, Polygon, Finnhub,
|
//! multiple free-tier API providers (Twelve Data, Polygon, Finnhub,
|
||||||
//! Alpha Vantage). Includes Morningstar-style performance calculations.
|
//! Alpha Vantage). Includes Morningstar-style performance calculations.
|
||||||
|
//!
|
||||||
|
//! ## Getting Started
|
||||||
|
//!
|
||||||
|
//! Most consumers should start with `DataService`, which orchestrates
|
||||||
|
//! fetching, caching, and provider selection. Pair it with a `Config`
|
||||||
|
//! (populated from environment variables / .env) to get going:
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//! const config = try zfin.Config.load(allocator);
|
||||||
|
//! var svc = zfin.DataService.init(allocator, config);
|
||||||
|
//! const result = try svc.getCandles("AAPL");
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! For portfolio workflows, load a Portfolio from an SRF file and pass
|
||||||
|
//! it through `risk` and `performance` for analytics.
|
||||||
|
//!
|
||||||
|
//! The `format` module contains shared rendering helpers used by both
|
||||||
|
//! the CLI commands and TUI.
|
||||||
|
|
||||||
// -- Data models --
|
// ── Data Models ──────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// Calendar date with financial-market helpers (trading days, expiry rules).
|
||||||
pub const Date = @import("models/date.zig").Date;
|
pub const Date = @import("models/date.zig").Date;
|
||||||
|
|
||||||
|
/// OHLCV price bar (open, high, low, close, volume) for a single trading day.
|
||||||
pub const Candle = @import("models/candle.zig").Candle;
|
pub const Candle = @import("models/candle.zig").Candle;
|
||||||
|
|
||||||
|
/// Cash dividend payment with ex-date, pay-date, and amount.
|
||||||
pub const Dividend = @import("models/dividend.zig").Dividend;
|
pub const Dividend = @import("models/dividend.zig").Dividend;
|
||||||
pub const DividendType = @import("models/dividend.zig").DividendType;
|
|
||||||
|
/// Stock split event (ratio + effective date).
|
||||||
pub const Split = @import("models/split.zig").Split;
|
pub const Split = @import("models/split.zig").Split;
|
||||||
|
|
||||||
|
/// Single options contract (strike, expiry, greeks, bid/ask).
|
||||||
pub const OptionContract = @import("models/option.zig").OptionContract;
|
pub const OptionContract = @import("models/option.zig").OptionContract;
|
||||||
|
|
||||||
|
/// Full options chain for a symbol (calls + puts grouped by expiry).
|
||||||
pub const OptionsChain = @import("models/option.zig").OptionsChain;
|
pub const OptionsChain = @import("models/option.zig").OptionsChain;
|
||||||
pub const ContractType = @import("models/option.zig").ContractType;
|
|
||||||
|
/// Quarterly earnings event with EPS estimate, actual, and surprise.
|
||||||
pub const EarningsEvent = @import("models/earnings.zig").EarningsEvent;
|
pub const EarningsEvent = @import("models/earnings.zig").EarningsEvent;
|
||||||
pub const ReportTime = @import("models/earnings.zig").ReportTime;
|
|
||||||
|
/// ETF profile: expense ratio, AUM, top holdings, sector weights.
|
||||||
pub const EtfProfile = @import("models/etf_profile.zig").EtfProfile;
|
pub const EtfProfile = @import("models/etf_profile.zig").EtfProfile;
|
||||||
pub const Holding = @import("models/etf_profile.zig").Holding;
|
|
||||||
pub const SectorWeight = @import("models/etf_profile.zig").SectorWeight;
|
/// A single position lot (shares, cost basis, purchase date, type).
|
||||||
pub const TickerInfo = @import("models/ticker_info.zig").TickerInfo;
|
|
||||||
pub const SecurityType = @import("models/ticker_info.zig").SecurityType;
|
|
||||||
pub const Lot = @import("models/portfolio.zig").Lot;
|
pub const Lot = @import("models/portfolio.zig").Lot;
|
||||||
pub const LotType = @import("models/portfolio.zig").LotType;
|
|
||||||
|
/// Aggregated position: symbol + all its lots.
|
||||||
pub const Position = @import("models/portfolio.zig").Position;
|
pub const Position = @import("models/portfolio.zig").Position;
|
||||||
|
|
||||||
|
/// Parsed portfolio: positions, cash, CDs, options, metadata.
|
||||||
pub const Portfolio = @import("models/portfolio.zig").Portfolio;
|
pub const Portfolio = @import("models/portfolio.zig").Portfolio;
|
||||||
|
|
||||||
|
/// Real-time or delayed price quote (last, bid, ask, volume).
|
||||||
pub const Quote = @import("models/quote.zig").Quote;
|
pub const Quote = @import("models/quote.zig").Quote;
|
||||||
|
|
||||||
// -- Infrastructure --
|
// ── Infrastructure ───────────────────────────────────────────
|
||||||
pub const Config = @import("config.zig").Config;
|
|
||||||
pub const RateLimiter = @import("net/rate_limiter.zig").RateLimiter;
|
|
||||||
pub const http = @import("net/http.zig");
|
|
||||||
|
|
||||||
// -- Cache --
|
/// Runtime configuration loaded from environment / .env file (API keys, paths).
|
||||||
|
pub const Config = @import("config.zig").Config;
|
||||||
|
|
||||||
|
// ── Cache ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// SRF-backed file cache for candles, dividends, earnings, and other fetched data.
|
||||||
pub const cache = @import("cache/store.zig");
|
pub const cache = @import("cache/store.zig");
|
||||||
|
|
||||||
// -- Analytics --
|
// ── Analytics ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// Morningstar-style holding-period return calculations (total + annualized).
|
||||||
pub const performance = @import("analytics/performance.zig");
|
pub const performance = @import("analytics/performance.zig");
|
||||||
|
|
||||||
|
/// Portfolio risk metrics: sector weights, fallback prices, DRIP aggregation.
|
||||||
pub const risk = @import("analytics/risk.zig");
|
pub const risk = @import("analytics/risk.zig");
|
||||||
|
|
||||||
|
/// Technical indicators: SMA, EMA, Bollinger Bands, RSI, MACD.
|
||||||
pub const indicators = @import("analytics/indicators.zig");
|
pub const indicators = @import("analytics/indicators.zig");
|
||||||
|
|
||||||
|
/// Fundamental analysis: valuation, momentum, quality, and yield scoring.
|
||||||
pub const analysis = @import("analytics/analysis.zig");
|
pub const analysis = @import("analytics/analysis.zig");
|
||||||
|
|
||||||
// -- Classification --
|
// ── Classification ───────────────────────────────────────────
|
||||||
|
|
||||||
|
/// Sector/industry/country classification for enriched securities.
|
||||||
pub const classification = @import("models/classification.zig");
|
pub const classification = @import("models/classification.zig");
|
||||||
|
|
||||||
// -- Formatting (shared between CLI and TUI) --
|
// ── Formatting ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// Shared rendering helpers (money formatting, charts, earnings rows, bars)
|
||||||
|
/// used by both CLI commands and TUI.
|
||||||
pub const format = @import("format.zig");
|
pub const format = @import("format.zig");
|
||||||
|
|
||||||
// -- Service layer --
|
// ── Service Layer ────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// High-level data service: orchestrates providers, caching, and fallback logic.
|
||||||
pub const DataService = @import("service.zig").DataService;
|
pub const DataService = @import("service.zig").DataService;
|
||||||
|
|
||||||
|
/// Errors returned by DataService (NoApiKey, RateLimited, etc.).
|
||||||
pub const DataError = @import("service.zig").DataError;
|
pub const DataError = @import("service.zig").DataError;
|
||||||
pub const DataSource = @import("service.zig").Source;
|
|
||||||
|
|
||||||
// -- Providers --
|
/// Company overview data (sector, industry, country, market cap) from Alpha Vantage.
|
||||||
pub const Provider = @import("providers/provider.zig").Provider;
|
pub const CompanyOverview = @import("service.zig").CompanyOverview;
|
||||||
pub const TwelveData = @import("providers/twelvedata.zig").TwelveData;
|
|
||||||
pub const Polygon = @import("providers/polygon.zig").Polygon;
|
|
||||||
pub const Finnhub = @import("providers/finnhub.zig").Finnhub;
|
|
||||||
pub const Cboe = @import("providers/cboe.zig").Cboe;
|
|
||||||
pub const AlphaVantage = @import("providers/alphavantage.zig").AlphaVantage;
|
|
||||||
pub const OpenFigi = @import("providers/openfigi.zig");
|
|
||||||
|
|
||||||
// -- Re-export SRF for portfolio file loading --
|
/// Result of a CUSIP-to-ticker lookup (ticker, name, security type).
|
||||||
pub const srf = @import("srf");
|
pub const CusipResult = @import("service.zig").CusipResult;
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ const Polygon = @import("providers/polygon.zig").Polygon;
|
||||||
const Finnhub = @import("providers/finnhub.zig").Finnhub;
|
const Finnhub = @import("providers/finnhub.zig").Finnhub;
|
||||||
const Cboe = @import("providers/cboe.zig").Cboe;
|
const Cboe = @import("providers/cboe.zig").Cboe;
|
||||||
const AlphaVantage = @import("providers/alphavantage.zig").AlphaVantage;
|
const AlphaVantage = @import("providers/alphavantage.zig").AlphaVantage;
|
||||||
|
const alphavantage = @import("providers/alphavantage.zig");
|
||||||
const OpenFigi = @import("providers/openfigi.zig");
|
const OpenFigi = @import("providers/openfigi.zig");
|
||||||
const performance = @import("analytics/performance.zig");
|
const performance = @import("analytics/performance.zig");
|
||||||
|
|
||||||
|
|
@ -34,6 +35,12 @@ pub const DataError = error{
|
||||||
OutOfMemory,
|
OutOfMemory,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Re-exported provider types needed by commands via DataService.
|
||||||
|
pub const CompanyOverview = alphavantage.CompanyOverview;
|
||||||
|
|
||||||
|
/// Result of a CUSIP-to-ticker lookup (provider-agnostic).
|
||||||
|
pub const CusipResult = OpenFigi.FigiResult;
|
||||||
|
|
||||||
/// Indicates whether the returned data came from cache or was freshly fetched.
|
/// Indicates whether the returned data came from cache or was freshly fetched.
|
||||||
pub const Source = enum {
|
pub const Source = enum {
|
||||||
cached,
|
cached,
|
||||||
|
|
@ -333,6 +340,14 @@ pub const DataService = struct {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fetch company overview (sector, industry, country, market cap) from Alpha Vantage.
|
||||||
|
/// No cache -- always fetches fresh. Caller must free the returned string fields.
|
||||||
|
pub fn getCompanyOverview(self: *DataService, symbol: []const u8) DataError!CompanyOverview {
|
||||||
|
var av = try self.getAlphaVantage();
|
||||||
|
return av.fetchCompanyOverview(self.allocator, symbol) catch
|
||||||
|
return DataError.FetchFailed;
|
||||||
|
}
|
||||||
|
|
||||||
/// Compute trailing returns for a symbol (fetches candles + dividends).
|
/// Compute trailing returns for a symbol (fetches candles + dividends).
|
||||||
/// Returns both as-of-date and month-end trailing returns.
|
/// Returns both as-of-date and month-end trailing returns.
|
||||||
/// As-of-date: end = latest close. Matches Morningstar "Trailing Returns" page.
|
/// As-of-date: end = latest close. Matches Morningstar "Trailing Returns" page.
|
||||||
|
|
@ -565,6 +580,14 @@ pub const DataService = struct {
|
||||||
|
|
||||||
// ── CUSIP Resolution ──────────────────────────────────────────
|
// ── CUSIP Resolution ──────────────────────────────────────────
|
||||||
|
|
||||||
|
/// Look up multiple CUSIPs in a single batch request via OpenFIGI.
|
||||||
|
/// Results array is parallel to the input cusips array (same length, same order).
|
||||||
|
/// Caller owns the returned slice and all strings within each CusipResult.
|
||||||
|
pub fn lookupCusips(self: *DataService, cusips: []const []const u8) DataError![]CusipResult {
|
||||||
|
return OpenFigi.lookupCusips(self.allocator, cusips, self.config.openfigi_key) catch
|
||||||
|
return DataError.FetchFailed;
|
||||||
|
}
|
||||||
|
|
||||||
/// Look up a CUSIP via OpenFIGI API. Returns the ticker if found, null otherwise.
|
/// Look up a CUSIP via OpenFIGI API. Returns the ticker if found, null otherwise.
|
||||||
/// Results are cached in {cache_dir}/cusip_tickers.srf.
|
/// Results are cached in {cache_dir}/cusip_tickers.srf.
|
||||||
/// Caller owns the returned string.
|
/// Caller owns the returned string.
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue