@@ -9,19 +9,12 @@ import (
99 "context"
1010 "os"
1111 "strings"
12- "time"
1312
1413 "buf.build/go/app/appcmd"
1514 "buf.build/go/app/appext"
1615 "github.com/bufdev/ibctl/cmd/ibctl/internal/ibctlcmd"
17- "github.com/bufdev/ibctl/internal/ibctl/ibctlconfig"
18- "github.com/bufdev/ibctl/internal/ibctl/ibctlfxrates"
1916 "github.com/bufdev/ibctl/internal/ibctl/ibctlholdings"
20- "github.com/bufdev/ibctl/internal/ibctl/ibctlmerge"
21- "github.com/bufdev/ibctl/internal/ibctl/ibctlpath"
22- "github.com/bufdev/ibctl/internal/ibctl/ibctlrealtime"
2317 "github.com/bufdev/ibctl/internal/pkg/cliio"
24- "github.com/bufdev/ibctl/internal/pkg/yahoofinance"
2518 "github.com/spf13/pflag"
2619)
2720
@@ -97,91 +90,29 @@ func run(ctx context.Context, container appext.Container, flags *flags) error {
9790 if err != nil {
9891 return appcmd .NewInvalidArgumentError (err .Error ())
9992 }
100- // Normalize base currency to uppercase for case-insensitive matching.
101- baseCurrency := strings .ToUpper (flags .BaseCurrency )
102- // Select the formatting function based on the base currency.
103- formatBase := formatBaseFunc (baseCurrency )
104- // Resolve the ibctl directory from the IBKR_DIR environment variable.
105- dirPath , err := ibctlcmd .DirPath (container )
93+ // Load data through the common pipeline (config, merge, FX, optional download/realtime).
94+ data , err := ibctlcmd .LoadHoldingsData (ctx , container , flags .Download , flags .Realtime , flags .BaseCurrency )
10695 if err != nil {
10796 return err
10897 }
109- // Read and validate the configuration file from the base directory.
110- config , err := ibctlconfig .ReadConfig (dirPath )
111- if err != nil {
112- return err
113- }
114- // Download fresh data if --download is set.
115- if flags .Download {
116- downloader , err := ibctlcmd .NewDownloader (container , dirPath )
117- if err != nil {
118- return err
119- }
120- if err := downloader .Download (ctx ); err != nil {
121- return err
122- }
123- }
124- // Merge trade data from all sources.
125- mergedData , err := ibctlmerge .Merge (
126- ibctlpath .DataAccountsDirPath (config .DirPath ),
127- ibctlpath .CacheAccountsDirPath (config .DirPath ),
128- ibctlpath .ActivityStatementsDirPath (config .DirPath ),
129- ibctlpath .SeedDirPath (config .DirPath ),
130- config .AccountAliases ,
131- config .Additions ,
132- )
133- if err != nil {
134- return err
135- }
136- // Load FX rates for base currency conversion.
137- fxStore := ibctlfxrates .NewStore (ibctlpath .CacheFXDirPath (config .DirPath ))
138- // Override market prices and FX rates with real-time data from Yahoo Finance.
139- if flags .Realtime {
140- todayDate := time .Now ().Format ("2006-01-02" )
141- if err := ibctlrealtime .ApplyOverrides (ctx , container .Logger (), yahoofinance .NewClient (), mergedData .Positions , fxStore , config , baseCurrency , todayDate ); err != nil {
142- return err
143- }
144- }
14598 // Compute holdings via FIFO from all trade data, converted to the base currency.
146- result , err := ibctlholdings .GetHoldingsOverview (mergedData . Trades , mergedData . Positions , mergedData . CashPositions , config , fxStore , baseCurrency )
99+ result , err := ibctlholdings .GetHoldingsOverview (data . MergedData . Trades , data . MergedData . Positions , data . MergedData . CashPositions , data . Config , data . FxStore , data . BaseCurrency )
147100 if err != nil {
148101 return err
149102 }
150103 // Aggregate holdings by category, optionally filtered by geo (case-insensitive).
151104 categories := ibctlholdings .GetCategoryList (result .Holdings , strings .ToUpper (flags .Geo ))
152105 // Write output in the requested format.
106+ formatBase := ibctlcmd .FormatBaseFunc (data .BaseCurrency )
153107 writer := os .Stdout
154108 switch format {
155109 case cliio .FormatTable :
156- headers := ibctlholdings .CategoryListHeaders (baseCurrency )
157- rows := make ([][]string , 0 , len (categories ))
158- for _ , c := range categories {
159- rows = append (rows , ibctlholdings .CategoryOverviewToTableRow (c , formatBase ))
160- }
161- return cliio .WriteTable (writer , headers , rows )
110+ return ibctlcmd .WriteCategoryListTable (writer , categories , data .BaseCurrency , formatBase )
162111 case cliio .FormatCSV :
163- headers := ibctlholdings .CategoryListHeaders (baseCurrency )
164- records := make ([][]string , 0 , len (categories )+ 1 )
165- records = append (records , headers )
166- for _ , c := range categories {
167- records = append (records , ibctlholdings .CategoryOverviewToRow (c ))
168- }
169- return cliio .WriteCSVRecords (writer , records )
112+ return ibctlcmd .WriteCategoryListCSV (writer , categories , data .BaseCurrency )
170113 case cliio .FormatJSON :
171114 return cliio .WriteJSON (writer , categories ... )
172115 default :
173116 return appcmd .NewInvalidArgumentErrorf ("unsupported format: %s" , format )
174117 }
175118}
176-
177- // formatBaseFunc returns a formatting function for display values in the given currency.
178- func formatBaseFunc (baseCurrency string ) func (string ) string {
179- switch baseCurrency {
180- case "USD" :
181- return cliio .FormatUSD
182- case "CAD" :
183- return cliio .FormatCAD
184- default :
185- return func (v string ) string { return v }
186- }
187- }
0 commit comments