app is now using historical exchange rates for transactions
This commit is contained in:
@@ -18,7 +18,7 @@ func calculateTotalInvested(activities []model.Activity) float64 {
|
||||
return total
|
||||
}
|
||||
|
||||
// calculateTotalInvestedInBaseCurrency calculates total invested converted to portfolio base currency
|
||||
// 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 {
|
||||
@@ -28,10 +28,17 @@ func calculateTotalInvestedInBaseCurrency(activities []model.Activity, baseCurre
|
||||
if activity.Currency == baseCurrency {
|
||||
total += activityTotal
|
||||
} else {
|
||||
convertedTotal, err := util.ConvertCurrency(activityTotal, activity.Currency, baseCurrency)
|
||||
// Use historical exchange rate for the transaction date
|
||||
convertedTotal, err := util.ConvertCurrencyHistorical(activityTotal, activity.Currency, baseCurrency, activity.Date)
|
||||
if err != nil {
|
||||
// If conversion fails, add original amount (fallback)
|
||||
total += activityTotal
|
||||
// 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
|
||||
}
|
||||
@@ -55,13 +62,13 @@ func getLastBuyDate(activities []model.Activity) string {
|
||||
return lastBuy.Format("02.01.2006")
|
||||
}
|
||||
|
||||
// convertActivityPrice converts activity price to portfolio base currency if different
|
||||
// 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.ConvertCurrency(activity.Price, activity.Currency, portfolioBaseCurrency)
|
||||
convertedPrice, err := util.ConvertCurrencyHistorical(activity.Price, activity.Currency, portfolioBaseCurrency, activity.Date)
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
@@ -69,13 +76,13 @@ func convertActivityPrice(activity model.Activity, portfolioBaseCurrency string)
|
||||
return convertedPrice, portfolioBaseCurrency, nil
|
||||
}
|
||||
|
||||
// formatActivityPrice formats activity price with conversion if needed
|
||||
// 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.ConvertCurrency(activity.Price, activity.Currency, portfolioBaseCurrency)
|
||||
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)
|
||||
}
|
||||
@@ -83,7 +90,7 @@ func formatActivityPrice(activity model.Activity, portfolioBaseCurrency string)
|
||||
return fmt.Sprintf("%.2f %s (~%.2f %s)", activity.Price, activity.Currency, convertedPrice, portfolioBaseCurrency)
|
||||
}
|
||||
|
||||
// formatActivityTotal formats activity total with conversion if needed
|
||||
// formatActivityTotal formats activity total with conversion if needed using historical rates
|
||||
func formatActivityTotal(activity model.Activity, portfolioBaseCurrency string) string {
|
||||
total := activity.Amount * activity.Price
|
||||
|
||||
@@ -91,7 +98,7 @@ func formatActivityTotal(activity model.Activity, portfolioBaseCurrency string)
|
||||
return fmt.Sprintf("%.2f %s", total, activity.Currency)
|
||||
}
|
||||
|
||||
convertedTotal, err := util.ConvertCurrency(total, activity.Currency, portfolioBaseCurrency)
|
||||
convertedTotal, err := util.ConvertCurrencyHistorical(total, activity.Currency, portfolioBaseCurrency, activity.Date)
|
||||
if err != nil {
|
||||
return fmt.Sprintf("%.2f %s (conv. error)", total, activity.Currency)
|
||||
}
|
||||
@@ -99,28 +106,38 @@ func formatActivityTotal(activity model.Activity, portfolioBaseCurrency string)
|
||||
return fmt.Sprintf("%.2f %s (~%.2f %s)", total, activity.Currency, convertedTotal, portfolioBaseCurrency)
|
||||
}
|
||||
|
||||
// getConvertedPrice returns the converted price or original if conversion fails
|
||||
// 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 ""
|
||||
}
|
||||
|
||||
convertedPrice, err := util.ConvertCurrency(activity.Price, activity.Currency, portfolioBaseCurrency)
|
||||
// Use historical exchange rate for the transaction date
|
||||
convertedPrice, isHistorical, err := util.GetExchangeRateWithFallback(activity.Currency, portfolioBaseCurrency, activity.Date)
|
||||
if err != nil {
|
||||
return "Conv. Error"
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%.2f %s", convertedPrice, portfolioBaseCurrency)
|
||||
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 or original if conversion fails
|
||||
// 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
|
||||
convertedTotal, err := util.ConvertCurrency(total, activity.Currency, portfolioBaseCurrency)
|
||||
|
||||
// Use historical exchange rate for the transaction date
|
||||
convertedTotal, err := util.ConvertCurrencyHistorical(total, activity.Currency, portfolioBaseCurrency, activity.Date)
|
||||
if err != nil {
|
||||
return "Conv. Error"
|
||||
}
|
||||
@@ -128,15 +145,47 @@ func getConvertedTotal(activity model.Activity, portfolioBaseCurrency string) st
|
||||
return fmt.Sprintf("%.2f %s", convertedTotal, portfolioBaseCurrency)
|
||||
}
|
||||
|
||||
// formatTotalInvestedWithConversion formats total invested with currency info
|
||||
// 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
|
||||
}
|
||||
}
|
||||
@@ -145,5 +194,120 @@ func formatTotalInvestedWithConversion(activities []model.Activity, baseCurrency
|
||||
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"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user