Skip to content

Commit

Permalink
Initial implementation of bank account
Browse files Browse the repository at this point in the history
  • Loading branch information
oxisto committed Dec 29, 2023
1 parent 0e223b4 commit c5874e1
Show file tree
Hide file tree
Showing 34 changed files with 1,616 additions and 587 deletions.
19 changes: 19 additions & 0 deletions cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,25 @@
// integrated client.
package cli

import (
"net/http"

"connectrpc.com/connect"
"github.com/oxisto/money-gopher/gen/portfoliov1connect"
)

// Session holds all necessary information about the current CLI session.
type Session struct {
PortfolioClient portfoliov1connect.PortfolioServiceClient
}

func NewSession() *Session {
var s Session

s.PortfolioClient = portfoliov1connect.NewPortfolioServiceClient(
http.DefaultClient, "http://localhost:8080",
connect.WithHTTPGet(),
)

return &s
}
37 changes: 37 additions & 0 deletions cli/commands/account.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package commands

import (
"context"
"fmt"

"connectrpc.com/connect"
"github.com/oxisto/money-gopher/cli"
portfoliov1 "github.com/oxisto/money-gopher/gen"
)

type BankAccountCmd struct {
Create CreateBankAccountCmd `cmd:"" help:"Creates a new bank account."`
}

type CreateBankAccountCmd struct {
Name string `help:"The identifier of the portfolio, e.g. mybank/myportfolio" required:""`
DisplayName string `help:"The display name of the portfolio"`
}

func (cmd *CreateBankAccountCmd) Run(s *cli.Session) error {
res, err := s.PortfolioClient.CreateBankAccount(
context.Background(),
connect.NewRequest(&portfoliov1.CreateBankAccountRequest{
BankAccount: &portfoliov1.BankAccount{
Name: cmd.Name,
DisplayName: cmd.DisplayName,
},
}),
)
if err != nil {
return err
}

fmt.Println(res.Msg)
return nil
}
5 changes: 3 additions & 2 deletions cli/commands/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ import (
var CLI struct {
Debug bool `help:"Enable debug mode."`

Security SecurityCmd `cmd:"" help:"Security commands."`
Portfolio PortfolioCmd `cmd:"" help:"Portfolio commands."`
Security SecurityCmd `cmd:"" help:"Security commands."`
Portfolio PortfolioCmd `cmd:"" help:"Portfolio commands."`
BankAccount BankAccountCmd `cmd:"" help:"Bank account commands."`

Completion kongcompletion.Completion `cmd:"" help:"Outputs shell code for initializing tab completions" hidden:"" completion-shell-default:"false"`
}
2 changes: 0 additions & 2 deletions cli/commands/portfolio.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,8 +209,6 @@ func eventTypeFrom(typ string) portfoliov1.PortfolioEventType {
return portfoliov1.PortfolioEventType_PORTFOLIO_EVENT_TYPE_DELIVERY_INBOUND
} else if typ == "delivery-outbound" {
return portfoliov1.PortfolioEventType_PORTFOLIO_EVENT_TYPE_DELIVERY_OUTBOUND
} else if typ == "dividend" {
return portfoliov1.PortfolioEventType_PORTFOLIO_EVENT_TYPE_DIVIDEND
}

return portfoliov1.PortfolioEventType_PORTFOLIO_EVENT_TYPE_UNSPECIFIED
Expand Down
2 changes: 1 addition & 1 deletion cmd/mgo/mgo.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,6 @@ func main() {
ctx, err := parser.Parse(os.Args[1:])
parser.FatalIfErrorf(err)

err = ctx.Run(&cli.Session{})
err = ctx.Run(cli.NewSession())
parser.FatalIfErrorf(err)
}
26 changes: 25 additions & 1 deletion finance/calculation.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,16 @@ type calculation struct {
Fees *portfoliov1.Currency
Taxes *portfoliov1.Currency

Cash *portfoliov1.Currency

fifo []*fifoTx
}

func NewCalculation(txs []*portfoliov1.PortfolioEvent) *calculation {
var c calculation
c.Fees = portfoliov1.Zero()
c.Taxes = portfoliov1.Zero()
c.Cash = portfoliov1.Zero()

for _, tx := range txs {
c.Apply(tx)
Expand All @@ -58,11 +61,20 @@ func (c *calculation) Apply(tx *portfoliov1.PortfolioEvent) {
case portfoliov1.PortfolioEventType_PORTFOLIO_EVENT_TYPE_DELIVERY_INBOUND:
fallthrough
case portfoliov1.PortfolioEventType_PORTFOLIO_EVENT_TYPE_BUY:
var (
total *portfoliov1.Currency
)

// Increase the amount of shares and the fees by the value stored in the
// transaction
c.Fees.PlusAssign(tx.Fees)
c.Amount += tx.Amount

total = portfoliov1.Times(tx.Price, tx.Amount).Plus(tx.Fees).Plus(tx.Taxes)

// Decrease our cash
c.Cash.MinusAssign(total)

// Add the transaction to the FIFO list. We need to have a list because
// sold shares are sold according to the FIFO principle. We therefore
// need to store this information to reduce the amount in the items
Expand All @@ -77,14 +89,20 @@ func (c *calculation) Apply(tx *portfoliov1.PortfolioEvent) {
fallthrough
case portfoliov1.PortfolioEventType_PORTFOLIO_EVENT_TYPE_SELL:
var (
sold float64
sold float64
total *portfoliov1.Currency
)

// Increase the fees and taxes by the value stored in the
// transaction
c.Fees.PlusAssign(tx.Fees)
c.Taxes.PlusAssign(tx.Taxes)

total = portfoliov1.Times(tx.Price, tx.Amount).Plus(tx.Fees).Plus(tx.Taxes)

// Increase our cash
c.Cash.PlusAssign(total)

// Store the amount of shares sold in this variable, since we later need
// to decrease it while looping through the FIFO list
sold = tx.Amount
Expand Down Expand Up @@ -128,6 +146,12 @@ func (c *calculation) Apply(tx *portfoliov1.PortfolioEvent) {

sold -= n
}
case portfoliov1.PortfolioEventType_PORTFOLIO_EVENT_TYPE_DEPOSIT_CASH:
// Add to the cash
c.Cash.PlusAssign(tx.Price)
case portfoliov1.PortfolioEventType_PORTFOLIO_EVENT_TYPE_WITHDRAW_CASH:

Check warning on line 152 in finance/calculation.go

View check run for this annotation

Codecov / codecov/patch

finance/calculation.go#L152

Added line #L152 was not covered by tests
// Remove from the cash
c.Cash.MinusAssign(tx.Price)

Check warning on line 154 in finance/calculation.go

View check run for this annotation

Codecov / codecov/patch

finance/calculation.go#L154

Added line #L154 was not covered by tests
}
}

Expand Down
7 changes: 6 additions & 1 deletion finance/calculation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ func TestNewCalculation(t *testing.T) {
name: "buy and sell",
args: args{
txs: []*portfoliov1.PortfolioEvent{
{
Type: portfoliov1.PortfolioEventType_PORTFOLIO_EVENT_TYPE_DEPOSIT_CASH,
Price: portfoliov1.Value(500000),
},
{
Type: portfoliov1.PortfolioEventType_PORTFOLIO_EVENT_TYPE_BUY,
Amount: 5,
Expand Down Expand Up @@ -88,7 +92,8 @@ func TestNewCalculation(t *testing.T) {
assert.Equals(t, 491425, int(c.NetValue().Value)) &&
assert.Equals(t, 494614, int(c.GrossValue().Value)) &&
assert.Equals(t, 19657, int(c.NetPrice().Value)) &&
assert.Equals(t, 19785, int(c.GrossPrice().Value))
assert.Equals(t, 19785, int(c.GrossPrice().Value)) &&
assert.Equals(t, 44099, int(c.Cash.Value))
},
},
}
Expand Down
93 changes: 93 additions & 0 deletions gen/currency.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Copyright 2023 Christian Banse
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// This file is part of The Money Gopher.
package portfoliov1

import (
"fmt"
"math"
)

func Zero() *Currency {
// TODO(oxisto): Somehow make it possible to change default currency
return &Currency{Symbol: "EUR"}
}

func Value(v int32) *Currency {
// TODO(oxisto): Somehow make it possible to change default currency
return &Currency{Symbol: "EUR", Value: v}
}

func (c *Currency) PlusAssign(o *Currency) {
if o != nil {
c.Value += o.Value
}
}

func (c *Currency) MinusAssign(o *Currency) {
if o != nil {
c.Value -= o.Value
}
}

func Plus(a *Currency, b *Currency) *Currency {
return &Currency{
Value: a.Value + b.Value,
Symbol: a.Symbol,
}
}

func (a *Currency) Plus(b *Currency) *Currency {
if b == nil {
return &Currency{
Value: a.Value,
Symbol: a.Symbol,
}
}

return &Currency{
Value: a.Value + b.Value,
Symbol: a.Symbol,
}
}

func Minus(a *Currency, b *Currency) *Currency {
return &Currency{
Value: a.Value - b.Value,
Symbol: a.Symbol,
}
}

func Divide(a *Currency, b float64) *Currency {
return &Currency{
Value: int32(math.Round((float64(a.Value) / b))),
Symbol: a.Symbol,
}
}

func Times(a *Currency, b float64) *Currency {
return &Currency{
Value: int32(math.Round((float64(a.Value) * b))),
Symbol: a.Symbol,
}
}

func (c *Currency) Pretty() string {
return fmt.Sprintf("%.0f %s", float32(c.Value)/100, c.Symbol)
}

func (c *Currency) IsZero() bool {
return c == nil || c.Value == 0
}
Loading

0 comments on commit c5874e1

Please sign in to comment.