ignore option mark to market in schwab

This commit is contained in:
Emil Lerch 2026-04-25 09:07:13 -07:00
parent 648a173fb1
commit a9321c343c

View file

@ -710,6 +710,7 @@ pub const SymbolComparison = struct {
shares_delta: ?f64, shares_delta: ?f64,
value_delta: ?f64, value_delta: ?f64,
is_cash: bool, is_cash: bool,
is_option: bool,
only_in_brokerage: bool, only_in_brokerage: bool,
only_in_portfolio: bool, only_in_portfolio: bool,
}; };
@ -723,6 +724,7 @@ pub const AccountComparison = struct {
portfolio_total: f64, portfolio_total: f64,
brokerage_total: f64, brokerage_total: f64,
total_delta: f64, total_delta: f64,
option_value_delta: f64,
has_discrepancies: bool, has_discrepancies: bool,
}; };
@ -769,6 +771,7 @@ pub fn compareAccounts(
var portfolio_total: f64 = 0; var portfolio_total: f64 = 0;
var brokerage_total: f64 = 0; var brokerage_total: f64 = 0;
var option_value_delta: f64 = 0;
var has_discrepancies = false; var has_discrepancies = false;
// Track which portfolio symbols we've matched // Track which portfolio symbols we've matched
@ -793,6 +796,7 @@ pub fn compareAccounts(
.shares_delta = if (bp.quantity) |q| q else null, .shares_delta = if (bp.quantity) |q| q else null,
.value_delta = bp.current_value, .value_delta = bp.current_value,
.is_cash = bp.is_cash, .is_cash = bp.is_cash,
.is_option = false,
.only_in_brokerage = true, .only_in_brokerage = true,
.only_in_portfolio = false, .only_in_portfolio = false,
}); });
@ -804,6 +808,7 @@ pub fn compareAccounts(
var pf_shares: f64 = 0; var pf_shares: f64 = 0;
var pf_value: f64 = 0; var pf_value: f64 = 0;
var pf_price: ?f64 = null; var pf_price: ?f64 = null;
var is_option = false;
if (bp.is_cash) { if (bp.is_cash) {
pf_shares = portfolio.cashForAccount(portfolio_acct_name.?); pf_shares = portfolio.cashForAccount(portfolio_acct_name.?);
@ -842,6 +847,7 @@ pub fn compareAccounts(
pf_shares += lot.shares; pf_shares += lot.shares;
pf_value += @abs(lot.shares) * lot.open_price * lot.multiplier; pf_value += @abs(lot.shares) * lot.open_price * lot.multiplier;
pf_price = lot.open_price * lot.multiplier; pf_price = lot.open_price * lot.multiplier;
is_option = true;
}, },
else => {}, else => {},
} }
@ -859,7 +865,14 @@ pub fn compareAccounts(
const shares_match = if (shares_delta) |d| @abs(d) < 0.01 else true; const shares_match = if (shares_delta) |d| @abs(d) < 0.01 else true;
const value_match = if (value_delta) |d| @abs(d) < 1.0 else true; const value_match = if (value_delta) |d| @abs(d) < 1.0 else true;
if (!shares_match or !value_match) has_discrepancies = true; // Option value deltas are expected (cost basis vs mark-to-market)
// track them separately rather than flagging as discrepancies
if (is_option) {
if (value_delta) |d| option_value_delta += d;
if (!shares_match) has_discrepancies = true;
} else {
if (!shares_match or !value_match) has_discrepancies = true;
}
const br_price: ?f64 = if (bp.quantity) |q| if (bp.current_value) |v| if (q != 0) v / q else null else null else null; const br_price: ?f64 = if (bp.quantity) |q| if (bp.current_value) |v| if (q != 0) v / q else null else null else null;
@ -874,6 +887,7 @@ pub fn compareAccounts(
.shares_delta = shares_delta, .shares_delta = shares_delta,
.value_delta = value_delta, .value_delta = value_delta,
.is_cash = bp.is_cash, .is_cash = bp.is_cash,
.is_option = is_option,
.only_in_brokerage = pf_shares == 0 and pf_value == 0, .only_in_brokerage = pf_shares == 0 and pf_value == 0,
.only_in_portfolio = false, .only_in_portfolio = false,
}); });
@ -906,6 +920,7 @@ pub fn compareAccounts(
.shares_delta = null, .shares_delta = null,
.value_delta = null, .value_delta = null,
.is_cash = false, .is_cash = false,
.is_option = false,
.only_in_brokerage = false, .only_in_brokerage = false,
.only_in_portfolio = true, .only_in_portfolio = true,
}); });
@ -962,6 +977,7 @@ pub fn compareAccounts(
.shares_delta = null, .shares_delta = null,
.value_delta = null, .value_delta = null,
.is_cash = is_cd, .is_cash = is_cd,
.is_option = !is_cd,
.only_in_brokerage = false, .only_in_brokerage = false,
.only_in_portfolio = true, .only_in_portfolio = true,
}); });
@ -977,6 +993,7 @@ pub fn compareAccounts(
.portfolio_total = portfolio_total, .portfolio_total = portfolio_total,
.brokerage_total = brokerage_total, .brokerage_total = brokerage_total,
.total_delta = brokerage_total - portfolio_total, .total_delta = brokerage_total - portfolio_total,
.option_value_delta = option_value_delta,
.has_discrepancies = has_discrepancies, .has_discrepancies = has_discrepancies,
}); });
} }
@ -997,6 +1014,7 @@ fn displayResults(results: []const AccountComparison, color: bool, out: *std.Io.
var total_portfolio: f64 = 0; var total_portfolio: f64 = 0;
var total_brokerage: f64 = 0; var total_brokerage: f64 = 0;
var total_option_delta: f64 = 0;
var discrepancy_count: usize = 0; var discrepancy_count: usize = 0;
for (results) |acct| { for (results) |acct| {
@ -1096,6 +1114,10 @@ fn displayResults(results: []const AccountComparison, color: bool, out: *std.Io.
} }
} }
// Options: shares match is sufficient value delta is expected
// (cost basis vs mark-to-market) and not actionable
if (cmp.is_option) break :blk "Option";
// Shares match show value delta (stale price) if any, muted // Shares match show value delta (stale price) if any, muted
if (cmp.value_delta) |d| { if (cmp.value_delta) |d| {
if (@abs(d) >= 1.0) { if (@abs(d) >= 1.0) {
@ -1151,23 +1173,32 @@ fn displayResults(results: []const AccountComparison, color: bool, out: *std.Io.
}); });
try cli.reset(out, color); try cli.reset(out, color);
const delta = acct.total_delta; const adj_delta = acct.total_delta - acct.option_value_delta;
if (@abs(delta) < 1.0) { if (@abs(adj_delta) < 1.0) {
try out.print("\n", .{}); // no delta text needed
} else { } else {
const sign: []const u8 = if (delta >= 0) "+" else "-"; const sign: []const u8 = if (adj_delta >= 0) "+" else "-";
if (delta >= 0) { if (adj_delta >= 0) {
try cli.setFg(out, color, cli.CLR_NEGATIVE); try cli.setFg(out, color, cli.CLR_NEGATIVE);
} else { } else {
try cli.setFg(out, color, cli.CLR_POSITIVE); try cli.setFg(out, color, cli.CLR_POSITIVE);
} }
try out.print("Delta {s}{s}\n", .{ sign, fmt.fmtMoneyAbs(&delta_buf, @abs(delta)) }); try out.print("Delta {s}{s}", .{ sign, fmt.fmtMoneyAbs(&delta_buf, @abs(adj_delta)) });
try cli.reset(out, color); try cli.reset(out, color);
} }
try out.print("\n", .{});
if (@abs(acct.option_value_delta) >= 1.0) {
var opt_delta_buf: [24]u8 = undefined;
const opt_sign: []const u8 = if (acct.option_value_delta >= 0) "+" else "-";
try cli.setFg(out, color, cli.CLR_MUTED);
try out.print(" (options {s}{s})", .{ opt_sign, fmt.fmtMoneyAbs(&opt_delta_buf, @abs(acct.option_value_delta)) });
try cli.reset(out, color);
}
try out.print("\n\n", .{});
total_portfolio += acct.portfolio_total; total_portfolio += acct.portfolio_total;
total_brokerage += acct.brokerage_total; total_brokerage += acct.brokerage_total;
total_option_delta += acct.option_value_delta;
} }
// Grand totals // Grand totals
@ -1175,6 +1206,7 @@ fn displayResults(results: []const AccountComparison, color: bool, out: *std.Io.
var br_grand_buf: [24]u8 = undefined; var br_grand_buf: [24]u8 = undefined;
var grand_delta_buf: [24]u8 = undefined; var grand_delta_buf: [24]u8 = undefined;
const grand_delta = total_brokerage - total_portfolio; const grand_delta = total_brokerage - total_portfolio;
const grand_adj_delta = grand_delta - total_option_delta;
try cli.setBold(out, color); try cli.setBold(out, color);
try out.print(" Total: portfolio {s} brokerage {s}", .{ try out.print(" Total: portfolio {s} brokerage {s}", .{
@ -1183,16 +1215,24 @@ fn displayResults(results: []const AccountComparison, color: bool, out: *std.Io.
}); });
try cli.reset(out, color); try cli.reset(out, color);
if (@abs(grand_delta) < 1.0) { if (@abs(grand_adj_delta) < 1.0) {
// no delta text needed // no delta text needed
} else { } else {
const sign: []const u8 = if (grand_delta >= 0) "+" else "-"; const sign: []const u8 = if (grand_adj_delta >= 0) "+" else "-";
if (grand_delta >= 0) { if (grand_adj_delta >= 0) {
try cli.setFg(out, color, cli.CLR_NEGATIVE); try cli.setFg(out, color, cli.CLR_NEGATIVE);
} else { } else {
try cli.setFg(out, color, cli.CLR_POSITIVE); try cli.setFg(out, color, cli.CLR_POSITIVE);
} }
try out.print(" delta {s}{s}", .{ sign, fmt.fmtMoneyAbs(&grand_delta_buf, @abs(grand_delta)) }); try out.print(" delta {s}{s}", .{ sign, fmt.fmtMoneyAbs(&grand_delta_buf, @abs(grand_adj_delta)) });
try cli.reset(out, color);
}
if (@abs(total_option_delta) >= 1.0) {
var opt_grand_buf: [24]u8 = undefined;
const opt_sign: []const u8 = if (total_option_delta >= 0) "+" else "-";
try cli.setFg(out, color, cli.CLR_MUTED);
try out.print(" (options {s}{s})", .{ opt_sign, fmt.fmtMoneyAbs(&opt_grand_buf, @abs(total_option_delta)) });
try cli.reset(out, color); try cli.reset(out, color);
} }
try out.print("\n", .{}); try out.print("\n", .{});