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 }