Files
2025-07-05 03:44:53 +02:00

314 lines
10 KiB
Go

package templates
import (
"fmt"
"portfolio-tracker/internal/model"
"portfolio-tracker/internal/util"
"time"
)
// calculateTotalInvested calculates the total amount invested (buy transactions)
func calculateTotalInvested(activities []model.Activity) float64 {
var total float64 = 0
for _, activity := range activities {
if activity.Type == model.Buy {
total += activity.Amount * activity.Price
}
}
return total
}
// calculateTotalInvestedInBaseCurrency calculates total invested converted to portfolio base currency using historical rates
func calculateTotalInvestedInBaseCurrency(activities []model.Activity, baseCurrency string) float64 {
var total float64 = 0
for _, activity := range activities {
if activity.Type == model.Buy {
activityTotal := activity.Amount * activity.Price
if activity.Currency == baseCurrency {
total += activityTotal
} else {
// Use historical exchange rate for the transaction date
convertedTotal, err := util.ConvertCurrencyHistorical(activityTotal, activity.Currency, baseCurrency, activity.Date)
if err != nil {
// If historical conversion fails, try current rate as fallback
fallbackTotal, fallbackErr := util.ConvertCurrency(activityTotal, activity.Currency, baseCurrency)
if fallbackErr != nil {
// If all conversions fail, add original amount (last resort)
total += activityTotal
} else {
total += fallbackTotal
}
} else {
total += convertedTotal
}
}
}
}
return total
}
// getLastBuyDate returns the date of the last buy transaction
func getLastBuyDate(activities []model.Activity) string {
var lastBuy time.Time
for _, activity := range activities {
if activity.Type == model.Buy && activity.Date.After(lastBuy) {
lastBuy = activity.Date
}
}
if lastBuy.IsZero() {
return "Keine Käufe"
}
return lastBuy.Format("02.01.2006")
}
// convertActivityPrice converts activity price to portfolio base currency using historical rate
func convertActivityPrice(activity model.Activity, portfolioBaseCurrency string) (float64, string, error) {
if activity.Currency == portfolioBaseCurrency {
return activity.Price, "", nil // No conversion needed
}
convertedPrice, err := util.ConvertCurrencyHistorical(activity.Price, activity.Currency, portfolioBaseCurrency, activity.Date)
if err != nil {
return 0, "", err
}
return convertedPrice, portfolioBaseCurrency, nil
}
// formatActivityPrice formats activity price with conversion if needed using historical rates
func formatActivityPrice(activity model.Activity, portfolioBaseCurrency string) string {
if activity.Currency == portfolioBaseCurrency {
return fmt.Sprintf("%.2f %s", activity.Price, activity.Currency)
}
convertedPrice, err := util.ConvertCurrencyHistorical(activity.Price, activity.Currency, portfolioBaseCurrency, activity.Date)
if err != nil {
return fmt.Sprintf("%.2f %s (conv. error)", activity.Price, activity.Currency)
}
return fmt.Sprintf("%.2f %s (~%.2f %s)", activity.Price, activity.Currency, convertedPrice, portfolioBaseCurrency)
}
// formatActivityTotal formats activity total with conversion if needed using historical rates
func formatActivityTotal(activity model.Activity, portfolioBaseCurrency string) string {
total := activity.Amount * activity.Price
if activity.Currency == portfolioBaseCurrency {
return fmt.Sprintf("%.2f %s", total, activity.Currency)
}
convertedTotal, err := util.ConvertCurrencyHistorical(total, activity.Currency, portfolioBaseCurrency, activity.Date)
if err != nil {
return fmt.Sprintf("%.2f %s (conv. error)", total, activity.Currency)
}
return fmt.Sprintf("%.2f %s (~%.2f %s)", total, activity.Currency, convertedTotal, portfolioBaseCurrency)
}
// getConvertedPrice returns the converted price using historical exchange rate or original if conversion fails
func getConvertedPrice(activity model.Activity, portfolioBaseCurrency string) string {
if activity.Currency == portfolioBaseCurrency {
return ""
}
// Use historical exchange rate for the transaction date
convertedPrice, isHistorical, err := util.GetExchangeRateWithFallback(activity.Currency, portfolioBaseCurrency, activity.Date)
if err != nil {
return "Conv. Error"
}
finalPrice := activity.Price * convertedPrice
// Add indicator if fallback (current) rate was used instead of historical
if !isHistorical {
return fmt.Sprintf("%.2f %s*", finalPrice, portfolioBaseCurrency)
}
return fmt.Sprintf("%.2f %s", finalPrice, portfolioBaseCurrency)
}
// getConvertedTotal returns the converted total using historical exchange rate or original if conversion fails
func getConvertedTotal(activity model.Activity, portfolioBaseCurrency string) string {
if activity.Currency == portfolioBaseCurrency {
return ""
}
total := activity.Amount * activity.Price
// Use historical exchange rate for the transaction date
convertedTotal, err := util.ConvertCurrencyHistorical(total, activity.Currency, portfolioBaseCurrency, activity.Date)
if err != nil {
return "Conv. Error"
}
return fmt.Sprintf("%.2f %s", convertedTotal, portfolioBaseCurrency)
}
// getConvertedTotalWithFallbackInfo returns the converted total with information about rate source
func getConvertedTotalWithFallbackInfo(activity model.Activity, portfolioBaseCurrency string) string {
if activity.Currency == portfolioBaseCurrency {
return ""
}
total := activity.Amount * activity.Price
// Use historical exchange rate with fallback information
rate, isHistorical, err := util.GetExchangeRateWithFallback(activity.Currency, portfolioBaseCurrency, activity.Date)
if err != nil {
return "Conv. Error"
}
convertedTotal := total * rate
// Add indicator if fallback (current) rate was used instead of historical
if !isHistorical {
return fmt.Sprintf("%.2f %s*", convertedTotal, portfolioBaseCurrency)
}
return fmt.Sprintf("%.2f %s", convertedTotal, portfolioBaseCurrency)
}
// formatTotalInvestedWithConversion formats total invested with currency info and conversion indicators
func formatTotalInvestedWithConversion(activities []model.Activity, baseCurrency string) string {
convertedTotal := calculateTotalInvestedInBaseCurrency(activities, baseCurrency)
// Check if any activities have different currencies
hasDifferentCurrencies := false
hasHistoricalConversions := false
for _, activity := range activities {
if activity.Type == model.Buy && activity.Currency != baseCurrency {
hasDifferentCurrencies = true
// Check if we can get historical rate for this transaction
_, isHistorical, err := util.GetExchangeRateWithFallback(activity.Currency, baseCurrency, activity.Date)
if err == nil && isHistorical {
hasHistoricalConversions = true
}
break
}
}
if !hasDifferentCurrencies {
return fmt.Sprintf("%.2f %s", convertedTotal, baseCurrency)
}
if hasHistoricalConversions {
return fmt.Sprintf("%.2f %s (historical rates)", convertedTotal, baseCurrency)
}
return fmt.Sprintf("%.2f %s (converted)", convertedTotal, baseCurrency)
}
// calculateTotalInvestedByDate calculates total invested up to a specific date
func calculateTotalInvestedByDate(activities []model.Activity, baseCurrency string, endDate time.Time) float64 {
var total float64 = 0
for _, activity := range activities {
if activity.Type == model.Buy && activity.Date.Before(endDate.AddDate(0, 0, 1)) { // Include transactions on endDate
activityTotal := activity.Amount * activity.Price
if activity.Currency == baseCurrency {
total += activityTotal
} else {
// Use historical exchange rate for the transaction date
convertedTotal, err := util.ConvertCurrencyHistorical(activityTotal, activity.Currency, baseCurrency, activity.Date)
if err != nil {
// If historical conversion fails, try current rate as fallback
fallbackTotal, fallbackErr := util.ConvertCurrency(activityTotal, activity.Currency, baseCurrency)
if fallbackErr != nil {
// If all conversions fail, add original amount (last resort)
total += activityTotal
} else {
total += fallbackTotal
}
} else {
total += convertedTotal
}
}
}
}
return total
}
// getConversionInfo returns information about currency conversions used in the portfolio
func getConversionInfo(activities []model.Activity, baseCurrency string) map[string]interface{} {
info := map[string]interface{}{
"hasDifferentCurrencies": false,
"historicalConversions": 0,
"fallbackConversions": 0,
"errorConversions": 0,
"currencies": make(map[string]int),
}
currencies := make(map[string]int)
historicalCount := 0
fallbackCount := 0
errorCount := 0
for _, activity := range activities {
if activity.Type == model.Buy {
currencies[activity.Currency]++
if activity.Currency != baseCurrency {
info["hasDifferentCurrencies"] = true
// Check conversion status
_, isHistorical, err := util.GetExchangeRateWithFallback(activity.Currency, baseCurrency, activity.Date)
if err != nil {
errorCount++
} else if isHistorical {
historicalCount++
} else {
fallbackCount++
}
}
}
}
info["historicalConversions"] = historicalCount
info["fallbackConversions"] = fallbackCount
info["errorConversions"] = errorCount
info["currencies"] = currencies
return info
}
// formatCurrencyWithConversionNote formats currency amount with appropriate conversion notes
func formatCurrencyWithConversionNote(amount float64, fromCurrency, toCurrency string, transactionDate time.Time) string {
if fromCurrency == toCurrency {
return util.FormatCurrencyWithSymbol(amount, toCurrency)
}
rate, isHistorical, err := util.GetExchangeRateWithFallback(fromCurrency, toCurrency, transactionDate)
if err != nil {
return fmt.Sprintf("%.2f %s (conv. error)", amount, fromCurrency)
}
convertedAmount := amount * rate
if isHistorical {
return fmt.Sprintf("%.2f %s (historical rate)", convertedAmount, toCurrency)
}
return fmt.Sprintf("%.2f %s (current rate)", convertedAmount, toCurrency)
}
// getActivityConversionStatus returns the conversion status for a specific activity
func getActivityConversionStatus(activity model.Activity, portfolioBaseCurrency string) string {
if activity.Currency == portfolioBaseCurrency {
return "same_currency"
}
_, isHistorical, err := util.GetExchangeRateWithFallback(activity.Currency, portfolioBaseCurrency, activity.Date)
if err != nil {
return "error"
}
if isHistorical {
return "historical"
}
return "fallback"
}