Skip to content

Commit 09b4453

Browse files
committed
commit
1 parent a351913 commit 09b4453

File tree

8 files changed

+45
-16
lines changed

8 files changed

+45
-16
lines changed

cmd/ibctl/internal/command/holding/category/categorylist/categorylist.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ func run(ctx context.Context, container appext.Container, flags *flags) error {
134134
// Override market prices and FX rates with real-time data from Yahoo Finance.
135135
if flags.Realtime {
136136
todayDate := time.Now().Format("2006-01-02")
137-
if err := ibctlrealtime.ApplyOverrides(ctx, mergedData.Positions, fxStore, config, baseCurrency, todayDate); err != nil {
137+
if err := ibctlrealtime.ApplyOverrides(ctx, container.Logger(), mergedData.Positions, fxStore, config, baseCurrency, todayDate); err != nil {
138138
return err
139139
}
140140
}

cmd/ibctl/internal/command/holding/geo/geolist/geolist.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ func run(ctx context.Context, container appext.Container, flags *flags) error {
135135
// Override market prices and FX rates with real-time data from Yahoo Finance.
136136
if flags.Realtime {
137137
todayDate := time.Now().Format("2006-01-02")
138-
if err := ibctlrealtime.ApplyOverrides(ctx, mergedData.Positions, fxStore, config, baseCurrency, todayDate); err != nil {
138+
if err := ibctlrealtime.ApplyOverrides(ctx, container.Logger(), mergedData.Positions, fxStore, config, baseCurrency, todayDate); err != nil {
139139
return err
140140
}
141141
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ func run(ctx context.Context, container appext.Container, flags *flags) error {
164164
// Override market prices and FX rates with real-time data from Yahoo Finance.
165165
if flags.Realtime {
166166
todayDate := time.Now().Format("2006-01-02")
167-
if err := ibctlrealtime.ApplyOverrides(ctx, mergedData.Positions, fxStore, config, baseCurrency, todayDate); err != nil {
167+
if err := ibctlrealtime.ApplyOverrides(ctx, container.Logger(), mergedData.Positions, fxStore, config, baseCurrency, todayDate); err != nil {
168168
return err
169169
}
170170
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ func run(ctx context.Context, container appext.Container, flags *flags) error {
131131
// Override market prices and FX rates with real-time data from Yahoo Finance.
132132
if flags.Realtime {
133133
todayDate := time.Now().Format("2006-01-02")
134-
if err := ibctlrealtime.ApplyOverrides(ctx, mergedData.Positions, fxStore, config, baseCurrency, todayDate); err != nil {
134+
if err := ibctlrealtime.ApplyOverrides(ctx, container.Logger(), mergedData.Positions, fxStore, config, baseCurrency, todayDate); err != nil {
135135
return err
136136
}
137137
}

cmd/ibctl/internal/command/holding/lot/lotlist/lotlist.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ func run(ctx context.Context, container appext.Container, flags *flags) error {
144144
// Override market prices and FX rates with real-time data from Yahoo Finance.
145145
if flags.Realtime {
146146
todayDate := time.Now().Format("2006-01-02")
147-
if err := ibctlrealtime.ApplyOverrides(ctx, mergedData.Positions, fxStore, config, baseCurrency, todayDate); err != nil {
147+
if err := ibctlrealtime.ApplyOverrides(ctx, container.Logger(), mergedData.Positions, fxStore, config, baseCurrency, todayDate); err != nil {
148148
return err
149149
}
150150
}

internal/ibctl/ibctlrealtime/ibctlrealtime.go

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ package ibctlrealtime
1515
import (
1616
"context"
1717
"fmt"
18+
"log/slog"
1819

1920
datav1 "github.com/bufdev/ibctl/internal/gen/proto/go/ibctl/data/v1"
2021
"github.com/bufdev/ibctl/internal/ibctl/ibctlconfig"
@@ -27,6 +28,10 @@ import (
2728
// assetCategoryCash is the IBKR asset category for cash/FX positions.
2829
const assetCategoryCash = "CASH"
2930

31+
// assetCategoryBond is the IBKR asset category for bond positions.
32+
// Yahoo Finance does not provide quotes for individual corporate bonds.
33+
const assetCategoryBond = "BOND"
34+
3035
// RealTimeData holds real-time prices and FX rates fetched from Yahoo Finance.
3136
// All values are in micros (6 decimal places). This data is transient and not persisted.
3237
type RealTimeData struct {
@@ -42,14 +47,15 @@ type RealTimeData struct {
4247
// Returns an error if the API is unreachable or any symbol/FX pair cannot be resolved.
4348
func ApplyOverrides(
4449
ctx context.Context,
50+
logger *slog.Logger,
4551
positions []*datav1.Position,
4652
fxStore *ibctlfxrates.Store,
4753
config *ibctlconfig.Config,
4854
baseCurrency string,
4955
todayDate string,
5056
) error {
5157
yahooClient := yahoofinance.NewClient()
52-
realTimeData, err := FetchData(ctx, yahooClient, positions, config, baseCurrency)
58+
realTimeData, err := FetchData(ctx, logger, yahooClient, positions, config, baseCurrency)
5359
if err != nil {
5460
return err
5561
}
@@ -73,6 +79,7 @@ func ApplyOverrides(
7379
// resolved.
7480
func FetchData(
7581
ctx context.Context,
82+
logger *slog.Logger,
7683
yahooClient yahoofinance.Client,
7784
positions []*datav1.Position,
7885
config *ibctlconfig.Config,
@@ -83,8 +90,10 @@ func FetchData(
8390
ibkrToYahoo := make(map[string]string)
8491
currencies := make(map[string]struct{})
8592
for _, position := range positions {
86-
// Skip cash positions — they have no equity quote.
87-
if position.GetAssetCategory() == assetCategoryCash {
93+
// Skip cash and bond positions — cash has no equity quote, and Yahoo Finance
94+
// does not provide quotes for individual corporate bonds.
95+
assetCategory := position.GetAssetCategory()
96+
if assetCategory == assetCategoryCash || assetCategory == assetCategoryBond {
8897
continue
8998
}
9099
ibkrSymbol := position.GetSymbol()
@@ -136,21 +145,31 @@ func FetchData(
136145
for _, quote := range quotes {
137146
yahooQuoteMap[quote.Symbol] = quote
138147
}
139-
// Verify all requested equity symbols were resolved and convert to micros.
148+
// Convert resolved equity symbols to micros. Symbols not found on Yahoo Finance
149+
// are skipped — positions without an override keep their cached IBKR price.
140150
quoteMicros := make(map[string]int64, len(ibkrToYahoo))
141151
for ibkrSymbol, yahooSymbol := range ibkrToYahoo {
142152
quote, ok := yahooQuoteMap[yahooSymbol]
143153
if !ok {
144-
return nil, fmt.Errorf("symbol %q not found on Yahoo Finance (queried as %q), add a mapping under realtime_symbols in ibctl.yaml", ibkrSymbol, yahooSymbol)
154+
logger.Warn("real-time quote not found, using cached price",
155+
slog.String("symbol", ibkrSymbol),
156+
slog.String("yahoo_symbol", yahooSymbol),
157+
)
158+
continue
145159
}
146160
quoteMicros[ibkrSymbol] = mathpb.ToMicros(quote.RegularMarketPrice)
147161
}
148-
// Verify all requested FX pairs were resolved and convert to micros.
162+
// Convert resolved FX pairs to micros. Pairs not found on Yahoo Finance
163+
// are skipped — the FX store falls back to its cached historical rate.
149164
fxRateMicros := make(map[string]int64, len(fxYahooToPair))
150165
for yahooFXSymbol, pairKey := range fxYahooToPair {
151166
quote, ok := yahooQuoteMap[yahooFXSymbol]
152167
if !ok {
153-
return nil, fmt.Errorf("FX pair %q not found on Yahoo Finance (queried as %q)", pairKey, yahooFXSymbol)
168+
logger.Warn("real-time FX rate not found, using cached rate",
169+
slog.String("pair", pairKey),
170+
slog.String("yahoo_symbol", yahooFXSymbol),
171+
)
172+
continue
154173
}
155174
fxRateMicros[pairKey] = mathpb.ToMicros(quote.RegularMarketPrice)
156175
}

internal/ibctl/ibctltaxlot/ibctltaxlot.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -400,15 +400,24 @@ func VerifyPositions(computed []*datav1.ComputedPosition, reported []*datav1.Pos
400400
key := lotKey{accountAlias: pos.GetAccountAlias(), symbol: pos.GetSymbol()}
401401
computedMap[key] = pos
402402
}
403-
// Build a map of reported positions by (account_alias, symbol).
403+
// Build a map of reported positions by (account_alias, symbol) and collect
404+
// the set of IBKR account aliases. Non-IBKR accounts (e.g., manual additions)
405+
// have no reported positions and are skipped during verification.
404406
reportedMap := make(map[lotKey]*datav1.Position, len(reported))
407+
ibkrAccounts := make(map[string]struct{})
405408
for _, pos := range reported {
406409
key := lotKey{accountAlias: pos.GetAccountAlias(), symbol: pos.GetSymbol()}
407410
reportedMap[key] = pos
411+
ibkrAccounts[pos.GetAccountAlias()] = struct{}{}
408412
}
409413
var discrepancies []PositionDiscrepancy
410-
// Check computed positions against reported.
414+
// Check computed positions against reported, skipping non-IBKR accounts.
411415
for key, comp := range computedMap {
416+
// Skip positions from non-IBKR accounts (e.g., manual additions) — there is
417+
// nothing to verify them against.
418+
if _, isIBKR := ibkrAccounts[key.accountAlias]; !isIBKR {
419+
continue
420+
}
412421
rep, ok := reportedMap[key]
413422
if !ok {
414423
discrepancies = append(discrepancies, PositionDiscrepancy{

internal/pkg/yahoofinance/yahoofinance.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,12 @@ func (c *client) GetQuotes(ctx context.Context, symbols []string) ([]Quote, erro
8282
}()
8383
}
8484
waitGroup.Wait()
85-
// Collect results, returning the first error encountered.
85+
// Collect successful results. Per-symbol errors are skipped — the caller
86+
// handles missing symbols via fallback to cached data.
8687
quotes := make([]Quote, 0, len(symbols))
8788
for _, r := range results {
8889
if r.err != nil {
89-
return nil, r.err
90+
continue
9091
}
9192
quotes = append(quotes, r.quote)
9293
}

0 commit comments

Comments
 (0)