246 lines
8.8 KiB
Markdown
246 lines
8.8 KiB
Markdown
# Currency Conversion Feature
|
|
|
|
## Overview
|
|
|
|
The Portfolio Tracker now supports automatic currency conversion with **historical exchange rates** for transactions that are in different currencies than the portfolio's base currency. This feature uses the actual exchange rates from the transaction date, providing accurate historical conversion and allowing users to have a unified view of their investments across multiple currencies.
|
|
|
|
## How It Works
|
|
|
|
### Automatic Historical Detection
|
|
- When a transaction is added, the system automatically detects the stock's currency from Yahoo Finance
|
|
- If the transaction currency differs from the portfolio's base currency, **historical conversion rates from the transaction date** are fetched
|
|
- Converted values using historical rates are displayed alongside original values for transparency
|
|
- Falls back to current rates if historical rates are unavailable (marked with *)
|
|
|
|
### Display Format
|
|
The transaction table now includes additional columns when multi-currency transactions are present:
|
|
|
|
| Original Column | New Column | Description |
|
|
|----------------|------------|-------------|
|
|
| Preis | Preis (BASE_CURRENCY) | Shows converted price in portfolio base currency |
|
|
| Gesamt | Gesamt (BASE_CURRENCY) | Shows converted total in portfolio base currency |
|
|
|
|
### Example Display
|
|
```
|
|
Original: 150.00 USD
|
|
Converted: 135.50 EUR (when EUR is portfolio base currency)
|
|
```
|
|
|
|
## Features
|
|
|
|
### 1. Historical Currency Conversion
|
|
- **Historical exchange rates** are fetched from `exchangerate-api.com` and `frankfurter.app` for accurate transaction-date conversion
|
|
- Historical rates are cached for 24 hours, current rates for 1 hour to improve performance
|
|
- Falls back to current rates when historical rates are unavailable
|
|
- Supports all major world currencies with date-specific accuracy
|
|
|
|
### 2. Smart Display Logic
|
|
- If transaction currency = portfolio currency: shows "-" in conversion columns
|
|
- If currencies differ: shows converted amount
|
|
- If conversion fails: shows "Conv. Error"
|
|
- If historical rate unavailable: shows converted amount with "*" indicator (using current rate)
|
|
|
|
### 3. Portfolio Summary
|
|
- Total invested amount is automatically converted to portfolio base currency using historical rates
|
|
- Summary shows "(historical rates)" or "(converted)" indicator when multi-currency transactions exist
|
|
- Provides most accurate historical portfolio valuation
|
|
|
|
### 4. Currency Information Panel
|
|
A new information panel explains:
|
|
- That transactions in other currencies are automatically converted using **historical exchange rates**
|
|
- Historical rates are fetched for the actual transaction date
|
|
- Falls back to current rates when historical data is unavailable (marked with *)
|
|
- Conversion provides accurate historical portfolio tracking
|
|
|
|
## Technical Implementation
|
|
|
|
### Currency Conversion Utility (`internal/util/currency.go`)
|
|
|
|
#### Key Functions:
|
|
```go
|
|
// Get current exchange rate between two currencies
|
|
GetExchangeRate(fromCurrency, toCurrency string) (float64, error)
|
|
|
|
// Get historical exchange rate for a specific date
|
|
GetHistoricalExchangeRate(fromCurrency, toCurrency string, date time.Time) (float64, error)
|
|
|
|
// Convert amount from one currency to another using current rates
|
|
ConvertCurrency(amount float64, fromCurrency, toCurrency string) (float64, error)
|
|
|
|
// Convert amount using historical exchange rate for a specific date
|
|
ConvertCurrencyHistorical(amount float64, fromCurrency, toCurrency string, date time.Time) (float64, error)
|
|
|
|
// Format currency with appropriate symbol
|
|
FormatCurrencyWithSymbol(amount float64, currency string) string
|
|
|
|
// Batch convert multiple amounts efficiently
|
|
BatchConvertCurrency(amounts []float64, fromCurrency, toCurrency string) ([]float64, error)
|
|
```
|
|
|
|
#### Caching System:
|
|
- **Cache Duration**: 1 hour per exchange rate
|
|
- **Cache Key Format**: `FROM_TO` (e.g., "USD_EUR")
|
|
- **Memory Usage**: Minimal - only stores rate and timestamp
|
|
- **Thread Safe**: Uses RWMutex for concurrent access
|
|
|
|
### Template Helper Functions (`internal/web/templates/helpers.go`)
|
|
|
|
#### Key Helper Functions:
|
|
```go
|
|
// Get converted price for display using historical rates
|
|
getConvertedPrice(activity model.Activity, portfolioBaseCurrency string) string
|
|
|
|
// Get converted total for display using historical rates
|
|
getConvertedTotal(activity model.Activity, portfolioBaseCurrency string) string
|
|
|
|
// Get converted total with fallback information
|
|
getConvertedTotalWithFallbackInfo(activity model.Activity, portfolioBaseCurrency string) string
|
|
|
|
// Calculate total invested in base currency using historical rates
|
|
calculateTotalInvestedInBaseCurrency(activities []model.Activity, baseCurrency string) float64
|
|
|
|
// Format total with historical conversion indicator
|
|
formatTotalInvestedWithConversion(activities []model.Activity, baseCurrency string) string
|
|
```
|
|
|
|
## Supported Currencies
|
|
|
|
The system supports all currencies provided by the exchange rate API, including:
|
|
|
|
| Currency | Code | Symbol |
|
|
|----------|------|--------|
|
|
| US Dollar | USD | $ |
|
|
| Euro | EUR | € |
|
|
| British Pound | GBP | £ |
|
|
| Japanese Yen | JPY | ¥ |
|
|
| Swiss Franc | CHF | CHF |
|
|
| Canadian Dollar | CAD | C$ |
|
|
| Australian Dollar | AUD | A$ |
|
|
| And many more... | | |
|
|
|
|
## Error Handling
|
|
|
|
### API Failures
|
|
- If exchange rate API is unavailable: shows "Conv. Error"
|
|
- Original transaction data is always preserved
|
|
- System continues to function normally
|
|
|
|
### Invalid Currencies
|
|
- Unknown currency codes are handled gracefully
|
|
- System falls back to displaying original amounts
|
|
- No data loss occurs
|
|
|
|
### Network Issues
|
|
- Cached rates are used when possible
|
|
- Graceful degradation to original currency display
|
|
- User is informed via "Conv. Error" message
|
|
|
|
## Performance Considerations
|
|
|
|
### Caching Strategy
|
|
- **Hit Rate**: High due to 1-hour cache duration
|
|
- **API Calls**: Minimized through intelligent caching
|
|
- **Memory Usage**: Low - only active currency pairs cached
|
|
- **Cleanup**: Automatic cache expiration
|
|
|
|
### Batch Operations
|
|
- Multiple conversions use single API call when possible
|
|
- Efficient for portfolios with many same-currency transactions
|
|
- Reduces API rate limit consumption
|
|
|
|
## Configuration
|
|
|
|
### API Limits
|
|
- **Free Tier**: 1,500 requests per month
|
|
- **Rate Limiting**: Built-in request throttling
|
|
- **Fallback**: Graceful degradation when limits exceeded
|
|
|
|
### Cache Management
|
|
```go
|
|
// Clear cache manually (for testing/debugging)
|
|
util.ClearCache()
|
|
|
|
// Get cache statistics
|
|
cacheInfo := util.GetCacheInfo()
|
|
```
|
|
|
|
## Usage Examples
|
|
|
|
### Adding Multi-Currency Transaction
|
|
1. User adds transaction for Apple (AAPL) in USD to EUR portfolio
|
|
2. System detects USD currency from Yahoo Finance
|
|
3. Fetches USD/EUR exchange rate
|
|
4. Displays both original (USD) and converted (EUR) amounts
|
|
5. Caches exchange rate for future use
|
|
|
|
### Portfolio Summary
|
|
```
|
|
Portfolio: "My Investments" (EUR)
|
|
Total Invested: 15,430.50 EUR (converted)
|
|
|
|
Recent Transactions:
|
|
- AAPL: 150.00 USD (~135.50 EUR)
|
|
- BMW.DE: 85.30 EUR (-)
|
|
- NESN.SW: 110.20 CHF (~102.15 EUR)
|
|
```
|
|
|
|
## Testing
|
|
|
|
### Unit Tests
|
|
- All currency utilities have comprehensive test coverage
|
|
- Tests include edge cases and error conditions
|
|
- Performance benchmarks included
|
|
|
|
### Integration Tests
|
|
- Test with real transaction data
|
|
- Verify UI display logic
|
|
- Cache behavior validation
|
|
|
|
## Future Enhancements
|
|
|
|
### Planned Features
|
|
1. **Historical Rates**: Use transaction date for accurate historical conversion
|
|
2. **Multiple Rate Providers**: Fallback to alternative APIs
|
|
3. **Custom Rate Override**: Allow manual exchange rate input
|
|
4. **Currency Trends**: Show exchange rate history
|
|
5. **Base Currency Change**: Convert entire portfolio to new base currency
|
|
|
|
### API Improvements
|
|
1. **Rate Provider Selection**: Choose between multiple APIs
|
|
2. **Custom Update Intervals**: Configurable cache duration
|
|
3. **Offline Mode**: Store rates locally for offline use
|
|
|
|
## Troubleshooting
|
|
|
|
### Common Issues
|
|
|
|
#### "Conv. Error" Displayed
|
|
- **Cause**: Exchange rate API unavailable or rate limit exceeded
|
|
- **Solution**: Wait and refresh, or check internet connection
|
|
- **Impact**: Original data still visible and functional
|
|
|
|
#### Slow Loading
|
|
- **Cause**: First-time rate fetching or cache expiration
|
|
- **Solution**: Subsequent loads will be faster due to caching
|
|
- **Mitigation**: Consider pre-warming cache for common currencies
|
|
|
|
#### Incorrect Rates
|
|
- **Cause**: API data delay or temporary inconsistency
|
|
- **Solution**: Rates update automatically within 1 hour
|
|
- **Manual Fix**: Clear cache to force immediate refresh
|
|
|
|
### Debug Commands
|
|
```bash
|
|
# Clear currency cache
|
|
curl -X POST http://localhost:8080/api/admin/clear-currency-cache
|
|
|
|
# Get cache status
|
|
curl http://localhost:8080/api/admin/currency-cache-info
|
|
```
|
|
|
|
## Security Considerations
|
|
|
|
- **No API Keys Required**: Uses free public API
|
|
- **Rate Limiting**: Built-in protection against abuse
|
|
- **Data Privacy**: No sensitive data sent to external APIs
|
|
- **Fallback Security**: System functions without external dependencies |