Skip to content

Commit

Permalink
Separate ibkr reader to package
Browse files Browse the repository at this point in the history
  • Loading branch information
thinktwice13 committed Mar 18, 2024
1 parent be47c21 commit cb29f3b
Show file tree
Hide file tree
Showing 5 changed files with 508 additions and 474 deletions.
38 changes: 38 additions & 0 deletions broker/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package broker

import "time"

// Tx is a catch-all transaction type
// in this version, it can represent all transaction types except for trades, which need to track quotes at a specific time (Year is not enough)
type Tx struct {
ISIN, Category, Currency string
Amount float64
Year int
}

type Trade struct {
// ISIN is the International Securities Identification Number. FIFO method needs to be partitioned by ISIN
ISIN string
// Category is the type of Trade: equity, bond, option, forex, crypto, etc.
Category string
Time time.Time
// Currency is the Currency of the Trade
Currency string `validate:"required,iso4217"`
// Quantity is the number of shares, contracts, or units
Quantity float64
// Price is the Price per share, contract, or unit
Price float64
}

// Statement is an envelope for all relevant broker data from a single file.
// This approach chosen as a middle ground between managing one channel per data type and marshal+switch option
type Statement struct {
Broker string
Filename string
Trades []Trade
FixedIncome, Tax, Fees []Tx
}

type Reader interface {
Read(filename string) (Statement, error)
}
85 changes: 19 additions & 66 deletions fx/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"io"
"log"
"net/http"
"regexp"
"strconv"
"strings"
"time"
Expand Down Expand Up @@ -49,70 +48,6 @@ type Rater interface {
Rate(currency string, year int) float64
}

func amountFromStringOld(s string) float64 {
if s == "" {
return 0

}
// Remove all but numbers, commas and points
re := regexp.MustCompile(`[0-9.,-]`)
ss := strings.Join(re.FindAllString(s, -1), "")
isNeg := ss[0] == '-'
// Find all commas and points
// If none found, return 0, print error
signs := regexp.MustCompile(`[.,]`).FindAllString(ss, -1)
if len(signs) == 0 {
f, err := strconv.ParseFloat(ss, 64)
if err != nil {
fmt.Printf("could not convert %s to number", s)
return 0
}

return f
}

// Use last sign as decimal separator and ignore others
// Find idx and replace whatever sign was to a decimal point
sign := signs[len(signs)-1]
signIdx := strings.LastIndex(ss, sign)
sign = "."
left := regexp.MustCompile(`[0-9]`).FindAllString(ss[:signIdx], -1)
right := ss[signIdx+1:]
n, err := strconv.ParseFloat(strings.Join(append(left, []string{sign, right}...), ""), 64)
if err != nil {
fmt.Printf("could not convert %s to number", s)
return 0
}
if isNeg {
n = n * -1
}
return n
}

// amountFromString formats number strings to float64 type
func amountFromString(s string) float64 {
if s == "" {
return 0
}

// Only leave the last decimal point and remove all other points, commas and spaces
lastDec := strings.LastIndex(s, ".")
if lastDec != -1 {
s = strings.Replace(s, ".", "", strings.Count(s, ".")-1)
}

s = strings.ReplaceAll(s, ",", "")
s = strings.ReplaceAll(s, " ", "")

// Convert to float
f, err := strconv.ParseFloat(s, 64)
if err != nil {
log.Fatal(err)
}

return f
}

// url composes the fx exchange rate url for a given currency and year
// It accounts for the 2024 currency change and has a default set of currencies to get rates for, to avoid multiple fetches in common currencies
func url(currency string, year int) string {
Expand Down Expand Up @@ -186,7 +121,12 @@ func (fx *Exchange) grabRates(year int, currency string) (err error) {

for _, r := range resp.Rates {
storeKey := fmt.Sprintf("%s%d", r.Currency, year)
fx.rates[storeKey] = amountFromString(r.Rate)
rate, err := parseFloat(r.Rate)
if err != nil {
log.Println("could not convert rate", r.Rate, "to float")
}

fx.rates[storeKey] = rate
}

return
Expand All @@ -199,6 +139,19 @@ type hnbApiResponse struct {
} `json:"rates"`
}

func parseFloat(s string) (float64, error) {
// Remove dots, replace commas with dot
s = strings.ReplaceAll(s, ".", "")
s = strings.ReplaceAll(s, ",", ".")
// Convert to float
f, err := strconv.ParseFloat(s, 64)
if err != nil {
return 0, err
}

return f, nil
}

func New() *Exchange {
return &Exchange{grabRetries: 3, rates: make(map[string]float64)}
}

0 comments on commit cb29f3b

Please sign in to comment.