Files
any2anexoj/main.go

100 lines
2.1 KiB
Go

package main
import (
"container/list"
"errors"
"fmt"
"io"
"log/slog"
"math/big"
"os"
"git.naterciomoniz.net/applications/broker2anexoj/internal"
"git.naterciomoniz.net/applications/broker2anexoj/internal/trading212"
)
func main() {
err := run()
if err != nil {
slog.Error("fatal error", slog.Any("err", err))
}
}
func run() error {
f, err := os.Open("test.csv")
if err != nil {
return fmt.Errorf("open statement: %w", err)
}
r := trading212.NewRecordReader(f)
assets := make(map[string]*list.List)
for {
record, err := r.ReadRecord()
if err != nil {
if errors.Is(err, io.EOF) {
break
}
return fmt.Errorf("read statement record: %w", err)
}
switch record.Side() {
case internal.SideBuy:
lst, ok := assets[record.Symbol()]
if !ok {
lst = list.New()
assets[record.Symbol()] = lst
}
lst.PushBack(record)
case internal.SideSell:
lst, ok := assets[record.Symbol()]
if !ok {
return ErrSellWithoutBuy
}
unmatchedQty := new(big.Float).Copy(record.Quantity())
zero := new(big.Float)
for unmatchedQty.Cmp(zero) > 0 {
front := lst.Front()
if front == nil {
return ErrSellWithoutBuy
}
next, ok := front.Value.(internal.Record)
if !ok {
return fmt.Errorf("unexpected record type: %T", front)
}
var matchedQty *big.Float
if next.Quantity().Cmp(unmatchedQty) > 0 {
matchedQty = unmatchedQty
next.Quantity().Sub(next.Quantity(), unmatchedQty)
} else {
matchedQty = next.Quantity()
lst.Remove(front)
}
unmatchedQty.Sub(unmatchedQty, matchedQty)
sellValue := new(big.Float).Mul(matchedQty, record.Price())
buyValue := new(big.Float).Mul(matchedQty, next.Price())
realisedPnL := new(big.Float).Sub(sellValue, buyValue)
slog.Info("Realised PnL",
slog.Any("Symbol", record.Symbol()),
slog.Any("PnL", realisedPnL))
}
default:
return fmt.Errorf("unknown side: %s", record.Side())
}
}
slog.Info("Finish processing statement", slog.Any("assets_count", len(assets)))
return nil
}
var ErrSellWithoutBuy = fmt.Errorf("found sell without bought volume")