Files
2025-07-05 05:10:42 +02:00

118 lines
4.3 KiB
Go

package model
import (
"fmt"
"time"
)
// Position represents a calculated position for a specific stock in a portfolio
type Position struct {
Stock string `json:"stock"` // Stock symbol/ISIN
Shares float64 `json:"shares"` // Total shares held (can be negative if oversold)
AverageCostPrice float64 `json:"average_cost_price"` // Average cost per share in portfolio base currency
TotalCostBasis float64 `json:"total_cost_basis"` // Total amount invested in portfolio base currency
CurrentPrice float64 `json:"current_price"` // Current market price per share in portfolio base currency
CurrentValue float64 `json:"current_value"` // Current total value (shares * current_price)
UnrealizedPL float64 `json:"unrealized_pl"` // Unrealized profit/loss (current_value - total_cost_basis)
UnrealizedPLPct float64 `json:"unrealized_pl_pct"` // Unrealized profit/loss percentage
Currency string `json:"currency"` // Stock's native currency
BaseCurrency string `json:"base_currency"` // Portfolio's base currency
LastUpdated time.Time `json:"last_updated"` // When position was last calculated
// Dividend information
TotalDividends float64 `json:"total_dividends"` // Total dividends received in base currency
// Transaction summary
TotalBought float64 `json:"total_bought"` // Total shares bought
TotalSold float64 `json:"total_sold"` // Total shares sold
RealizedPL float64 `json:"realized_pl"` // Realized profit/loss from sales
}
// PositionSummary represents aggregated portfolio position data
type PositionSummary struct {
TotalValue float64 `json:"total_value"` // Total portfolio value
TotalCostBasis float64 `json:"total_cost_basis"` // Total amount invested
TotalUnrealizedPL float64 `json:"total_unrealized_pl"` // Total unrealized profit/loss
TotalRealizedPL float64 `json:"total_realized_pl"` // Total realized profit/loss
TotalDividends float64 `json:"total_dividends"` // Total dividends received
TotalReturn float64 `json:"total_return"` // Total return (realized + unrealized + dividends)
TotalReturnPct float64 `json:"total_return_pct"` // Total return percentage
Currency string `json:"currency"` // Portfolio base currency
LastUpdated time.Time `json:"last_updated"` // When summary was calculated
}
// IsOpen returns true if the position has shares (positive or negative)
func (p *Position) IsOpen() bool {
return p.Shares != 0
}
// IsLong returns true if the position has positive shares
func (p *Position) IsLong() bool {
return p.Shares > 0
}
// IsShort returns true if the position has negative shares
func (p *Position) IsShort() bool {
return p.Shares < 0
}
// GetDisplayValue returns the absolute value for display purposes
func (p *Position) GetDisplayValue() float64 {
if p.CurrentValue < 0 {
return -p.CurrentValue
}
return p.CurrentValue
}
// GetUnrealizedPLColor returns CSS class for profit/loss color
func (p *Position) GetUnrealizedPLColor() string {
if p.UnrealizedPL > 0 {
return "text-green"
} else if p.UnrealizedPL < 0 {
return "text-red"
}
return "text-muted"
}
// GetUnrealizedPLIcon returns icon for profit/loss
func (p *Position) GetUnrealizedPLIcon() string {
if p.UnrealizedPL > 0 {
return "trending-up"
} else if p.UnrealizedPL < 0 {
return "trending-down"
}
return "minus"
}
// FormatCurrency formats a value with currency symbol
func (p *Position) FormatCurrency(value float64) string {
switch p.BaseCurrency {
case "EUR":
return fmt.Sprintf("€%.2f", value)
case "USD":
return fmt.Sprintf("$%.2f", value)
case "GBP":
return fmt.Sprintf("£%.2f", value)
case "CHF":
return fmt.Sprintf("%.2f CHF", value)
default:
return fmt.Sprintf("%.2f %s", value, p.BaseCurrency)
}
}
// FormatShares formats share count with appropriate decimal places
func (p *Position) FormatShares() string {
if p.Shares == float64(int(p.Shares)) {
return fmt.Sprintf("%.0f", p.Shares)
}
return fmt.Sprintf("%.4f", p.Shares)
}
// FormatPercentage formats percentage with sign and color
func (p *Position) FormatPercentage() string {
if p.UnrealizedPLPct > 0 {
return fmt.Sprintf("+%.2f%%", p.UnrealizedPLPct)
}
return fmt.Sprintf("%.2f%%", p.UnrealizedPLPct)
}