Skip to content

Commit

Permalink
Converting++
Browse files Browse the repository at this point in the history
  • Loading branch information
oxisto committed Dec 29, 2024
1 parent 1caf16e commit 0fc6c8d
Show file tree
Hide file tree
Showing 10 changed files with 942 additions and 110 deletions.
89 changes: 56 additions & 33 deletions finance/snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,77 +10,85 @@ import (
"connectrpc.com/connect"
moneygopher "github.com/oxisto/money-gopher"
portfoliov1 "github.com/oxisto/money-gopher/gen"
"github.com/oxisto/money-gopher/graph"
"github.com/oxisto/money-gopher/models"
"github.com/oxisto/money-gopher/persistence"
"google.golang.org/protobuf/types/known/timestamppb"
)

func BuildSnapshot(time time.Time, db *persistence.DB) (*graph.PortfolioSnapshot, error) {
// SnapshotDataProvider is an interface that provides the necessary data for
// building a snapshot. It includes methods for retrieving portfolio events and
// securities by their IDs.
type SnapshotDataProvider interface {
ListPortfolioEventsByPortfolioID(ctx context.Context, portfolioID string) ([]*persistence.PortfolioEvent, error)
ListSecuritiesByIDs(ctx context.Context, ids []string) ([]*persistence.Security, error)
}

// BuildSnapshot creates a snapshot of the portfolio at a given time. It
// calculates the performance and market value of the current positions and the
// total value of the portfolio.
//
// The snapshot is built by retrieving all events and security information from
// a [SnapshotDataProvider]. The snapshot is built by iterating over the events
// and calculating the positions at the specified timestamp.
func BuildSnapshot(
ctx context.Context,
timestamp *time.Time,
portfolioID string,
provider SnapshotDataProvider,
) (snap *models.PortfolioSnapshot, err error) {
var (
snap *portfoliov1.PortfolioSnapshot
events []*persistence.PortfolioEvent
p portfoliov1.Portfolio
m map[string][]*portfoliov1.PortfolioEvent
names []string
secres *connect.Response[portfoliov1.ListSecuritiesResponse]
secmap map[string]*portfoliov1.Security
ids []string
secs []*persistence.Security
secmap map[string]*persistence.Security
)

// Retrieve transactions
p.Events, err = svc.events.List(req.Msg.PortfolioId)
// Retrieve events
events, err = provider.ListPortfolioEventsByPortfolioID(ctx, portfolioID)
if err != nil {
return nil, connect.NewError(connect.CodeInternal, err)
}

// If no time is specified, we assume it to be now
if req.Msg.Time == nil {
req.Msg.Time = timestamppb.Now()
}

// Set up the snapshot
snap = &portfoliov1.PortfolioSnapshot{
Time: req.Msg.Time,
Positions: make(map[string]*portfoliov1.PortfolioPosition),
TotalPurchaseValue: portfoliov1.Zero(),
snap = &models.PortfolioSnapshot{
Time: timestamp.Format(time.RFC3339),
Positions: make([]*models.PortfolioPosition, 0),
/*TotalPurchaseValue: portfoliov1.Zero(),
TotalMarketValue: portfoliov1.Zero(),
TotalProfitOrLoss: portfoliov1.Zero(),
Cash: portfoliov1.Zero(),
Cash: portfoliov1.Zero(),*/
}

// Record the first transaction time
if len(p.Events) > 0 {
snap.FirstTransactionTime = p.Events[0].Time
snap.FirstTransactionTime = events[0].Time.Format(time.RFC3339)
}

// Retrieve the event map; a map of events indexed by their security ID
m = p.EventMap()
names = slices.Collect(maps.Keys(m))
ids = slices.Collect(maps.Keys(m))

// Retrieve market value of filtered securities
secres, err = svc.securities.ListSecurities(
context.Background(),
forwardAuth(connect.NewRequest(&portfoliov1.ListSecuritiesRequest{
Filter: &portfoliov1.ListSecuritiesRequest_Filter{
SecurityIds: names,
},
}), req),
)
secs, err = provider.ListSecuritiesByIDs(context.Background(), ids)

if err != nil {
return nil, connect.NewError(connect.CodeInternal,
fmt.Errorf("internal error while calling ListSecurities on securities service: %w", err),
)
}

// Make a map out of the securities list so we can access it easier
secmap = moneygopher.Map(secres.Msg.Securities, func(s *portfoliov1.Security) string {
return s.Id
secmap = moneygopher.Map(secs, func(s *persistence.Security) string {
return s.ID
})

// We need to look at the portfolio events up to the time of the snapshot
// and calculate the current positions.
for name, txs := range m {
txs = portfoliov1.EventsBefore(txs, snap.Time.AsTime())
txs = eventsBefore(txs, timestamp)

Check failure on line 89 in finance/snapshot.go

View workflow job for this annotation

GitHub Actions / build

cannot use eventsBefore(txs, timestamp) (value of type []*persistence.PortfolioEvent) as []*portfoliov1.PortfolioEvent value in assignment

Check failure on line 89 in finance/snapshot.go

View workflow job for this annotation

GitHub Actions / build

cannot use txs (variable of type []*portfoliov1.PortfolioEvent) as []*persistence.PortfolioEvent value in argument to eventsBefore

Check failure on line 89 in finance/snapshot.go

View workflow job for this annotation

GitHub Actions / build

cannot use timestamp (variable of type *time.Time) as time.Time value in argument to eventsBefore

c := finance.NewCalculation(txs)
c := NewCalculation(txs)

if name == "cash" {
// Add deposited/withdrawn cash directly
Expand Down Expand Up @@ -125,3 +133,18 @@ func BuildSnapshot(time time.Time, db *persistence.DB) (*graph.PortfolioSnapshot

return connect.NewResponse(snap), nil
}

// TODO: move to SQL query
func eventsBefore(events []*persistence.PortfolioEvent, t time.Time) (out []*persistence.PortfolioEvent) {
out = make([]*persistence.PortfolioEvent, 0, len(events))

for _, event := range events {
if event.Time.After(t) {
continue
}

out = append(out, event)
}

return
}
4 changes: 2 additions & 2 deletions gqlgen.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ exec:

# Where should any generated models go?
model:
filename: graph/models_gen.go
package: graph
filename: models/models_gen.go
package: models

# Optional: Pass in a path to a new gotpl template to use for generating the models
# model_template: [your/path/model.gotpl]
Expand Down
Loading

0 comments on commit 0fc6c8d

Please sign in to comment.