Skip to content

Commit

Permalink
Refactoring portfolio
Browse files Browse the repository at this point in the history
Added Postion entity
  • Loading branch information
marcelometal committed Aug 30, 2020
1 parent 31dbd7b commit 862aa57
Show file tree
Hide file tree
Showing 10 changed files with 160 additions and 130 deletions.
4 changes: 2 additions & 2 deletions api/portfolios.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func (s *server) portfolio(c echo.Context) error {
}

// FIXME
err = s.db.GetPortfolioItems(result, year)
err = s.db.GetPortfolioData(result, year)
if err != nil {
errMsg := fmt.Sprintf("Error on get portfolio '%s' items: %v", slug, err)
return logAndReturnError(c, errMsg)
Expand Down Expand Up @@ -88,7 +88,7 @@ func (s *server) portfolios(c echo.Context) error {
portfolios := make([]wallet.Portfolio, len(allPortfolios))
for idx, p := range allPortfolios {
portfolio := p.(*wallet.Portfolio)
err := s.db.GetPortfolioItems(portfolio, year)
err := s.db.GetPortfolioData(portfolio, year)
if err != nil {
errMsg := fmt.Sprintf("Error on get portfolio items: %v", err)
return logAndReturnError(c, errMsg)
Expand Down
2 changes: 1 addition & 1 deletion db/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ type DB interface {
GetBySlug(slug string, d wallet.Queryable) error
Update(id string, d wallet.Queryable) (*mongo.UpdateResult, error)

GetPortfolioItems(p *wallet.Portfolio, year int) error
GetPortfolioData(p *wallet.Portfolio, year int) error
GetAllOperations() (interface{}, error)
GetAllPurchases() (interface{}, error)
GetAllSales() (interface{}, error)
Expand Down
5 changes: 5 additions & 0 deletions db/operations.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ func (m *mongoSession) getOperationsSymbols(filter bson.M) ([]interface{}, error
return m.collection.Distinct(operationsCollection, "symbol", filter)
}

func (m *mongoSession) getItemTypes() ([]interface{}, error) {
log.Debug("[DB] getItemTypes")
return m.collection.Distinct(operationsCollection, "itemType", bson.M{})
}

func (m *mongoSession) getAllOperationsBySymbol(symbol, itemType string, year int) (wallet.OperationsList, error) {
log.Debug("[DB] getAllOperationsBySymbol")
date := time.Date(year, 12, 31, 23, 59, 59, 0, time.UTC)
Expand Down
81 changes: 42 additions & 39 deletions db/portfolios.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,78 +4,81 @@
package db

import (
"fmt"

"github.com/mfinancecombr/finance-wallet-api/financeapi"
"github.com/mfinancecombr/finance-wallet-api/wallet"
log "github.com/sirupsen/logrus"
"go.mongodb.org/mongo-driver/bson"
)

func (m *mongoSession) getPortfolioItem(itemType string, year int) (map[string]wallet.PortfolioItem, error) {
func (m *mongoSession) getPositionsByItemType(itemType string, year int) ([]wallet.Position, error) {
log.Debugf("[DB] Getting portfolio item %s", itemType)
operationsSymbols, err := m.getOperationsSymbols(bson.M{"itemType": itemType})
if err != nil {
return nil, err
}

items := map[string]wallet.PortfolioItem{}
// Get all data by itemType
query := ""
for _, s := range operationsSymbols {
symbol := s.(string)
portfolioItem := &wallet.PortfolioItem{}
// FIXME: one request
if err := financeapi.GetJSON("/"+itemType+"/"+symbol, portfolioItem); err != nil {
log.Errorf("Error on get stock item: %s", err)
query += fmt.Sprintf("symbols=%s&", s)
}
tempPosition := &map[string][]wallet.Position{}
url := fmt.Sprintf("/%s/?%s", itemType, query)
if err := financeapi.GetJSON(url, tempPosition); err != nil {
log.Warnf("Error on get %s symbols: %v", itemType, err)
}

// Convert to map of symbols
symbolsMap := map[string]wallet.Position{}
for _, a := range *tempPosition {
for _, item := range a {
symbolsMap[item.Symbol] = item
}
}

items := []wallet.Position{}
for _, s := range operationsSymbols {
symbol := s.(string)
operations, err := m.getAllOperationsBySymbol(symbol, itemType, year)
if err != nil {
return nil, err
}

// FIXME
broker := ""
if len(operations) > 0 {
operation := operations[0]
if operation != nil {
broker = operation.GetBrokerSlug()
}
var position wallet.Position
if val, ok := symbolsMap[symbol]; ok {
position = val
} else {
position = wallet.Position{}
}

portfolioItem.BrokerSlug = broker
portfolioItem.ItemType = itemType
portfolioItem.Operations = operations
portfolioItem.Recalculate()
items[symbol] = *portfolioItem
position.Symbol = symbol
position.ItemType = itemType
position.Operations = operations
position.Recalculate()
items = append(items, position)
}

return items, nil
}

// FIXME
func (m *mongoSession) GetPortfolioItems(portfolio *wallet.Portfolio, year int) error {
log.Debug("[DB] GetPortfolioItems")

slugs := []string{
"certificates-of-deposit",
"ficfi",
"fiis",
"stocks",
"stocks-funds",
"treasuries-direct",
func (m *mongoSession) GetPortfolioData(portfolio *wallet.Portfolio, year int) error {
log.Debug("[DB] GetPositions")
itemTypes, err := m.getItemTypes()
if err != nil {
return err
}

portfolio.Items = map[string]wallet.PortfolioItem{}
for _, slug := range slugs {
stocks, err := m.getPortfolioItem(slug, year)
portfolio.Items = map[string][]wallet.Position{}
for _, itemType := range itemTypes {
kind := itemType.(string)
positions, err := m.getPositionsByItemType(kind, year)
if err != nil {
log.Errorf("[DB] Error on get portfolio items: %v", err)
continue
}
for symbol, portfolioItem := range stocks {
portfolio.Items[symbol] = portfolioItem
}
portfolio.Items[kind] = positions
}

portfolio.Recalculate()

return nil
}
16 changes: 11 additions & 5 deletions docs/docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -1444,7 +1444,10 @@ var doc = `{
"items": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/wallet.PortfolioItem"
"type": "array",
"items": {
"$ref": "#/definitions/wallet.Position"
}
}
},
"name": {
Expand All @@ -1458,15 +1461,15 @@ var doc = `{
}
}
},
"wallet.PortfolioItem": {
"wallet.Position": {
"type": "object",
"required": [
"symbol"
],
"properties": {
"averagePrice": {
"type": "number"
},
"brokerSlug": {
"type": "string"
},
"change": {
"type": "number"
},
Expand Down Expand Up @@ -1515,6 +1518,9 @@ var doc = `{
},
"subSector": {
"type": "string"
},
"symbol": {
"type": "string"
}
}
},
Expand Down
16 changes: 11 additions & 5 deletions docs/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -1429,7 +1429,10 @@
"items": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/wallet.PortfolioItem"
"type": "array",
"items": {
"$ref": "#/definitions/wallet.Position"
}
}
},
"name": {
Expand All @@ -1443,15 +1446,15 @@
}
}
},
"wallet.PortfolioItem": {
"wallet.Position": {
"type": "object",
"required": [
"symbol"
],
"properties": {
"averagePrice": {
"type": "number"
},
"brokerSlug": {
"type": "string"
},
"change": {
"type": "number"
},
Expand Down Expand Up @@ -1500,6 +1503,9 @@
},
"subSector": {
"type": "string"
},
"symbol": {
"type": "string"
}
}
},
Expand Down
12 changes: 8 additions & 4 deletions docs/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,9 @@ definitions:
type: string
items:
additionalProperties:
$ref: '#/definitions/wallet.PortfolioItem'
items:
$ref: '#/definitions/wallet.Position'
type: array
type: object
name:
type: string
Expand All @@ -147,12 +149,10 @@ definitions:
- name
- slug
type: object
wallet.PortfolioItem:
wallet.Position:
properties:
averagePrice:
type: number
brokerSlug:
type: string
change:
type: number
closingPrice:
Expand Down Expand Up @@ -186,6 +186,10 @@ definitions:
type: number
subSector:
type: string
symbol:
type: string
required:
- symbol
type: object
wallet.Stock:
properties:
Expand Down
1 change: 0 additions & 1 deletion financeapi/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,5 @@ func GetJSON(path string, target interface{}) error {
return err
}
defer r.Body.Close()

return json.NewDecoder(r.Body).Decode(target)
}
85 changes: 12 additions & 73 deletions wallet/portfolio.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,35 +7,14 @@ import (
"math"
)

type PortfolioItem struct {
AveragePrice float64 `json:"averagePrice" bson:"averagePrice"`
BrokerSlug string `json:"brokerSlug" bson:"brokerSlug"`
Change float64 `json:"change" bson:"change"`
ClosingPrice float64 `json:"closingPrice" bson:"closingPrice"`
Commission float64 `json:"commission" bson:"commission"`
CostBasis float64 `json:"costBasis" bson:"costBasis"`
Gain float64 `json:"gain" bson:"gain"`
ItemType string `json:"itemType" bson:"itemType"`
LastPrice float64 `json:"lastPrice" bson:"lastPrice"`
LastYearHigh float64 `json:"lastYearHigh" bson:"lastYearHigh"`
LastYearLow float64 `json:"lastYearLow" bson:"lastYearLow"`
Name string `json:"name" bson:"name"`
Operations OperationsList `json:"operations" bson:"operations"`
OverallReturn float64 `json:"overallReturn" bson:"overallReturn"`
Sector string `json:"sector" bson:"sector"`
Segment string `json:"segment" bson:"segment"`
Shares float64 `json:"shares" bson:"shares"`
SubSector string `json:"subSector" bson:"subSector"`
}

type Portfolio struct {
CostBasis float64 `json:"costBasis" bson:"costBasis,omitempty"`
Gain float64 `json:"gain" bson:"gain,omitempty"`
ID string `json:"id,omitempty" bson:"_id,omitempty"`
Items map[string]PortfolioItem `json:"items" bson:"items,omitempty"`
Name string `json:"name" bson:"name" validate:"required"`
OverallReturn float64 `json:"overallReturn" bson:"overallReturn,omitempty"`
Slug string `json:"slug" bson:"slug" validate:"required"`
CostBasis float64 `json:"costBasis" bson:"costBasis,omitempty"`
Gain float64 `json:"gain" bson:"gain,omitempty"`
ID string `json:"id,omitempty" bson:"_id,omitempty"`
Items map[string][]Position `json:"items" bson:"items,omitempty"`
Name string `json:"name" bson:"name" validate:"required"`
OverallReturn float64 `json:"overallReturn" bson:"overallReturn,omitempty"`
Slug string `json:"slug" bson:"slug" validate:"required"`
}

func (s Portfolio) GetCollectionName() string {
Expand All @@ -57,54 +36,14 @@ func (p *Portfolio) Recalculate() {

costBasis := 0.0
gain := 0.0
for _, item := range p.Items {
costBasis += item.CostBasis
gain += item.Gain
for _, items := range p.Items {
for _, item := range items {
costBasis += item.CostBasis
gain += item.Gain
}
}

p.CostBasis = roundFloatTwoDecimalPlaces(costBasis)
p.Gain = roundFloatTwoDecimalPlaces(gain)
p.OverallReturn = roundFloatTwoDecimalPlaces(p.Gain * 100 / p.CostBasis)
}

func (pi *PortfolioItem) Recalculate() {
commission := 0.0
totalPrice := 0.0
totalShares := 0.0

for _, s := range pi.Operations {
var operationPrice = s.GetPrice()
var operationShares = s.GetShares()
var operationCommission = s.GetComission()
var operationType = s.(Tradable).GetType()
if operationType == "purchase" {
totalPrice += (operationPrice * operationShares) + operationCommission
totalShares += operationShares
commission += operationCommission
} else {
// To properly calculate the average price we need to remove from the cost basis
// based on the average price at the time of the sale.
totalPrice -= (totalPrice / totalShares) * operationShares
totalPrice += operationCommission
totalShares -= operationShares
commission += operationCommission
}
}

pi.Shares = totalShares
if pi.Shares > 0 {
pi.Commission = roundFloatTwoDecimalPlaces(commission)
pi.CostBasis = roundFloatTwoDecimalPlaces(totalPrice)
pi.AveragePrice = roundFloatTwoDecimalPlaces(pi.CostBasis / pi.Shares)

// FIXME
if pi.ItemType == "stocks" || pi.ItemType == "fiis" {
gain := (pi.Shares * pi.LastPrice) - pi.CostBasis
pi.Gain = roundFloatTwoDecimalPlaces(gain)
pi.OverallReturn = roundFloatTwoDecimalPlaces((gain * 100) / pi.CostBasis)
} else {
pi.Gain = 0
pi.OverallReturn = 0
}
}
}
Loading

0 comments on commit 862aa57

Please sign in to comment.