ai: portfolio refactor
This commit is contained in:
parent
f7505936d2
commit
73b96f7399
1 changed files with 114 additions and 79 deletions
|
|
@ -205,6 +205,107 @@ pub fn run(allocator: std.mem.Allocator, config: zfin.Config, svc: *zfin.DataSer
|
|||
}
|
||||
}
|
||||
|
||||
// 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| {
|
||||
if (svc.getCachedCandles(sym)) |cs| {
|
||||
try candle_map.put(sym, cs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Collect watch symbols and their prices for display.
|
||||
// Includes watch lots from portfolio + symbols from separate watchlist file.
|
||||
var watch_list: std.ArrayList([]const u8) = .empty;
|
||||
defer watch_list.deinit(allocator);
|
||||
var watch_prices = std.StringHashMap(f64).init(allocator);
|
||||
defer watch_prices.deinit();
|
||||
{
|
||||
var watch_seen = std.StringHashMap(void).init(allocator);
|
||||
defer watch_seen.deinit();
|
||||
// Exclude portfolio position symbols from watchlist
|
||||
for (summary.allocations) |a| {
|
||||
try watch_seen.put(a.symbol, {});
|
||||
}
|
||||
|
||||
// Watch lots from portfolio
|
||||
for (portfolio.lots) |lot| {
|
||||
if (lot.lot_type == .watch) {
|
||||
const sym = lot.priceSymbol();
|
||||
if (watch_seen.contains(sym)) continue;
|
||||
try watch_seen.put(sym, {});
|
||||
try watch_list.append(allocator, sym);
|
||||
if (svc.getCachedLastClose(sym)) |close| {
|
||||
try watch_prices.put(sym, close);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Separate watchlist file (backward compat)
|
||||
if (watchlist_path) |wl_path| {
|
||||
const wl_data = std.fs.cwd().readFileAlloc(allocator, wl_path, 1024 * 1024) catch null;
|
||||
if (wl_data) |wd| {
|
||||
defer allocator.free(wd);
|
||||
var wl_lines = std.mem.splitScalar(u8, wd, '\n');
|
||||
while (wl_lines.next()) |line| {
|
||||
const trimmed = std.mem.trim(u8, line, &std.ascii.whitespace);
|
||||
if (trimmed.len == 0 or trimmed[0] == '#') continue;
|
||||
if (std.mem.indexOf(u8, trimmed, "symbol::")) |idx| {
|
||||
const rest = trimmed[idx + "symbol::".len ..];
|
||||
const end = std.mem.indexOfScalar(u8, rest, ',') orelse rest.len;
|
||||
const sym = std.mem.trim(u8, rest[0..end], &std.ascii.whitespace);
|
||||
if (sym.len > 0 and sym.len <= 10) {
|
||||
if (watch_seen.contains(sym)) continue;
|
||||
try watch_seen.put(sym, {});
|
||||
try watch_list.append(allocator, sym);
|
||||
if (svc.getCachedLastClose(sym)) |close| {
|
||||
try watch_prices.put(sym, close);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try display(
|
||||
allocator,
|
||||
out,
|
||||
color,
|
||||
file_path,
|
||||
&portfolio,
|
||||
positions,
|
||||
&summary,
|
||||
prices,
|
||||
candle_map,
|
||||
watch_list.items,
|
||||
watch_prices,
|
||||
);
|
||||
}
|
||||
|
||||
/// Render the full portfolio display. All data is pre-fetched; no service calls.
|
||||
pub fn display(
|
||||
allocator: std.mem.Allocator,
|
||||
out: *std.Io.Writer,
|
||||
color: bool,
|
||||
file_path: []const u8,
|
||||
portfolio: *const zfin.Portfolio,
|
||||
positions: []const zfin.Position,
|
||||
summary: *const zfin.risk.PortfolioSummary,
|
||||
prices: std.StringHashMap(f64),
|
||||
candle_map: std.StringHashMap([]const zfin.Candle),
|
||||
watch_symbols: []const []const u8,
|
||||
watch_prices: std.StringHashMap(f64),
|
||||
) !void {
|
||||
// Header with summary
|
||||
try cli.setBold(out, color);
|
||||
try out.print("\nPortfolio Summary ({s})\n", .{file_path});
|
||||
|
|
@ -239,24 +340,6 @@ 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);
|
||||
|
||||
// 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| {
|
||||
if (svc.getCachedCandles(sym)) |cs| {
|
||||
try candle_map.put(sym, cs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Historical portfolio value snapshots
|
||||
{
|
||||
if (candle_map.count() > 0) {
|
||||
|
|
@ -664,67 +747,19 @@ pub fn run(allocator: std.mem.Allocator, config: zfin.Config, svc: *zfin.DataSer
|
|||
try cli.reset(out, color);
|
||||
}
|
||||
|
||||
// Watchlist (from watch lots in portfolio + separate watchlist file)
|
||||
{
|
||||
var any_watch = false;
|
||||
var watch_seen = std.StringHashMap(void).init(allocator);
|
||||
defer watch_seen.deinit();
|
||||
|
||||
// Mark portfolio position symbols as seen
|
||||
for (summary.allocations) |a| {
|
||||
try watch_seen.put(a.symbol, {});
|
||||
}
|
||||
|
||||
// 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);
|
||||
try o.print(" Watchlist:\n", .{});
|
||||
try cli.reset(o, c);
|
||||
any.* = true;
|
||||
}
|
||||
var price_str2: [16]u8 = undefined;
|
||||
var ps2: []const u8 = "--";
|
||||
if (s.getCachedLastClose(sym)) |close| {
|
||||
ps2 = fmt.fmtMoney2(&price_str2, close);
|
||||
}
|
||||
try o.print(" " ++ fmt.sym_col_spec ++ " {s:>10}\n", .{ sym, ps2 });
|
||||
}
|
||||
}.f;
|
||||
|
||||
// Watch lots from portfolio
|
||||
for (portfolio.lots) |lot| {
|
||||
if (lot.lot_type == .watch) {
|
||||
if (watch_seen.contains(lot.priceSymbol())) continue;
|
||||
try watch_seen.put(lot.priceSymbol(), {});
|
||||
try renderWatch(out, color, svc, allocator, lot.priceSymbol(), &any_watch);
|
||||
}
|
||||
}
|
||||
|
||||
// Separate watchlist file (backward compat)
|
||||
if (watchlist_path) |wl_path| {
|
||||
const wl_data = std.fs.cwd().readFileAlloc(allocator, wl_path, 1024 * 1024) catch null;
|
||||
if (wl_data) |wd| {
|
||||
defer allocator.free(wd);
|
||||
var wl_lines = std.mem.splitScalar(u8, wd, '\n');
|
||||
while (wl_lines.next()) |line| {
|
||||
const trimmed = std.mem.trim(u8, line, &std.ascii.whitespace);
|
||||
if (trimmed.len == 0 or trimmed[0] == '#') continue;
|
||||
if (std.mem.indexOf(u8, trimmed, "symbol::")) |idx| {
|
||||
const rest = trimmed[idx + "symbol::".len ..];
|
||||
const end = std.mem.indexOfScalar(u8, rest, ',') orelse rest.len;
|
||||
const sym = std.mem.trim(u8, rest[0..end], &std.ascii.whitespace);
|
||||
if (sym.len > 0 and sym.len <= 10) {
|
||||
if (watch_seen.contains(sym)) continue;
|
||||
try watch_seen.put(sym, {});
|
||||
try renderWatch(out, color, svc, allocator, sym, &any_watch);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Watchlist
|
||||
if (watch_symbols.len > 0) {
|
||||
try out.print("\n", .{});
|
||||
try cli.setBold(out, color);
|
||||
try out.print(" Watchlist:\n", .{});
|
||||
try cli.reset(out, color);
|
||||
for (watch_symbols) |sym| {
|
||||
var price_str: [16]u8 = undefined;
|
||||
const ps: []const u8 = if (watch_prices.get(sym)) |close|
|
||||
fmt.fmtMoney2(&price_str, close)
|
||||
else
|
||||
"--";
|
||||
try out.print(" " ++ fmt.sym_col_spec ++ " {s:>10}\n", .{ sym, ps });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue