@@ -8,24 +8,12 @@ package holdingvalue
88import (
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
9684func 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