Skip to content

Commit

Permalink
Enable custom xlsx import with non-ibkr data
Browse files Browse the repository at this point in the history
  • Loading branch information
thinktwice13 committed Jun 13, 2022
1 parent 4b9d4ba commit 23bd49e
Show file tree
Hide file tree
Showing 10 changed files with 545 additions and 278 deletions.
72 changes: 50 additions & 22 deletions fx.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,30 +13,53 @@ import (
"time"
)

type Rates map[int]map[string]float64
type Rates struct {
l *sync.Mutex
rates map[int]map[string]float64
}

func (r *Rates) Rate(ccy string, yr int) float64 {
return r.rates[yr][ccy] // Should not be any errors here. Throw if not fetched correctly
}

func (r Rates) Rate(ccy string, yr int) float64 {
return r[yr][ccy] // TODO Check errors
func (r *Rates) setRates(y int, rates map[string]float64) {
r.l.Lock()
defer r.l.Unlock()
r.rates[y] = rates
}

func NewFxRates(currencies []string, years []int) (Rates, error) {
r := make(Rates, len(years))
var m sync.Mutex
// NewFxRates creates a new Rates struct by fetching currency exhange rates for provided years and currencies
// TODO Do not fetch in New
func NewFxRates(currencies []string, years []int) (*Rates, error) {
r := &Rates{
l: new(sync.Mutex),
rates: map[int]map[string]float64{},
}
var wg sync.WaitGroup
wg.Add(len(years))
for _, y := range years {
go func(r Rates, y int, m *sync.Mutex, wg *sync.WaitGroup) {
workers := maxWorkers
if len(years) < maxWorkers {
workers = len(years)
}
wg.Add(workers)
yrs := make(chan int)
for w := 0; w < workers; w++ {
go func(r *Rates, yrs <-chan int, wg *sync.WaitGroup) {
defer wg.Done()
rates, err := grabHRKRates(y, currencies, 3)
if err != nil {
fmt.Printf("failed getting exchange rates for year %d", y)
return
for y := range yrs {
rates, err := grabHRKRates(y, currencies, 3)
if err != nil {
log.Fatalln("failed getting currency exchange rates from hnb.hr. Please try again later")
}
r.setRates(y, rates)
}
m.Lock()
defer m.Unlock()
r[y] = rates
}(r, y, &m, &wg)
}(r, yrs, &wg)
}

for _, y := range years {
yrs <- y
}

close(yrs)
wg.Wait()
return r, nil
}
Expand All @@ -51,16 +74,18 @@ type ratesResponse struct {
Rates []rateResponse `json:"rates"`
}

// grabHRKRates fetches HRK exchange rates for a list of currencies in a provided year from hnb.hr
func grabHRKRates(year int, c []string, retries int) (map[string]float64, error) {
if year <= 1900 {
log.Fatal("Cannot get currency rates for a year before 1901")
}

// TODO Other currencies than HRK
date := LastDateForYear(year)
date := lastDateForYear(year)

// url := fmt.Sprintf("https://api.hnb.hr/tecajn/v1?datum-od=%s&datum-do=%s", from.Format("2006-01-02"), to.Format("2006-01-02"))
url := fmt.Sprintf("https://api.hnb.hr/tecajn/v1?datum=%s", date.Format("2006-01-02"))
baseUrl := "https://api.hnb.hr/tecajn/v1"
url := fmt.Sprintf(baseUrl+"?datum=%s", date.Format("2006-01-02"))
for _, curr := range c {
url = url + "&valuta=" + curr
}
Expand Down Expand Up @@ -98,7 +123,10 @@ func grabHRKRates(year int, c []string, retries int) (map[string]float64, error)
return rm, nil
}

func LastDateForYear(y int) (d time.Time) {
// lastDateForYear calculates last day of the year for the input
// If year is current year, returns today
// Return time set to UTC
func lastDateForYear(y int) (d time.Time) {
if y == time.Now().Year() {
d = time.Now().UTC()
} else {
Expand All @@ -108,11 +136,11 @@ func LastDateForYear(y int) (d time.Time) {
return
}

// formatApiRate formats the api response currency rate received from hnb.hr
func formatApiRate(r string) float64 {
s := strings.ReplaceAll(strings.ReplaceAll(r, ".", ""), ",", ".")

if s == "" {
log.Fatalf("Cannot create amount from %s", s)
log.Fatalf("cannot create amount from %s", s)
}
s = strings.ReplaceAll(s, ",", "")
v, err := strconv.ParseFloat(s, 64)
Expand Down
13 changes: 11 additions & 2 deletions import_results.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,13 +89,16 @@ func (r *ImportResults) AddInstrumentInfo(symbols []string, cat string) {
}
a.Category = cat
}
func (r *ImportResults) AddTrade(sym, ccy string, tm time.Time, qty, price, fee float64) {
func (r *ImportResults) AddTrade(sym, ccy string, tm *time.Time, qty, price, fee float64) {
if sym == "" || ccy == "" || tm == nil || qty*price == 0 {
return
}
r.l.Lock()
defer r.l.Unlock()
a := r.assets.bySymbols(sym)
t := Trade{}
t.Currency = ccy
t.Time = tm
t.Time = *tm
t.Quantity = qty
t.Price = price
t.Fee = fee
Expand All @@ -106,6 +109,9 @@ func (r *ImportResults) AddTrade(sym, ccy string, tm time.Time, qty, price, fee

}
func (r *ImportResults) AddDividend(sym, ccy string, yr int, amt float64, isTax bool) {
if sym == "" || ccy == "" || yr == 0 || amt == 0 {
return
}
r.l.Lock()
defer r.l.Unlock()
a := r.assets.bySymbols(sym)
Expand All @@ -123,6 +129,9 @@ func (r *ImportResults) AddDividend(sym, ccy string, yr int, amt float64, isTax
r.years[yr] = true
}
func (r *ImportResults) AddFee(ccy string, amt float64, yr int) {
if yr == 0 || amt == 0 || ccy == "" {
return
}
r.l.Lock()
defer r.l.Unlock()
f := Transaction{}
Expand Down
27 changes: 27 additions & 0 deletions import_results_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package main

import (
"testing"
)

func TestAssetBySymbols(t *testing.T) {
a1 := &AssetImport{}
a1.Symbols = []string{"A"}
a2 := &AssetImport{}
a2.Symbols = []string{"B", "C"}
a3 := &AssetImport{}
a3.Symbols = []string{"A", "B", "D"}

assets := assets{}
assets.bySymbols(a1.Symbols...)
assets.bySymbols(a2.Symbols...)
assets.bySymbols(a3.Symbols...)

assetPtrs := make(map[*AssetImport]bool)
for _, a := range assets {
assetPtrs[a] = true
}
if len(assetPtrs) != 1 {
t.Errorf("Expected 1 unique asset. Got %d", len(assetPtrs))
}
}
15 changes: 10 additions & 5 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,23 @@ import (
"log"
"os"
"path/filepath"
"runtime"
"time"
)

var maxWorkers int

func main() {
t := time.Now()
CheckPWD()
maxWorkers = runtime.NumCPU()
SetPWD()

assets, fees, years, currencies := readDir()
if len(assets) == 0 {
fmt.Println("No data found. Exiting")
os.Exit(0)
}

// Fetch currency conversion rates per year
rates, err := NewFxRates(currencies, years)
if err != nil {
Expand All @@ -34,17 +40,16 @@ func main() {

// Write asset summaries and tax reports to spreadsheet
r := NewReport("Portfolio Report")
err = writeReport(&tr, r)
err = writeReport(&summaries, r)
writeReport(&tr, r)
writeReport(&summaries, r)
err = r.Save()
if err != nil {
log.Fatalln(err)
}
fmt.Println("Finished in", time.Since(t))
os.Exit(0)
}

func CheckPWD() {
func SetPWD() {
if os.Getenv("GOPATH") == "" {
dirErr := "could not read directory"
exec, err := os.Executable()
Expand Down
Loading

0 comments on commit 23bd49e

Please sign in to comment.