Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Foundation

protocol PortfolioPerformanceCalculating {
func performance(of portfolio: Portfolio, on date: YearMonthDayDate) async throws -> DatedPortfolioPerformance
func performance(of portfolio: Portfolio, on date: YearMonthDayDate, priceCache: inout [String: Decimal]) async throws -> DatedPortfolioPerformance
}

class PortfolioPerformanceCalculator: PortfolioPerformanceCalculating {
Expand All @@ -11,15 +11,28 @@ class PortfolioPerformanceCalculator: PortfolioPerformanceCalculating {
self.priceService = priceService
}

func performance(of portfolio: Portfolio, on date: YearMonthDayDate) async throws -> DatedPortfolioPerformance {
func performance(
of portfolio: Portfolio,
on date: YearMonthDayDate,
priceCache: inout [String: Decimal]
) async throws -> DatedPortfolioPerformance {
let transactionsUntilDate = portfolio.transactions.filter { YearMonthDayDate($0.purchaseDate) <= date }

let financialsForDate = Financials()

for transaction in transactionsUntilDate {
let inAmount = transaction.numberOfShares * transaction.pricePerShareAtPurchase + transaction.fees
await financialsForDate.addMoneyIn(inAmount)

let value = try await transaction.numberOfShares * self.priceService.price(for: transaction.ticker, on: date)
let price: Decimal
if let cachedPrice = priceCache[transaction.ticker] {
price = cachedPrice
} else {
price = try await self.priceService.price(for: transaction.ticker, on: date)
priceCache[transaction.ticker] = price
}

let value = transaction.numberOfShares * price
await financialsForDate.addValue(value)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,20 @@ class PortfolioPerformanceUpdater: PortfolioHistoricalPerformanceUpdater {
}

func updatePerformanceOfAllPortfolios() async throws {
// Update historical prices for all tickers
// Remove all historrical prices
for quote in try await quoteRepository.allHistoricalQuote() {
try await quoteRepository.delete(quote)
}

// Update historical prices and latest prices for all tickers
let allTickers = try await tickerRepository.allTickers()
for ticker in allTickers {
await rateLimiter.waitIfNeeded()
_ = try await priceService.fetchAndCreateHistoricalPrices(for: ticker.symbol)
await rateLimiter.waitIfNeeded()
if let quote = try await quoteRepository.quote(for: ticker.symbol) {
_ = try await priceService.fetchAndUpdatePrice(for: quote)
}
}

// Update historical performance for all portfolios
Expand All @@ -53,10 +62,11 @@ class PortfolioPerformanceUpdater: PortfolioHistoricalPerformanceUpdater {
func recalculateHistoricalPerformance(of portfolio: Portfolio) async throws {
var datedPerformance = [DatedPortfolioPerformance]()
guard let earliestTransaction = portfolio.earliestTransaction else { return }
let dates = dateRangeUntilYesterday(from: earliestTransaction.purchaseDate)
let dates = dateRangeUntilToday(from: earliestTransaction.purchaseDate)
var priceCache = [String: Decimal]()

for date in dates {
let performanceForDate = try await performanceCalculator.performance(of: portfolio, on: date)
let performanceForDate = try await performanceCalculator.performance(of: portfolio, on: date, priceCache: &priceCache)
datedPerformance.append(performanceForDate)
}

Expand All @@ -72,14 +82,14 @@ class PortfolioPerformanceUpdater: PortfolioHistoricalPerformanceUpdater {
}
}

private func dateRangeUntilYesterday(from startDate: Date) -> [YearMonthDayDate] {
private func dateRangeUntilToday(from startDate: Date) -> [YearMonthDayDate] {
var dates: [YearMonthDayDate] = []
var currentDate = startDate
let calendar = Calendar.current
let yesterday = calendar.date(byAdding: .day, value: -1, to: Date())!
let today = Date()


while currentDate <= yesterday {
while currentDate <= today {
let ymdDate = YearMonthDayDate(currentDate)
dates.append(ymdDate)
currentDate = calendar.date(byAdding: .day, value: 1, to: currentDate)!
Expand Down
8 changes: 2 additions & 6 deletions Sources/Grodt/BusinessLogic/PriceService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ protocol PriceService {
func price(for ticker: String) async throws -> Decimal
func price(for ticker: String, on date: YearMonthDayDate) async throws -> Decimal
func fetchAndCreateHistoricalPrices(for ticker: String) async throws -> HistoricalQuote
func fetchAndUpdatePrice(for outdatedQuote: Quote) async throws -> Decimal
}

class CachedPriceService: PriceService {
Expand Down Expand Up @@ -93,15 +94,10 @@ class CachedPriceService: PriceService {
let quotes = try await liveHistoricalPrices(for: ticker)
let historicalQuote = HistoricalQuote(symbol: ticker, datedQuotes: quotes)
try await quoteRepository.create(historicalQuote)

if let previousQuote = try await quoteRepository.historicalQuote(for: ticker) {
try await quoteRepository.delete(previousQuote)
}

return historicalQuote
}

private func fetchAndUpdatePrice(for outdatedQuote: Quote) async throws -> Decimal {
func fetchAndUpdatePrice(for outdatedQuote: Quote) async throws -> Decimal {
let quote = try await latestQuote(for: outdatedQuote.symbol)
outdatedQuote.lastUpdate = Date()
try await quoteRepository.update(outdatedQuote)
Expand Down
13 changes: 13 additions & 0 deletions Sources/Grodt/Persistency/Repositories/QuoteRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@ protocol QuoteRepository {

func update(_ quote: Quote) async throws

func allHistoricalQuote() async throws -> [HistoricalQuote]

func historicalQuote(for ticker: String) async throws -> HistoricalQuote?

func create(_ historicalQuote: HistoricalQuote) async throws

func delete(_ historicalQuote: HistoricalQuote) async throws

func update(_ historicalQuote: HistoricalQuote) async throws
}

class PostgresQuoteRepository: QuoteRepository {
Expand All @@ -36,6 +40,11 @@ class PostgresQuoteRepository: QuoteRepository {
try await quote.update(on: database)
}

func allHistoricalQuote() async throws -> [HistoricalQuote] {
return try await HistoricalQuote.query(on: database)
.all()
}

func historicalQuote(for ticker: String) async throws -> HistoricalQuote? {
return try await HistoricalQuote.query(on: database)
.filter(\HistoricalQuote.$symbol == ticker)
Expand All @@ -46,6 +55,10 @@ class PostgresQuoteRepository: QuoteRepository {
try await historicalQuote.save(on: database)
}

func update(_ historicalQuote: HistoricalQuote) async throws {
try await historicalQuote.update(on: database)
}

func delete(_ historicalQuote: HistoricalQuote) async throws {
try await historicalQuote.delete(on: database)
}
Expand Down
2 changes: 0 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
version: '3.7'

services:
db:
image: postgres
Expand Down