314 lines
10 KiB
Go
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"
|
|
}
|