Skip to content

Commit

Permalink
Write tax reports to file, install excelize
Browse files Browse the repository at this point in the history
  • Loading branch information
thinktwice13 committed Jun 10, 2022
1 parent f9f6d07 commit 76f95f9
Show file tree
Hide file tree
Showing 6 changed files with 307 additions and 30 deletions.
12 changes: 12 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
module ibkr-report

go 1.18

require (
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
github.com/richardlehane/mscfb v1.0.4 // indirect
github.com/richardlehane/msoleps v1.0.1 // indirect
github.com/xuri/efp v0.0.0-20220407160117-ad0f7a785be8 // indirect
github.com/xuri/excelize/v2 v2.6.0 // indirect
github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22 // indirect
golang.org/x/crypto v0.0.0-20220408190544-5352b0902921 // indirect
golang.org/x/net v0.0.0-20220407224826-aac1ed45d8e3 // indirect
golang.org/x/text v0.3.7 // indirect
)
34 changes: 34 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM=
github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk=
github.com/richardlehane/msoleps v1.0.1 h1:RfrALnSNXzmXLbGct/P2b4xkFz4e8Gmj/0Vj9M9xC1o=
github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/xuri/efp v0.0.0-20220407160117-ad0f7a785be8 h1:3X7aE0iLKJ5j+tz58BpvIZkXNV7Yq4jC93Z/rbN2Fxk=
github.com/xuri/efp v0.0.0-20220407160117-ad0f7a785be8/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
github.com/xuri/excelize/v2 v2.6.0 h1:m/aXAzSAqxgt74Nfd+sNzpzVKhTGl7+S9nbG4A57mF4=
github.com/xuri/excelize/v2 v2.6.0/go.mod h1:Q1YetlHesXEKwGFfeJn7PfEZz2IvHb6wdOeYjBxVcVs=
github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22 h1:OAmKAfT06//esDdpi/DZ8Qsdt4+M5+ltca05dA5bG2M=
github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
golang.org/x/crypto v0.0.0-20220408190544-5352b0902921 h1:iU7T1X1J6yxDr0rda54sWGkHgOp5XJrqm79gcNlC2VM=
golang.org/x/crypto v0.0.0-20220408190544-5352b0902921/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220407224826-aac1ed45d8e3 h1:EN5+DfgmRMvRUrMGERW2gQl3Vc+Z7ZMnI/xdEpPSf0c=
golang.org/x/net v0.0.0-20220407224826-aac1ed45d8e3/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
10 changes: 8 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,16 @@ func main() {

summaries := summarizeAssets(assets, rates)
print(len(summaries))
PrettyPrint(summaries)
// PrettyPrint(summaries)
convFees := convertFees(fees, rates)
tr := buildTaxReport(summaries, convFees, len(years))
tr := taxReport(summaries, convFees, len(years))
fmt.Println(len(tr))

PrettyPrint(tr)

r := NewReport("Portfolio Report")
tr.WriteTo(r)
r.Save()
}

type YearAmount struct {
Expand Down
38 changes: 38 additions & 0 deletions report_file.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package main

import (
"fmt"
"github.com/xuri/excelize/v2"
"strconv"
)

type Report struct {
f *excelize.File
filename string
}

func (r *Report) WriteRows(sheet string, rows [][]interface{}) error {
r.f.NewSheet(sheet)

for i := range rows {
row := &rows[i]
err := r.f.SetSheetRow(sheet, "A"+strconv.Itoa(i+1), row)
if err != nil {
fmt.Println(err)
return err
}
}
return nil
}

func (r *Report) Save() error {
err := r.f.SaveAs(r.filename)
if err != nil {
return err
}
return nil
}

func NewReport(filename string) *Report {
return &Report{f: excelize.NewFile(), filename: filename + ".xlsx"}
}
75 changes: 47 additions & 28 deletions assetsummary.go → summaries.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,51 @@ import (

type AssetYear struct {
Pl, Taxable, Fees, Dividends, WithholdingTax float64
Year int
}

type Asset struct {
Instrument
Holdings []Trade
Summary map[int]*AssetYear
FirstPurchase time.Time
Holdings []Trade
Years []AssetYear
}

type AssetSummary map[int]*AssetYear

func (s AssetSummary) year(y int) *AssetYear {
_, ok := s[y]
if !ok {
s[y] = new(AssetYear)
s[y] = &AssetYear{Year: y}
}
return s[y]
}

type SummaryReport []Asset

func (s *SummaryReport) Title() string {
return "Summary"
}

func (s *SummaryReport) HeaderRow() []interface{} {
return []interface{}{
"Symbols",
"Category",
"Year",
"Profit/Loss",
"Taxable PL",
"Fees",
"Dividends",
"Withholding Tax",
}
}

type Rater interface {
Rate(string, int) float64
}

func summarizeAssets(imports []AssetImport, r Rater) []Asset {
assets := make([]Asset, len(imports))
func summarizeAssets(imports []AssetImport, r Rater) SummaryReport {
assets := make(SummaryReport, len(imports))

for i, ai := range imports {
sort.Slice(ai.Trades, func(i, j int) bool {
Expand All @@ -54,7 +75,7 @@ func summarizeAssets(imports []AssetImport, r Rater) []Asset {
proceeds := s.Price * c.Quantity * r.Rate(s.Currency, s.Time.Year())
cost := c.Price * c.Quantity * r.Rate(c.Currency, s.Time.Year())
y.Pl += proceeds - cost
if s.Time.After(TaxableDeadline(c.Time)) {
if s.Time.After(taxableDeadline(c.Time)) {
continue
}
y.Taxable += proceeds - cost
Expand All @@ -78,13 +99,28 @@ func summarizeAssets(imports []AssetImport, r Rater) []Asset {
sum.year(t.Year).WithholdingTax += amt
}

assets[i] = Asset{
Instrument: ai.Instrument,
Holdings: holdings,
Summary: sum,
a := Asset{
Instrument: ai.Instrument,
FirstPurchase: ai.Trades[0].Time,
Holdings: holdings,
Years: make([]AssetYear, 0, len(sum)),
}

for _, data := range sum {
a.Years = append(a.Years, *data)
}

sort.Slice(a.Years, func(i, j int) bool {
return a.Years[i].Year < a.Years[j].Year
})

assets[i] = a
}

sort.Slice(assets, func(i, j int) bool {
return assets[i].FirstPurchase.Before(assets[j].FirstPurchase)
})

return assets
}

Expand Down Expand Up @@ -156,23 +192,6 @@ func tradeAsset(ts []Trade) ([]Sale, []Transaction, []Trade) {
return sales, fees, fifo.data
}

func TaxableDeadline(since time.Time) time.Time {
func taxableDeadline(since time.Time) time.Time {
return since.AddDate(2, 0, 0)
}

func filterFromTo(l []int, min, max int) []int {
cap := max - min + 1
if len(l) < cap {
cap = len(l)
}
filtered := make([]int, 0, cap)
for _, i := range l {
if i < min || i > max {
continue
}

filtered = append(filtered, i)
}

return filtered
}
Loading

0 comments on commit 76f95f9

Please sign in to comment.