Skip to content

Commit 0ff6148

Browse files
committed
Expand ImportedTransaction to cover all broker data types and fix FIFO
Proto: add REDEMPTION, DIVIDEND, INTEREST, FEE, WITHHOLDING_TAX, TRANSFER_IN, TRANSFER_OUT, DEPOSIT, WITHDRAWAL, OTHER types. Add optional fields: amount, currency_code, source, original_type, security_id, tax, accrued_interest. Seed data: import all 1864 transactions from UBS and RBC (was 655). Includes dividends (347), interest (149), fees (57), transfers (445), deposits/withdrawals (71), and other (37). FIFO: transfers don't affect lot computation — they're informational records of asset movements between brokers. Only buys, sells, splits, stock dividends, expiries, and redemptions affect FIFO. Result: 45/45 positions match, 0 missing, 0 phantom positions.
1 parent 1d63c4f commit 0ff6148

3 files changed

Lines changed: 252 additions & 49 deletions

File tree

internal/gen/proto/go/ibctl/data/v1/imported_transaction.pb.go

Lines changed: 172 additions & 33 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/ibctl/ibctlmerge/ibctlmerge.go

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,12 @@ func Merge(
9292
importedTxns, err := protoio.ReadMessagesJSON(seedTxnPath, func() *datav1.ImportedTransaction { return &datav1.ImportedTransaction{} })
9393
if err == nil {
9494
for _, txn := range importedTxns {
95+
// Only security transactions (buys, sells, splits, etc.) become trades.
96+
// Non-security transactions (dividends, interest, fees) return nil.
9597
trade := importedTransactionToTrade(txn)
96-
allTrades = append(allTrades, trade)
98+
if trade != nil {
99+
allTrades = append(allTrades, trade)
100+
}
97101
}
98102
}
99103
}
@@ -238,30 +242,43 @@ func generateTradeID(symbol string, dateTime time.Time, quantity string, price s
238242
}
239243

240244
// importedTransactionToTrade converts an ImportedTransaction (from the previous broker)
241-
// to a Trade proto for FIFO processing. Uses the ibkr_symbol field for the symbol
242-
// so it matches IBKR's naming convention.
245+
// to a Trade proto for FIFO processing. Returns nil for non-security transactions
246+
// (dividends, interest, fees, transfers, deposits, withdrawals) that don't affect
247+
// FIFO lot computation.
243248
func importedTransactionToTrade(txn *datav1.ImportedTransaction) *datav1.Trade {
244249
// Map imported transaction type to trade side.
250+
// Non-security transactions return nil — they're stored for audit/income tracking
251+
// but don't participate in FIFO.
245252
var side datav1.TradeSide
246253
switch txn.GetType() {
247254
case datav1.ImportedTransactionType_IMPORTED_TRANSACTION_TYPE_BUY,
248255
datav1.ImportedTransactionType_IMPORTED_TRANSACTION_TYPE_STOCK_DIVIDEND:
249256
// Buys and stock dividends add to position.
250257
side = datav1.TradeSide_TRADE_SIDE_BUY
251258
case datav1.ImportedTransactionType_IMPORTED_TRANSACTION_TYPE_SELL,
252-
datav1.ImportedTransactionType_IMPORTED_TRANSACTION_TYPE_EXPIRY:
253-
// Sells and expiries reduce position.
259+
datav1.ImportedTransactionType_IMPORTED_TRANSACTION_TYPE_EXPIRY,
260+
datav1.ImportedTransactionType_IMPORTED_TRANSACTION_TYPE_REDEMPTION:
261+
// Sells, expiries, and redemptions reduce position.
254262
side = datav1.TradeSide_TRADE_SIDE_SELL
255263
case datav1.ImportedTransactionType_IMPORTED_TRANSACTION_TYPE_SPLIT:
256-
// Splits are handled as buys with zero price (quantity adjustment).
264+
// Splits are handled as buys or sells with zero price (quantity adjustment).
257265
if txn.GetQuantity() >= 0 {
258266
side = datav1.TradeSide_TRADE_SIDE_BUY
259267
} else {
260268
side = datav1.TradeSide_TRADE_SIDE_SELL
261269
}
262-
case datav1.ImportedTransactionType_IMPORTED_TRANSACTION_TYPE_UNSPECIFIED:
263-
// Skip unspecified transactions.
264-
side = datav1.TradeSide_TRADE_SIDE_UNSPECIFIED
270+
case datav1.ImportedTransactionType_IMPORTED_TRANSACTION_TYPE_DIVIDEND,
271+
datav1.ImportedTransactionType_IMPORTED_TRANSACTION_TYPE_INTEREST,
272+
datav1.ImportedTransactionType_IMPORTED_TRANSACTION_TYPE_FEE,
273+
datav1.ImportedTransactionType_IMPORTED_TRANSACTION_TYPE_WITHHOLDING_TAX,
274+
datav1.ImportedTransactionType_IMPORTED_TRANSACTION_TYPE_TRANSFER_IN,
275+
datav1.ImportedTransactionType_IMPORTED_TRANSACTION_TYPE_TRANSFER_OUT,
276+
datav1.ImportedTransactionType_IMPORTED_TRANSACTION_TYPE_DEPOSIT,
277+
datav1.ImportedTransactionType_IMPORTED_TRANSACTION_TYPE_WITHDRAWAL,
278+
datav1.ImportedTransactionType_IMPORTED_TRANSACTION_TYPE_OTHER,
279+
datav1.ImportedTransactionType_IMPORTED_TRANSACTION_TYPE_UNSPECIFIED:
280+
// Non-security transactions don't affect FIFO.
281+
return nil
265282
}
266283
// Use ibkr_symbol for FIFO matching.
267284
symbol := txn.GetIbkrSymbol()

0 commit comments

Comments
 (0)