Skip to content

Commit

Permalink
Calculating profit/loss on backend (#190)
Browse files Browse the repository at this point in the history
  • Loading branch information
oxisto authored Nov 8, 2023
1 parent c936101 commit 48af140
Show file tree
Hide file tree
Showing 11 changed files with 542 additions and 467 deletions.
4 changes: 2 additions & 2 deletions buf.gen.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ managed:
go_package_prefix:
default: github.com/oxisto/money-gopher/gen
plugins:
- plugin: buf.build/bufbuild/connect-go
- plugin: buf.build/connectrpc/go
out: gen
opt:
- paths=source_relative
- plugin: buf.build/protocolbuffers/go
out: gen
opt:
- paths=source_relative
- plugin: buf.build/bufbuild/connect-es
- plugin: buf.build/connectrpc/es
out: ui/src/lib/gen
opt: target=ts
- plugin: buf.build/bufbuild/es
Expand Down
540 changes: 294 additions & 246 deletions gen/mgo.pb.go

Large diffs are not rendered by default.

324 changes: 162 additions & 162 deletions gen/portfoliov1connect/mgo.connect.go

Large diffs are not rendered by default.

12 changes: 2 additions & 10 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,25 +1,17 @@
connectrpc.com/connect v1.10.1 h1:vnoz6AZnSIu8/SGU6458VtrFZFhkEZTqn6xgAzMS5og=
connectrpc.com/connect v1.10.1/go.mod h1:ARfc9N8ifAehhTXIVUG5effZoJ5DG9ATm6/x6kVhvP8=
connectrpc.com/connect v1.11.1 h1:dqRwblixqkVh+OFBOOL1yIf1jS/yP0MSJLijRj29bFg=
connectrpc.com/connect v1.11.1/go.mod h1:3AGaO6RRGMx5IKFfqbe3hvK1NqLosFNP2BxDYTPmNPo=
connectrpc.com/connect v1.12.0 h1:HwKdOY0lGhhoHdsza+hW55aqHEC64pYpObRNoAgn70g=
connectrpc.com/connect v1.12.0/go.mod h1:3AGaO6RRGMx5IKFfqbe3hvK1NqLosFNP2BxDYTPmNPo=
github.com/bufbuild/connect-go v1.10.0 h1:QAJ3G9A1OYQW2Jbk3DeoJbkCxuKArrvZgDt47mjdTbg=
github.com/bufbuild/connect-go v1.10.0/go.mod h1:CAIePUgkDR5pAFaylSMtNK45ANQjp9JvpluG20rhpV8=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mattn/go-sqlite3 v1.14.18 h1:JL0eqdCOq6DJVNPSvArO/bIV9/P7fbGrV00LZHc+5aI=
github.com/mattn/go-sqlite3 v1.14.18/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/oxisto/assert v0.0.6 h1:Z/wRt0qndURRof+eOGr7GbcJ6BHZT2nyZd9diuZHS8o=
github.com/oxisto/assert v0.0.6/go.mod h1:07ANKfyBm6j+pZk1qArFueno6fCoEGKvPbPeJSQkH3s=
golang.org/x/net v0.16.0 h1:7eBu7KsSvFDtSXUIDbh3aqlK4DPsZ1rByC8PFfBThos=
golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
Expand Down
15 changes: 15 additions & 0 deletions mgo.proto
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,14 @@ message PortfolioSnapshot {

// TotalMarketValue contains the total market value of all positions
float total_market_value = 11;

// TotalProfitOrLoss contains the total absolute amount of profit or loss in
// this snapshot.
float total_profit_or_loss = 20;

// TotalGains contains the total relative amount of profit or loss in this
// snapshot.
float total_gains = 21;
}

message PortfolioPosition {
Expand All @@ -104,6 +112,13 @@ message PortfolioPosition {
// TotalFees is the total amount of fees accumulating in this position through
// various transactions.
float total_fees = 15;

// ProfitOrLoss contains the absolute amount of profit or loss in this
// position.
float profit_or_loss = 20;

// Gains contains the relative amount of profit or loss in this position.
float gains = 21;
}

enum PortfolioEventType {
Expand Down
17 changes: 14 additions & 3 deletions service/portfolio/snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ func (svc *service) GetPortfolioSnapshot(ctx context.Context, req *connect.Reque
continue
}

snap.Positions[name] = &portfoliov1.PortfolioPosition{
pos := &portfoliov1.PortfolioPosition{
Security: secmap[name],
Amount: c.Amount,
PurchaseValue: c.NetValue(),
Expand All @@ -101,11 +101,22 @@ func (svc *service) GetPortfolioSnapshot(ctx context.Context, req *connect.Reque
MarketPrice: marketPrice(secmap, name, c.NetPrice()),
}

// Calculate loss and gains
pos.ProfitOrLoss = pos.MarketValue - pos.PurchaseValue
pos.Gains = (pos.MarketValue - pos.PurchaseValue) / pos.PurchaseValue

// Add to total value(s)
snap.TotalPurchaseValue += snap.Positions[name].PurchaseValue
snap.TotalMarketValue += snap.Positions[name].MarketValue
snap.TotalPurchaseValue += pos.PurchaseValue
snap.TotalMarketValue += pos.MarketValue
snap.TotalProfitOrLoss += pos.ProfitOrLoss

// Store position in map
snap.Positions[name] = pos
}

// Calculate total gains
snap.TotalGains = (snap.TotalMarketValue - snap.TotalPurchaseValue) / snap.TotalPurchaseValue

return connect.NewResponse(snap), nil
}

Expand Down
17 changes: 6 additions & 11 deletions ui/src/lib/components/Performance.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,21 @@
export let snapshot: PortfolioSnapshot;
export let icon: boolean = true;
$: perf =
((snapshot.totalMarketValue - snapshot.totalPurchaseValue) / snapshot.totalPurchaseValue) * 100;
$: perfAbs = snapshot.totalMarketValue - snapshot.totalPurchaseValue;
</script>

<div
class="{perf < 0 ? 'text-red-400' : 'text-green-400'}
class="{snapshot.totalGains < 0 ? 'text-red-400' : 'text-green-400'}
flex items-center"
>
{#if icon}
<Icon
src={perf > 0 ? ArrowTrendingUp : ArrowTrendingDown}
class="{perf < 0 ? 'text-red-400' : 'text-green-400'}
src={snapshot.totalGains > 0 ? ArrowTrendingUp : ArrowTrendingDown}
class="{snapshot.totalGains < 0 ? 'text-red-400' : 'text-green-400'}
mr-1.5 h-5 w-5 flex-shrink-0"
aria-hidden="true"
/>
{/if}
{Intl.NumberFormat(navigator.language, { maximumFractionDigits: 2 }).format(perf)} % ({currency(
perfAbs,
'EUR'
)})
{Intl.NumberFormat(navigator.language, { maximumFractionDigits: 2 }).format(
snapshot.totalGains * 100
)} % ({currency(snapshot.totalProfitOrLoss, 'EUR')})
</div>
18 changes: 10 additions & 8 deletions ui/src/lib/components/PortfolioPositionRow.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
export let position: PortfolioPosition;
$: perf = ((position.marketPrice - position.purchasePrice) / position.purchasePrice) * 100;
function shorten(text: string): string {
let max = 30;
Expand Down Expand Up @@ -51,24 +49,28 @@
</div>
</td>
<td
class="{perf < 0
? 'text-red-500'
: perf <= 1
class="{Math.abs(position.gains) <= 0.01
? 'text-gray-500'
: position.gains < 0
? 'text-red-500'
: 'text-green-500'} whitespace-nowrap px-3 py-2 text-right text-sm"
>
<div>
{Intl.NumberFormat(navigator.language, {
maximumFractionDigits: 2
}).format(perf)} %
}).format(position.gains * 100)} %
<Icon
src={perf < 0 ? ArrowDown : perf < 1 ? ArrowRight : ArrowUp}
src={Math.abs(position.gains) < 0.01
? ArrowRight
: position.gains < 0
? ArrowDown
: ArrowUp}
class="float-right mt-0.5 h-4 w-4"
aria-hidden="true"
/>
</div>
<div class="pr-4">
{currency(position.marketValue - position.purchaseValue, 'EUR')}
{currency(position.profitOrLoss, 'EUR')}
</div>
</td>
</tr>
Expand Down
2 changes: 1 addition & 1 deletion ui/src/lib/gen/mgo_connect.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// @generated by protoc-gen-connect-es v0.12.0 with parameter "target=ts"
// @generated by protoc-gen-connect-es v1.1.3 with parameter "target=ts"
// @generated from file mgo.proto (package mgo.portfolio.v1, syntax proto3)
/* eslint-disable */
// @ts-nocheck
Expand Down
37 changes: 36 additions & 1 deletion ui/src/lib/gen/mgo_pb.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// @generated by protoc-gen-es v1.3.0 with parameter "target=ts"
// @generated by protoc-gen-es v1.4.2 with parameter "target=ts"
// @generated from file mgo.proto (package mgo.portfolio.v1, syntax proto3)
/* eslint-disable */
// @ts-nocheck
Expand Down Expand Up @@ -650,6 +650,22 @@ export class PortfolioSnapshot extends Message<PortfolioSnapshot> {
*/
totalMarketValue = 0;

/**
* TotalProfitOrLoss contains the total absolute amount of profit or loss in
* this snapshot.
*
* @generated from field: float total_profit_or_loss = 20;
*/
totalProfitOrLoss = 0;

/**
* TotalGains contains the total relative amount of profit or loss in this
* snapshot.
*
* @generated from field: float total_gains = 21;
*/
totalGains = 0;

constructor(data?: PartialMessage<PortfolioSnapshot>) {
super();
proto3.util.initPartial(data, this);
Expand All @@ -663,6 +679,8 @@ export class PortfolioSnapshot extends Message<PortfolioSnapshot> {
{ no: 3, name: "first_transaction_time", kind: "message", T: Timestamp, opt: true },
{ no: 10, name: "total_purchase_value", kind: "scalar", T: 2 /* ScalarType.FLOAT */ },
{ no: 11, name: "total_market_value", kind: "scalar", T: 2 /* ScalarType.FLOAT */ },
{ no: 20, name: "total_profit_or_loss", kind: "scalar", T: 2 /* ScalarType.FLOAT */ },
{ no: 21, name: "total_gains", kind: "scalar", T: 2 /* ScalarType.FLOAT */ },
]);

static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): PortfolioSnapshot {
Expand Down Expand Up @@ -736,6 +754,21 @@ export class PortfolioPosition extends Message<PortfolioPosition> {
*/
totalFees = 0;

/**
* ProfitOrLoss contains the absolute amount of profit or loss in this
* position.
*
* @generated from field: float profit_or_loss = 20;
*/
profitOrLoss = 0;

/**
* Gains contains the relative amount of profit or loss in this position.
*
* @generated from field: float gains = 21;
*/
gains = 0;

constructor(data?: PartialMessage<PortfolioPosition>) {
super();
proto3.util.initPartial(data, this);
Expand All @@ -751,6 +784,8 @@ export class PortfolioPosition extends Message<PortfolioPosition> {
{ no: 10, name: "market_value", kind: "scalar", T: 2 /* ScalarType.FLOAT */ },
{ no: 11, name: "market_price", kind: "scalar", T: 2 /* ScalarType.FLOAT */ },
{ no: 15, name: "total_fees", kind: "scalar", T: 2 /* ScalarType.FLOAT */ },
{ no: 20, name: "profit_or_loss", kind: "scalar", T: 2 /* ScalarType.FLOAT */ },
{ no: 21, name: "gains", kind: "scalar", T: 2 /* ScalarType.FLOAT */ },
]);

static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): PortfolioPosition {
Expand Down
23 changes: 0 additions & 23 deletions ui/src/routes/portfolios/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,30 +1,7 @@
<script setup lang="ts">
import type { Portfolio, PortfolioSnapshot } from '$lib/gen/mgo_pb';
import PortfolioCard from '$lib/components/PortfolioCard.svelte';
import type { PageData } from './$types';
/*import Button from '@/components/Button.vue';
import PortfolioCard from '@/components/portfolio/PortfolioCard.vue';
import { PortfolioServiceClientKey } from '@/symbols'
import { inject } from 'vue';
let client = inject(PortfolioServiceClientKey)
if (client == undefined) {
throw "could not instantiate portfolio client"
}
let portfolios = (await client.listPortfolios({}, {})).portfolios;
// TODO(oxisto): This is a bit inefficient, since it waits until all are
// finished but it works
let snapshots = await Promise.all(portfolios.map(async (p) => {
if (client == undefined) {
throw "could not instantiate portfolio client"
}
return await client.getPortfolioSnapshot({ portfolioName: p.name })
}))*/
let snapshots: PortfolioSnapshot[] = [];
export let data: PageData;
</script>

Expand Down

0 comments on commit 48af140

Please sign in to comment.