157 lines
3.5 KiB
Go
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
|
|
}
|