Skip to content

Commit

Permalink
adds "Renames" config for renamed/delisted stocks
Browse files Browse the repository at this point in the history
  • Loading branch information
gerbenjacobs committed Oct 5, 2024
1 parent 3155c27 commit 9764099
Show file tree
Hide file tree
Showing 10 changed files with 55 additions and 20 deletions.
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ symbols:
RIO: RIO.L
SAN: SAN.PA

# Renames deal with stocks that have changed their symbol
# for example by becoming a new company or by being delisted
renames:
GPS: GAP
TUP: TUPBQ

# Pies allows you split your aggregation into multiple CSVs
# uncomment to use
#pies:
Expand Down Expand Up @@ -95,7 +101,8 @@ The default currency is set to EUR, but you can use the dropdown to change it to

## Changelog

- v0.2.5 - 2024-02-98 - Added "Lending interest" field
- v0.2.6 - 2024-10-05 - Added "Renames" config for renamed/delisted stocks
- v0.2.5 - 2024-02-08 - Added "Lending interest" field
- v0.2.4 - 2023-08-01 - Fix stock splits for stocks that are untouched + Update dependencies
- v0.2.3 - 2023-07-31 - Skip "Currency conversion" action + Deposits has changed fields
- v0.2.2 - 2023-07-24 - Currency name not in headers anymore
Expand Down
3 changes: 2 additions & 1 deletion cmd/aggregator/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,14 @@ func main() {
"pies": len(cfg.Pies),
"splits": len(cfg.Splits),
"symbols": len(cfg.Symbols),
"renames": len(cfg.Renames),
}).Info("Starting process.")

// loop through directory and find csv files
events := trading212.Collect(cfg.Input)

// aggregate events via Trading212 algorithm
stocks, totals := trading212.Aggregate(cfg.Splits, events)
stocks, totals := trading212.Aggregate(cfg.Splits, cfg.Renames, events)

log.WithFields(logrus.Fields{
"deposits": totals.Deposits,
Expand Down
1 change: 1 addition & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type Config struct {
PieOnly string `yaml:"pie-only"`
Splits []Splits `yaml:"splits"`
Symbols map[string]string `yaml:"symbols"`
Renames map[string]string `yaml:"renames"`
Pies []struct {
Name string `yaml:"name"`
Symbols []string `yaml:"symbols"`
Expand Down
6 changes: 6 additions & 0 deletions config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ symbols:
RIO: RIO.L
SAN: SAN.PA

# Renames deal with stocks that have changed their symbol
# for example by becoming a new company or by being delisted
renames:
GPS: GAP
TUP: TUPBQ

# Pies allows you split your aggregation into multiple CSVs
# uncomment to use
#pies:
Expand Down
2 changes: 1 addition & 1 deletion testdata/gbp/trading212.csv
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ Deposit,2021-09-07 13:43:10,,,,,,,,,1000.00,,,1001.40,1.40,,"Transaction ID: xxx
Market buy,2021-09-27 13:19:13,US02079K1079,ABEC,"Google",0.0041253700,2424.00,EUR,1.00000,,10.00,,,,,,,EOF5,,
Dividend (Ordinary),2021-09-30 11:15:32,US5949181045,MSFT,"Microsoft",0.2709950000,0.48,USD,Not available,,0.11,0.02,USD,,,,,,,
Market buy,2022-03-07 16:10:26,FR0000120578,SAN,"Sanofi",0.1117960000,89.18,EUR,1.00000,,10.00,,,,,,,EOF6,,0.03
Market buy,2022-07-29 14:28:17,US02079K1079,ABEC,"Alphabet (Class C)",2.2887315000,113.60,EUR,1.00000,,260.00,,,,,,,EOF7,,
Market buy,2022-07-29 14:28:17,US02079K1079,GOOG,"Alphabet (Class C)",2.2887315000,113.60,EUR,1.00000,,260.00,,,,,,,EOF7,,

2 changes: 1 addition & 1 deletion testdata/multiple/3-wrong-order.csv
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Action,Time,ISIN,Ticker,Name,No. of shares,Price / share,Currency (Price / share),Exchange rate,Result,Total,Withholding tax,Currency (Withholding tax),Charge amount,Deposit fee,Stamp duty reserve tax,Notes,ID,Currency conversion fee,French transaction tax
Market buy,2022-03-07 16:10:26,FR0000120578,SAN,"Sanofi",0.1117960000,89.18,EUR,1.00000,,10.00,,,,,,,EOF6,,0.03
Market buy,2022-07-29 14:28:17,US02079K1079,ABEC,"Alphabet (Class C)",2.2887315000,113.60,EUR,1.00000,,260.00,,,,,,,EOF7,,
Market buy,2022-07-29 14:28:17,US02079K1079,GOOG,"Alphabet (Class C)",2.2887315000,113.60,EUR,1.00000,,260.00,,,,,,,EOF7,,

2 changes: 1 addition & 1 deletion testdata/trading212.csv
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ Deposit,2021-09-07 13:43:10,,,,,,,,,1000.00,,,1001.40,1.40,,"Transaction ID: xxx
Market buy,2021-09-27 13:19:13,US02079K1079,ABEC,"Google",0.0041253700,2424.00,EUR,1.00000,,10.00,,,,,,,EOF5,,
Dividend (Ordinary),2021-09-30 11:15:32,US5949181045,MSFT,"Microsoft",0.2709950000,0.48,USD,Not available,,0.11,0.02,USD,,,,,,,
Market buy,2022-03-07 16:10:26,FR0000120578,SAN,"Sanofi",0.1117960000,89.18,EUR,1.00000,,10.00,,,,,,,EOF6,,0.03
Market buy,2022-07-29 14:28:17,US02079K1079,ABEC,"Alphabet (Class C)",2.2887315000,113.60,EUR,1.00000,,260.00,,,,,,,EOF7,,
Market buy,2022-07-29 14:28:17,US02079K1079,GOOG,"Alphabet (Class C)",2.2887315000,113.60,EUR,1.00000,,260.00,,,,,,,EOF7,,

23 changes: 15 additions & 8 deletions trading212/aggregate.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (

// Aggregate takes a map of events and aggregates them into a map of stocks and totals,
// based on the Trading212 algorithm, along with stock splits.
func Aggregate(splits []fin.Splits, events []TradeEvent) ([]fin.Aggregate, fin.Totals) {
func Aggregate(splits []fin.Splits, renames map[string]string, events []TradeEvent) ([]fin.Aggregate, fin.Totals) {
var stocks = make(map[string]fin.Aggregate)
var stockNames []string
var totals fin.Totals
Expand All @@ -30,20 +30,26 @@ func Aggregate(splits []fin.Splits, events []TradeEvent) ([]fin.Aggregate, fin.T
continue
}

// handle renamed stock symbols
symbol := e.TickerSymbol
if rn, ok := renames[symbol]; ok {
symbol = rn
}

// create entry if it doesn't exist
if _, ok := stocks[e.TickerSymbol]; !ok {
stocks[e.TickerSymbol] = fin.Aggregate{
Symbol: e.TickerSymbol,
if _, ok := stocks[symbol]; !ok {
stocks[symbol] = fin.Aggregate{
Symbol: symbol,
}
stockNames = append(stockNames, e.TickerSymbol)
stockNames = append(stockNames, symbol)
}

// calculate changes
a := stocks[e.TickerSymbol]
a := stocks[symbol]

// did a stock split happen today
for _, split := range splits {
if split.Symbol == e.TickerSymbol &&
if split.Symbol == symbol &&
split.Date > a.LastUpdate.Format("2006-01-02") && split.Date <= e.Time.Format("2006-01-02") {
a.ShareCount = a.ShareCount * split.Ratio
}
Expand Down Expand Up @@ -74,7 +80,8 @@ func Aggregate(splits []fin.Splits, events []TradeEvent) ([]fin.Aggregate, fin.T
totals.Taxes += e.Tax

// update totals
if a.ShareCount > 0 {
if floorFloat(a.ShareCount, 4) > 0 {
// if it's practically zero, reset it (float comparison issues)
a.AvgPrice = a.ShareCost / a.ShareCount
} else {
// during this event everything was sold
Expand Down
25 changes: 19 additions & 6 deletions trading212/aggregate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ import (

func TestAggregate(t *testing.T) {
splits := []fin.Splits{
{Symbol: "ABEC", Date: "2022-07-16", Ratio: 20},
{Symbol: "GOOG", Date: "2022-07-16", Ratio: 20},
}
renames := map[string]string{
"ABEC": "GOOG",
}
tests := []struct {
name string
Expand All @@ -22,8 +25,8 @@ func TestAggregate(t *testing.T) {
name: "Regular test like our testdata",
events: defaultTestDataEvents,
want: []fin.Aggregate{
{Symbol: "ABEC", Name: "Alphabet (Class C)", ShareCount: 2.371231, AvgPrice: 113.86, PriceCurrency: "EUR", ShareCost: 270, ShareCostLocal: 270, ShareResult: 0, TotalDividend: 0, Fees: 0, Final: 0, LastUpdate: time.Date(2022, 7, 29, 14, 28, 17, 0, time.UTC)},
{Symbol: "FB", Name: "Meta Platforms", ShareCount: 0.086391, AvgPrice: 362, PriceCurrency: "USD", ShareCost: 31.27, ShareCostLocal: 26.67, ShareResult: 0, TotalDividend: 0, Fees: 0.04, Final: -0.04, LastUpdate: time.Date(2021, 8, 9, 18, 31, 41, 0, time.UTC)},
{Symbol: "GOOG", Name: "Alphabet (Class C)", ShareCount: 2.371231, AvgPrice: 113.86, PriceCurrency: "EUR", ShareCost: 270, ShareCostLocal: 270, ShareResult: 0, TotalDividend: 0, Fees: 0, Final: 0, LastUpdate: time.Date(2022, 7, 29, 14, 28, 17, 0, time.UTC)},
{Symbol: "MSFT", Name: "Microsoft", ShareCount: 0, AvgPrice: 0, PriceCurrency: "USD", ShareCost: 0, ShareCostLocal: 0, ShareResult: 2.61, TotalDividend: 0.11, Fees: 0.2, Final: 2.51, LastUpdate: time.Date(2021, 9, 30, 11, 15, 32, 0, time.UTC)},
{Symbol: "SAN", Name: "Sanofi", ShareCount: 0.111796, AvgPrice: 89.18, PriceCurrency: "EUR", ShareCost: 9.97, ShareCostLocal: 10, ShareResult: 0, TotalDividend: 0, Fees: 0.03, Final: -0.03, LastUpdate: time.Date(2022, 3, 7, 16, 10, 26, 0, time.UTC)},
{Symbol: "TSLA", Name: "Tesla", ShareCount: 0.076654, AvgPrice: 713.94, PriceCurrency: "USD", ShareCost: 54.72, ShareCostLocal: 46.67, ShareResult: 0, TotalDividend: 0, Fees: 0.07, Final: -0.08, LastUpdate: time.Date(2021, 8, 9, 18, 31, 41, 0, time.UTC)},
Expand All @@ -41,17 +44,27 @@ func TestAggregate(t *testing.T) {
{
name: "Test with a split",
events: []TradeEvent{
{Action: "Market buy", Time: DateTime{Time: time.Date(2021, 9, 27, 13, 19, 13, 0, time.UTC)}, TickerSymbol: "ABEC", ShareCount: 0.005, SharePrice: 2000.00, Total: 10.00, ID: "EOF1"},
{Action: "Market buy", Time: DateTime{Time: time.Date(2022, 9, 27, 13, 19, 13, 0, time.UTC)}, TickerSymbol: "ABEC", ShareCount: 0.125, SharePrice: 80.00, Total: 10.00, ID: "EOF2"},
{Action: "Market buy", Time: DateTime{Time: time.Date(2021, 9, 27, 13, 19, 13, 0, time.UTC)}, TickerSymbol: "GOOG", ShareCount: 0.005, SharePrice: 2000.00, Total: 10.00, ID: "EOF1"},
{Action: "Market buy", Time: DateTime{Time: time.Date(2022, 9, 27, 13, 19, 13, 0, time.UTC)}, TickerSymbol: "GOOG", ShareCount: 0.125, SharePrice: 80.00, Total: 10.00, ID: "EOF2"},
},
want: []fin.Aggregate{
{Symbol: "GOOG", ShareCount: 0.225, AvgPrice: 88.88, ShareCost: 20, ShareCostLocal: 20, ShareResult: 0, TotalDividend: 0, Fees: 0, Final: 0, LastUpdate: time.Date(2022, 9, 27, 13, 19, 13, 0, time.UTC)},
},
},
{
name: "Test float precision when selling",
events: []TradeEvent{
{Action: "Market buy", Time: DateTime{Time: time.Date(2021, 9, 27, 13, 19, 13, 0, time.UTC)}, TickerSymbol: "FB", ShareCount: 1.2345678, SharePrice: 2000.00, Total: 10.00, ID: "EOF1"},
{Action: "Market sell", Time: DateTime{Time: time.Date(2022, 9, 27, 13, 19, 13, 0, time.UTC)}, TickerSymbol: "FB", ShareCount: 1.2345, SharePrice: 2100.00, Result: 100, Total: 10.00, ID: "EOF2"},
},
want: []fin.Aggregate{
{Symbol: "ABEC", ShareCount: 0.225, AvgPrice: 88.88, ShareCost: 20, ShareCostLocal: 20, ShareResult: 0, TotalDividend: 0, Fees: 0, Final: 0, LastUpdate: time.Date(2022, 9, 27, 13, 19, 13, 0, time.UTC)},
{Symbol: "FB", ShareCount: 0, AvgPrice: 0, ShareCost: 0, ShareCostLocal: 0, ShareResult: 100, TotalDividend: 0, Fees: 0, Final: 100, LastUpdate: time.Date(2022, 9, 27, 13, 19, 13, 0, time.UTC)},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
aggregates, totals := Aggregate(splits, tt.events)
aggregates, totals := Aggregate(splits, renames, tt.events)
for idx, agg := range aggregates {
if !reflect.DeepEqual(agg, tt.want[idx]) {
t.Errorf("aggregate for %s is a mismatch \n%#v\n%#v", agg.Symbol, agg, tt.want[idx])
Expand Down
2 changes: 1 addition & 1 deletion trading212/collect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ var defaultTestDataEvents = []TradeEvent{
{Action: "Market buy", Time: DateTime{Time: time.Date(2021, 9, 27, 13, 19, 13, 0, time.UTC)}, ISIN: "US02079K1079", TickerSymbol: "ABEC", TickerName: "Google", ShareCount: 0.0041253700, SharePrice: 2424.00, ShareCurrency: "EUR", ExchangeRate: "1.00000", Total: 10.00, ID: "EOF5"},
{Action: "Dividend (Ordinary)", Time: DateTime{Time: time.Date(2021, 9, 30, 11, 15, 32, 0, time.UTC)}, ISIN: "US5949181045", TickerSymbol: "MSFT", TickerName: "Microsoft", ShareCount: 0.2709950000, SharePrice: 0.48, ShareCurrency: "USD", ExchangeRate: "Not available", Total: 0.11, Tax: 0.02, TaxCurrency: "USD"},
{Action: "Market buy", Time: DateTime{Time: time.Date(2022, 3, 7, 16, 10, 26, 0, time.UTC)}, ISIN: "FR0000120578", TickerSymbol: "SAN", TickerName: "Sanofi", ShareCount: 0.1117960000, SharePrice: 89.18, ShareCurrency: "EUR", ExchangeRate: "1.00000", Total: 10.00, ID: "EOF6", FRFee: 0.03},
{Action: "Market buy", Time: DateTime{Time: time.Date(2022, 7, 29, 14, 28, 17, 0, time.UTC)}, ISIN: "US02079K1079", TickerSymbol: "ABEC", TickerName: "Alphabet (Class C)", ShareCount: 2.2887315000, SharePrice: 113.60, ShareCurrency: "EUR", ExchangeRate: "1.00000", Total: 260.00, ID: "EOF7"},
{Action: "Market buy", Time: DateTime{Time: time.Date(2022, 7, 29, 14, 28, 17, 0, time.UTC)}, ISIN: "US02079K1079", TickerSymbol: "GOOG", TickerName: "Alphabet (Class C)", ShareCount: 2.2887315000, SharePrice: 113.60, ShareCurrency: "EUR", ExchangeRate: "1.00000", Total: 260.00, ID: "EOF7"},
}

func TestCollect(t *testing.T) {
Expand Down

0 comments on commit 9764099

Please sign in to comment.