Files
Portfolio-Engine/internal/model/portifolio.go
2026-04-06 07:36:45 +02:00

157 lines
3.5 KiB
Go

package model
import (
"errors"
"fmt"
"time"
)
type Dividend struct {
Symbol string
CurrencyCode string
Product TradeProduct
Value float64
PaymentDate time.Time
TaxAmount float64
TaxRate float64
NetValue float64
}
type CloseEntry struct {
Shares int
InPrice float64
OutPrice float64
GainPrice float64
CloseTime time.Time
}
type ClosedPosition struct {
Symbol string
CurrencyCode string
Product TradeProduct
Closes []CloseEntry // each close carries its own prices + share count
OpenTime time.Time
RealizedGain float64
TaxAmount float64
HoldingDays int
}
type Position struct {
CompanyID int
Symbol string
CurrencyCode string
CurrencyID int
Weight float64
CostBasis float64
Shares int
}
type TradeProduct int
const (
StockTrade TradeProduct = iota // 0
OptionCallTrade // 1
OptionPutTrade // 2
CurrencyTrade // 3
BondTrade
)
type TradeType int
const (
BuyType TradeType = iota // 0
SellType // 1
DividendType // 2
)
type AddTradeRequest struct {
Symbol string `json:"symbol"`
Shares int `json:"shares"`
Product int `json:"product"`
Type int `json:"type"` // was bool, now int
Price float64 `json:"price"`
CurrencyCode string `json:"currency_code"`
Date time.Time `json:"date"`
// Dividend-specific fields (only populated when Type == 2)
TaxAmount float64 `json:"tax_amount,omitempty"`
TaxRate float64 `json:"tax_rate,omitempty"`
NetValue float64 `json:"net_value,omitempty"`
PaymentDate time.Time `json:"payment_date,omitempty"`
}
type Trade struct {
Symbol string
CurrencyCode string
Shares int
Product TradeProduct
Type TradeType
Price float64
Date time.Time
}
func (r *AddTradeRequest) Validate() error {
if r.Symbol == "" {
return errors.New("empty Symbol string")
}
if r.Shares <= 0 {
return errors.New("shares must be a positive integer")
}
if r.Product < 0 || r.Product > 3 {
return errors.New("product must be between 0 and 3")
}
if r.Price <= 0 {
return errors.New("price must be a positive number")
}
if r.CurrencyCode == "" {
return errors.New("currency is required")
}
if r.Date.IsZero() {
return errors.New("date is required")
}
if r.Date.After(time.Now()) {
return errors.New("date cannot be in the future")
}
return nil
}
func (r AddTradeRequest) ToDividend() (Dividend, error) {
if TradeType(r.Type) != DividendType {
return Dividend{}, fmt.Errorf("trade type is not a dividend")
}
return Dividend{
Symbol: r.Symbol,
CurrencyCode: r.CurrencyCode,
Product: TradeProduct(r.Product),
Value: r.Price, // gross value
PaymentDate: r.PaymentDate,
TaxAmount: r.TaxAmount,
TaxRate: r.TaxRate,
NetValue: r.NetValue,
}, nil
}
func (r AddTradeRequest) ToTrade() (Trade, error) {
t := TradeType(r.Type)
if t != BuyType && t != SellType {
return Trade{}, fmt.Errorf("trade type is not buy or sell")
}
return Trade{
Symbol: r.Symbol,
CurrencyCode: r.CurrencyCode,
Shares: r.Shares,
Product: TradeProduct(r.Product),
Type: t,
Price: r.Price,
Date: r.Date,
}, nil
}
// for now trades and none stock position will not be supported.
type Portifolio struct {
Positions []Position
Trades []Trade
closed []ClosedPosition
Dividends []Dividend
}