Skip to content

Commit c34f3ec

Browse files
committed
Add YTD realized income tax section to holding value command
1 parent 0979a91 commit c34f3ec

1 file changed

Lines changed: 12 additions & 91 deletions

File tree

cmd/ibctl/internal/command/holding/holdingvalue/holdingvalue.go

Lines changed: 12 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,12 @@ package holdingvalue
88
import (
99
"context"
1010
"fmt"
11-
"math"
1211
"os"
13-
"strings"
14-
"time"
1512

1613
"buf.build/go/app/appcmd"
1714
"buf.build/go/app/appext"
1815
"github.com/bufdev/ibctl/cmd/ibctl/internal/ibctlcmd"
19-
"github.com/bufdev/ibctl/internal/ibctl/ibctlconfig"
20-
"github.com/bufdev/ibctl/internal/ibctl/ibctlfxrates"
2116
"github.com/bufdev/ibctl/internal/ibctl/ibctlholdings"
22-
"github.com/bufdev/ibctl/internal/ibctl/ibctlmerge"
23-
"github.com/bufdev/ibctl/internal/ibctl/ibctlpath"
24-
"github.com/bufdev/ibctl/internal/ibctl/ibctlrealtime"
25-
"github.com/bufdev/ibctl/internal/pkg/cliio"
26-
"github.com/bufdev/ibctl/internal/pkg/mathpb"
27-
"github.com/bufdev/ibctl/internal/pkg/moneypb"
28-
"github.com/bufdev/ibctl/internal/pkg/yahoofinance"
2917
"github.com/spf13/pflag"
3018
)
3119

@@ -94,93 +82,26 @@ func (f *flags) Bind(flagSet *pflag.FlagSet) {
9482
}
9583

9684
func run(ctx context.Context, container appext.Container, flags *flags) error {
97-
// Normalize base currency to uppercase for case-insensitive matching.
98-
baseCurrency := strings.ToUpper(flags.BaseCurrency)
99-
// Select the formatting function based on the base currency.
100-
formatBaseMicros := formatBaseMicrosFunc(baseCurrency)
101-
// Resolve the ibctl directory from the IBKR_DIR environment variable.
102-
dirPath, err := ibctlcmd.DirPath(container)
85+
// Load data through the common pipeline (config, merge, FX, optional download/realtime).
86+
data, err := ibctlcmd.LoadHoldingsData(ctx, container, flags.Download, flags.Realtime, flags.BaseCurrency)
10387
if err != nil {
10488
return err
10589
}
106-
// Read and validate the configuration file from the base directory.
107-
config, err := ibctlconfig.ReadConfig(dirPath)
90+
// Compute holdings via FIFO from all trade data, converted to the base currency.
91+
result, err := ibctlholdings.GetHoldingsOverview(data.MergedData.Trades, data.MergedData.Positions, data.MergedData.CashPositions, data.Config, data.FxStore, data.BaseCurrency)
10892
if err != nil {
10993
return err
11094
}
111-
// Download fresh data if --download is set.
112-
if flags.Download {
113-
downloader, err := ibctlcmd.NewDownloader(container, dirPath)
114-
if err != nil {
115-
return err
116-
}
117-
if err := downloader.Download(ctx); err != nil {
118-
return err
119-
}
120-
}
121-
// Merge trade data from all sources.
122-
mergedData, err := ibctlmerge.Merge(
123-
ibctlpath.DataAccountsDirPath(config.DirPath),
124-
ibctlpath.CacheAccountsDirPath(config.DirPath),
125-
ibctlpath.ActivityStatementsDirPath(config.DirPath),
126-
ibctlpath.SeedDirPath(config.DirPath),
127-
config.AccountAliases,
128-
config.Additions,
129-
)
95+
formatBaseMicros := ibctlcmd.FormatBaseMicrosFunc(data.BaseCurrency)
96+
// Compute and print the portfolio value summary with unrealized tax impact.
97+
summary := ibctlcmd.ComputeValueSummary(result.Holdings, data.Config)
98+
ibctlcmd.WriteValueSummary(os.Stdout, summary, data.Config, formatBaseMicros)
99+
// Compute and print the YTD realized income tax summary.
100+
ytdSummary, err := ibctlcmd.ComputeYTDTaxSummary(data.MergedData, data.Config, data.FxStore, data.BaseCurrency)
130101
if err != nil {
131102
return err
132103
}
133-
// Load FX rates for base currency conversion.
134-
fxStore := ibctlfxrates.NewStore(ibctlpath.CacheFXDirPath(config.DirPath))
135-
// Override market prices and FX rates with real-time data from Yahoo Finance.
136-
if flags.Realtime {
137-
todayDate := time.Now().Format("2006-01-02")
138-
if err := ibctlrealtime.ApplyOverrides(ctx, container.Logger(), yahoofinance.NewClient(), mergedData.Positions, fxStore, config, baseCurrency, todayDate); err != nil {
139-
return err
140-
}
141-
}
142-
// Compute holdings via FIFO from all trade data, converted to the base currency.
143-
result, err := ibctlholdings.GetHoldingsOverview(mergedData.Trades, mergedData.Positions, mergedData.CashPositions, config, fxStore, baseCurrency)
144-
if err != nil {
145-
return err
146-
}
147-
// Sum up portfolio value, STCG, and LTCG from all holdings in the base currency.
148-
var totalValueMicros, totalSTCGMicros, totalLTCGMicros int64
149-
for _, h := range result.Holdings {
150-
totalValueMicros += mathpb.ParseMicros(h.MarketValueBase)
151-
totalSTCGMicros += mathpb.ParseMicros(h.STCGBase)
152-
totalLTCGMicros += mathpb.ParseMicros(h.LTCGBase)
153-
}
154-
// Compute tax amounts. Gains are taxed; losses reduce taxes (can be negative).
155-
stcgTaxMicros := int64(math.Round(float64(totalSTCGMicros) * config.TaxRateSTCG))
156-
ltcgTaxMicros := int64(math.Round(float64(totalLTCGMicros) * config.TaxRateLTCG))
157-
totalTaxMicros := stcgTaxMicros + ltcgTaxMicros
158-
// After-tax value = portfolio value - total taxes.
159-
afterTaxMicros := totalValueMicros - totalTaxMicros
160-
// Print the summary using the base currency formatting.
161-
writer := os.Stdout
162-
fmt.Fprintf(writer, "Portfolio Value: %s\n", formatBaseMicros(totalValueMicros))
163-
fmt.Fprintf(writer, "\n")
164-
fmt.Fprintf(writer, "STCG: %s\n", formatBaseMicros(totalSTCGMicros))
165-
fmt.Fprintf(writer, "STCG Tax (%.1f%%): %s\n", config.TaxRateSTCG*100, formatBaseMicros(stcgTaxMicros))
166-
fmt.Fprintf(writer, "LTCG: %s\n", formatBaseMicros(totalLTCGMicros))
167-
fmt.Fprintf(writer, "LTCG Tax (%.1f%%): %s\n", config.TaxRateLTCG*100, formatBaseMicros(ltcgTaxMicros))
168-
fmt.Fprintf(writer, "Total Tax: %s\n", formatBaseMicros(totalTaxMicros))
169-
fmt.Fprintf(writer, "\n")
170-
fmt.Fprintf(writer, "After-Tax Value: %s\n", formatBaseMicros(afterTaxMicros))
104+
fmt.Fprintln(os.Stdout)
105+
ibctlcmd.WriteYTDTaxSummary(os.Stdout, ytdSummary, data.Config, formatBaseMicros)
171106
return nil
172107
}
173-
174-
// formatBaseMicrosFunc returns a formatting function for micros values in the given currency.
175-
func formatBaseMicrosFunc(baseCurrency string) func(int64) string {
176-
switch baseCurrency {
177-
case "USD":
178-
return cliio.FormatUSDMicros
179-
case "CAD":
180-
return cliio.FormatCADMicros
181-
default:
182-
return func(micros int64) string {
183-
return moneypb.MoneyValueToString(moneypb.MoneyFromMicros(baseCurrency, micros))
184-
}
185-
}
186-
}

0 commit comments

Comments
 (0)