special case for last close
This commit is contained in:
parent
2b62827bdb
commit
f7505936d2
3 changed files with 66 additions and 24 deletions
36
src/cache/store.zig
vendored
36
src/cache/store.zig
vendored
|
|
@ -215,6 +215,42 @@ pub const Store = struct {
|
|||
std.fs.cwd().deleteFile(path) catch {};
|
||||
}
|
||||
|
||||
/// Read the close price from the last candle record without parsing the entire file.
|
||||
/// Seeks to the end, reads the last ~256 bytes, and extracts `close:num:X`.
|
||||
/// Returns null if the file doesn't exist or has no candle data.
|
||||
pub fn readLastClose(self: *Store, symbol: []const u8) ?f64 {
|
||||
const path = self.symbolPath(symbol, DataType.candles_daily.fileName()) catch return null;
|
||||
defer self.allocator.free(path);
|
||||
|
||||
const file = std.fs.cwd().openFile(path, .{}) catch return null;
|
||||
defer file.close();
|
||||
|
||||
const stat = file.stat() catch return null;
|
||||
const file_size = stat.size;
|
||||
if (file_size < 20) return null; // too small to have candle data
|
||||
|
||||
// Read the last 256 bytes (one candle line is ~100 bytes, gives margin)
|
||||
const read_size: u64 = @min(256, file_size);
|
||||
file.seekTo(file_size - read_size) catch return null;
|
||||
|
||||
var buf: [256]u8 = undefined;
|
||||
const n = file.readAll(buf[0..@intCast(read_size)]) catch return null;
|
||||
const chunk = buf[0..n];
|
||||
|
||||
// Find the last complete line (skip trailing newline, then find the previous newline)
|
||||
const trimmed = std.mem.trimRight(u8, chunk, "\n");
|
||||
if (trimmed.len == 0) return null;
|
||||
const last_nl = std.mem.lastIndexOfScalar(u8, trimmed, '\n');
|
||||
const last_line = if (last_nl) |pos| trimmed[pos + 1 ..] else trimmed;
|
||||
|
||||
// Extract close:num:VALUE from the line
|
||||
const marker = "close:num:";
|
||||
const close_start = std.mem.indexOf(u8, last_line, marker) orelse return null;
|
||||
const val_start = close_start + marker.len;
|
||||
const val_end = std.mem.indexOfScalar(u8, last_line[val_start..], ',') orelse (last_line.len - val_start);
|
||||
return std.fmt.parseFloat(f64, last_line[val_start .. val_start + val_end]) catch null;
|
||||
}
|
||||
|
||||
/// Clear all cached data.
|
||||
pub fn clearAll(self: *Store) !void {
|
||||
std.fs.cwd().deleteTree(self.cache_dir) catch {};
|
||||
|
|
|
|||
|
|
@ -76,18 +76,15 @@ pub fn run(allocator: std.mem.Allocator, config: zfin.Config, svc: *zfin.DataSer
|
|||
const is_fresh = svc.isCandleCacheFresh(sym);
|
||||
|
||||
if (is_fresh and !force_refresh) {
|
||||
// Load from cache (no network)
|
||||
if (svc.getCachedCandles(sym)) |cs| {
|
||||
defer allocator.free(cs);
|
||||
if (cs.len > 0) {
|
||||
try prices.put(sym, cs[cs.len - 1].close);
|
||||
// Read only the last close price from cache (no full deserialization)
|
||||
if (svc.getCachedLastClose(sym)) |close| {
|
||||
try prices.put(sym, close);
|
||||
}
|
||||
// Cached (including negative cache entries with 0 candles)
|
||||
// Cached (including negative cache entries where getCachedLastClose returns null)
|
||||
cached_count += 1;
|
||||
try cli.stderrProgress(sym, " (cached)", loaded_count, all_syms_count, color);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Need to fetch from API
|
||||
const wait_s = svc.estimateWaitSeconds();
|
||||
|
|
@ -242,14 +239,15 @@ pub fn run(allocator: std.mem.Allocator, config: zfin.Config, svc: *zfin.DataSer
|
|||
try out.print(" Lots: {d} open, {d} closed Positions: {d} symbols\n", .{ open_lots, closed_lots, positions.len });
|
||||
try cli.reset(out, color);
|
||||
|
||||
// Historical portfolio value snapshots
|
||||
{
|
||||
// Build candle map once for historical snapshots and risk metrics.
|
||||
// This avoids parsing the full candle history multiple times.
|
||||
var candle_map = std.StringHashMap([]const zfin.Candle).init(allocator);
|
||||
defer {
|
||||
var it = candle_map.valueIterator();
|
||||
while (it.next()) |v| allocator.free(v.*);
|
||||
candle_map.deinit();
|
||||
}
|
||||
{
|
||||
const stock_syms = try portfolio.stockSymbols(allocator);
|
||||
defer allocator.free(stock_syms);
|
||||
for (stock_syms) |sym| {
|
||||
|
|
@ -257,6 +255,10 @@ pub fn run(allocator: std.mem.Allocator, config: zfin.Config, svc: *zfin.DataSer
|
|||
try candle_map.put(sym, cs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Historical portfolio value snapshots
|
||||
{
|
||||
if (candle_map.count() > 0) {
|
||||
const snapshots = zfin.risk.computeHistoricalSnapshots(
|
||||
fmt.todayDate(),
|
||||
|
|
@ -676,6 +678,7 @@ pub fn run(allocator: std.mem.Allocator, config: zfin.Config, svc: *zfin.DataSer
|
|||
// Helper to render a watch symbol
|
||||
const renderWatch = struct {
|
||||
fn f(o: *std.Io.Writer, c: bool, s: *zfin.DataService, a2: std.mem.Allocator, sym: []const u8, any: *bool) !void {
|
||||
_ = a2;
|
||||
if (!any.*) {
|
||||
try o.print("\n", .{});
|
||||
try cli.setBold(o, c);
|
||||
|
|
@ -685,11 +688,8 @@ pub fn run(allocator: std.mem.Allocator, config: zfin.Config, svc: *zfin.DataSer
|
|||
}
|
||||
var price_str2: [16]u8 = undefined;
|
||||
var ps2: []const u8 = "--";
|
||||
if (s.getCachedCandles(sym)) |candles2| {
|
||||
defer a2.free(candles2);
|
||||
if (candles2.len > 0) {
|
||||
ps2 = fmt.fmtMoney2(&price_str2, candles2[candles2.len - 1].close);
|
||||
}
|
||||
if (s.getCachedLastClose(sym)) |close| {
|
||||
ps2 = fmt.fmtMoney2(&price_str2, close);
|
||||
}
|
||||
try o.print(" " ++ fmt.sym_col_spec ++ " {s:>10}\n", .{ sym, ps2 });
|
||||
}
|
||||
|
|
@ -733,8 +733,7 @@ pub fn run(allocator: std.mem.Allocator, config: zfin.Config, svc: *zfin.DataSer
|
|||
var any_risk = false;
|
||||
|
||||
for (summary.allocations) |a| {
|
||||
if (svc.getCachedCandles(a.symbol)) |candles| {
|
||||
defer allocator.free(candles);
|
||||
if (candle_map.get(a.symbol)) |candles| {
|
||||
if (zfin.risk.computeRisk(candles)) |metrics| {
|
||||
if (!any_risk) {
|
||||
try out.print("\n", .{});
|
||||
|
|
|
|||
|
|
@ -394,6 +394,13 @@ pub const DataService = struct {
|
|||
return s.isFresh(symbol, .candles_daily) catch false;
|
||||
}
|
||||
|
||||
/// Read only the latest close price from cached candles (no full deserialization).
|
||||
/// Returns null if no cached data exists.
|
||||
pub fn getCachedLastClose(self: *DataService, symbol: []const u8) ?f64 {
|
||||
var s = self.store();
|
||||
return s.readLastClose(symbol);
|
||||
}
|
||||
|
||||
/// Estimate wait time (in seconds) before the next TwelveData API call can proceed.
|
||||
/// Returns 0 if a request can be made immediately. Returns null if no API key.
|
||||
pub fn estimateWaitSeconds(self: *DataService) ?u64 {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue