Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using a dedicated currency object instead of float #255

Merged
merged 4 commits into from
Dec 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions cli/commands/portfolio.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,10 @@ func (l *ListPortfolioCmd) Run(s *cli.Session) error {
strings.Repeat("-", 15),
strings.Repeat("-", 15),
15, "Market Value",
15, fmt.Sprintf("%.02f €", snapshot.Msg.TotalMarketValue),
15, snapshot.Msg.TotalMarketValue.Pretty(),
15, "Performance",
15, fmt.Sprintf("%s € (%s %%)",
greenOrRed(snapshot.Msg.TotalProfitOrLoss),
greenOrRed(float64(snapshot.Msg.TotalProfitOrLoss.Value)),
greenOrRed(snapshot.Msg.TotalGains*100),
),
)
Expand Down Expand Up @@ -148,7 +148,7 @@ func (cmd *ShowPortfolioCmd) Run(s *cli.Session) error {
return nil
}

func greenOrRed(f float32) string {
func greenOrRed(f float64) string {
if f < 0 {
return color.RedString("%.02f", f)
} else {
Expand All @@ -160,7 +160,7 @@ type CreateTransactionCmd struct {
PortfolioName string `required:"" predictor:"portfolio" help:"The name of the portfolio where the transaction will be created in"`
SecurityName string `arg:"" predictor:"security" help:"The name of the security this transaction belongs to (its ISIN)"`
Type string `required:"" enum:"buy,sell,delivery-inbound,delivery-outbound,dividend" default:"buy"`
Amount float32 `required:"" help:"The amount of securities involved in the transaction"`
Amount float64 `required:"" help:"The amount of securities involved in the transaction"`
Price float32 `required:"" help:"The price without fees or taxes"`
Fees float32 `help:"Any fees that applied to the transaction"`
Taxes float32 `help:"Any taxes that applied to the transaction"`
Expand All @@ -175,9 +175,9 @@ func (cmd *CreateTransactionCmd) Run(s *cli.Session) error {
Type: eventTypeFrom(cmd.Type), // eventTypeFrom(cmd.Type)
Amount: cmd.Amount,
Time: timeOrNow(cmd.Time),
Price: cmd.Price,
Fees: cmd.Fees,
Taxes: cmd.Taxes,
Price: portfoliov1.Value(int32(cmd.Price * 100)),
Fees: portfoliov1.Value(int32(cmd.Fees * 100)),
Taxes: portfoliov1.Value(int32(cmd.Taxes * 100)),
},
})

Expand Down
56 changes: 29 additions & 27 deletions finance/calculation.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,26 +27,24 @@ import (
// list. We basically need to copy the values from the original transaction,
// since we need to modify it.
type fifoTx struct {
// amount of shares in this transaction
amount float32
// value contains the net value of this transaction, i.e., without taxes and fees
value float32
// fees contain any fees associated to this transaction
fees float32
// ppu is the price per unit (amount)
ppu float32
amount float64 // amount of shares in this transaction
value *portfoliov1.Currency // value contains the net value of this transaction, i.e., without taxes and fees
fees *portfoliov1.Currency // fees contain any fees associated to this transaction
ppu *portfoliov1.Currency // ppu is the price per unit (amount)
}

type calculation struct {
Amount float32
Fees float32
Taxes float32
Amount float64
Fees *portfoliov1.Currency
Taxes *portfoliov1.Currency

fifo []*fifoTx
}

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

for _, tx := range txs {
c.Apply(tx)
Expand All @@ -62,7 +60,7 @@ func (c *calculation) Apply(tx *portfoliov1.PortfolioEvent) {
case portfoliov1.PortfolioEventType_PORTFOLIO_EVENT_TYPE_BUY:
// Increase the amount of shares and the fees by the value stored in the
// transaction
c.Fees += tx.Fees
c.Fees.PlusAssign(tx.Fees)
c.Amount += tx.Amount

// Add the transaction to the FIFO list. We need to have a list because
Expand All @@ -72,20 +70,20 @@ func (c *calculation) Apply(tx *portfoliov1.PortfolioEvent) {
c.fifo = append(c.fifo, &fifoTx{
amount: tx.Amount,
ppu: tx.Price,
value: tx.Price * float32(tx.Amount),
value: portfoliov1.Times(tx.Price, tx.Amount),
fees: tx.Fees,
})
case portfoliov1.PortfolioEventType_PORTFOLIO_EVENT_TYPE_DELIVERY_OUTBOUND:
fallthrough
case portfoliov1.PortfolioEventType_PORTFOLIO_EVENT_TYPE_SELL:
var (
sold float32
sold float64
)

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

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

// Reduce the number of shares in this entry by the sold amount (but
// max it to the item's amount).
n := float32(math.Min(float64(sold), float64(item.amount)))
n := math.Min(float64(sold), float64(item.amount))
item.amount -= n

// Adjust the value with the new amount
item.value = item.ppu * float32(item.amount)
item.value = portfoliov1.Times(item.ppu, item.amount)

// If no shares are left in this FIFO transaction, also remove the
// fees, because they are now associated to the sale and not part of
// the price calculation anymore.
if item.amount <= 0 {
item.fees = 0
item.fees = portfoliov1.Zero()
}

sold -= n
}
}
}

func (c *calculation) NetValue() (f float32) {
func (c *calculation) NetValue() (f *portfoliov1.Currency) {
f = portfoliov1.Zero()

for _, item := range c.fifo {
f += item.value
f.PlusAssign(item.value)
}

return
}

func (c *calculation) GrossValue() (f float32) {
func (c *calculation) GrossValue() (f *portfoliov1.Currency) {
f = portfoliov1.Zero()

for _, item := range c.fifo {
f += (item.value + item.fees)
f.PlusAssign(portfoliov1.Plus(item.value, item.fees))
}

return
}

func (c *calculation) NetPrice() (f float32) {
return c.NetValue() / float32(c.Amount)
func (c *calculation) NetPrice() (f *portfoliov1.Currency) {
return portfoliov1.Divide(c.NetValue(), c.Amount)
}

func (c *calculation) GrossPrice() (f float32) {
return c.GrossValue() / float32(c.Amount)
func (c *calculation) GrossPrice() (f *portfoliov1.Currency) {
return portfoliov1.Divide(c.GrossValue(), c.Amount)
}
38 changes: 19 additions & 19 deletions finance/calculation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,55 +40,55 @@ func TestNewCalculation(t *testing.T) {
{
Type: portfoliov1.PortfolioEventType_PORTFOLIO_EVENT_TYPE_BUY,
Amount: 5,
Price: 181.10,
Fees: 7.16,
Price: portfoliov1.Value(18110),
Fees: portfoliov1.Value(716),
},
{
Type: portfoliov1.PortfolioEventType_PORTFOLIO_EVENT_TYPE_SELL,
Amount: 2,
Price: 304.30,
Fees: 6.42,
Taxes: 16.32,
Price: portfoliov1.Value(30430),
Fees: portfoliov1.Value(642),
Taxes: portfoliov1.Value(1632),
},
{
Type: portfoliov1.PortfolioEventType_PORTFOLIO_EVENT_TYPE_BUY,
Amount: 5,
Price: 290,
Fees: 8.53,
Price: portfoliov1.Value(29000),
Fees: portfoliov1.Value(853),
},
{
Type: portfoliov1.PortfolioEventType_PORTFOLIO_EVENT_TYPE_SELL,
Amount: 3,
Price: 220,
Fees: 8.45,
Price: portfoliov1.Value(22000),
Fees: portfoliov1.Value(845),
},
{
Type: portfoliov1.PortfolioEventType_PORTFOLIO_EVENT_TYPE_BUY,
Amount: 5,
Price: 203.30,
Fees: 7.44,
Price: portfoliov1.Value(20330),
Fees: portfoliov1.Value(744),
},
{
Type: portfoliov1.PortfolioEventType_PORTFOLIO_EVENT_TYPE_BUY,
Amount: 5,
Price: 196.45,
Fees: 7.36,
Price: portfoliov1.Value(19645),
Fees: portfoliov1.Value(736),
},
{
Type: portfoliov1.PortfolioEventType_PORTFOLIO_EVENT_TYPE_BUY,
Amount: 10,
Price: 146.55,
Fees: 8.56,
Price: portfoliov1.Value(14655),
Fees: portfoliov1.Value(856),
},
},
},
want: func(t *testing.T, c *calculation) bool {
return true &&
assert.Equals(t, 25, c.Amount) &&
assert.Equals(t, 4914.25, c.NetValue()) &&
assert.Equals(t, 4946.14, c.GrossValue()) &&
assert.Equals(t, 196.57, c.NetPrice()) &&
assert.Equals(t, 197.84561, c.GrossPrice())
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))
},
},
}
Expand Down
Loading