Skip to content

Commit be8d29b

Browse files
committed
Compute investments
1 parent 7702994 commit be8d29b

File tree

7 files changed

+82
-15
lines changed

7 files changed

+82
-15
lines changed

Sources/Grodt/Application/routes.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,15 @@ func routes(_ app: Application) async throws {
99
let tickerDTOMapper = TickerDTOMapper()
1010
let loginResponseDTOMapper = LoginResponseDTOMapper()
1111
let transactionDTOMapper = TransactionDTOMapper(currencyDTOMapper: currencyDTOMapper)
12+
let tickerRepository = PostgresTickerRepository(database: app.db)
1213
let livePriceService = LivePriceService(alphavantage: alphavantage)
1314
let quoteCache = PostgresQuoteRepository(database: app.db)
1415
let priceService = CachedPriceService(priceService: livePriceService, cache: quoteCache)
16+
let investmentDTOMapper = InvestmentDTOMapper(currencyDTOMapper: currencyDTOMapper,
17+
tickerRepository: tickerRepository,
18+
priceService: priceService)
1519
let portfolioPerformanceCalculator = PortfolioPerformanceCalculator(priceService: priceService)
16-
let portfolioDTOMapper = PortfolioDTOMapper(transactionDTOMapper: transactionDTOMapper,
20+
let portfolioDTOMapper = PortfolioDTOMapper(investmentDTOMapper: investmentDTOMapper,
1721
currencyDTOMapper: currencyDTOMapper,
1822
performanceCalculator: portfolioPerformanceCalculator)
1923
let portfolioPerformanceUpdater = PortfolioPerformanceUpdater(
@@ -26,7 +30,7 @@ func routes(_ app: Application) async throws {
2630
let transactionChangedHandler = TransactionChangedHandler(portfolioRepository: PostgresPortfolioRepository(database: app.db),
2731
historicalPerformanceUpdater: portfolioPerformanceUpdater)
2832

29-
var tickersController = TickersController(tickerRepository: PostgresTickerRepository(database: app.db),
33+
var tickersController = TickersController(tickerRepository: tickerRepository,
3034
dataMapper: tickerDTOMapper,
3135
tickerService: alphavantage)
3236
let tickerChangeHandler = TickerChangeHandler(priceService: priceService)
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import Foundation
2+
import CollectionConcurrencyKit
3+
4+
class InvestmentDTOMapper {
5+
private let currencyDTOMapper: CurrencyDTOMapper
6+
private let tickerRepository: TickerRepository
7+
private let priceService: PriceService
8+
9+
init(currencyDTOMapper: CurrencyDTOMapper,
10+
tickerRepository: TickerRepository,
11+
priceService: PriceService) {
12+
self.currencyDTOMapper = currencyDTOMapper
13+
self.priceService = priceService
14+
self.tickerRepository = tickerRepository
15+
}
16+
17+
func investments(from transactions: [Transaction]) async throws -> [InvestmentDTO] {
18+
let investments: [InvestmentDTO] = try await transactions
19+
.grouped { $0.ticker }
20+
.asyncCompactMap { (ticker, transactions) in
21+
let name = try await tickerRepository.tickers(for: ticker)?.name ?? ""
22+
let latestPrice = try await priceService.price(for: ticker)
23+
var pricePerPurchase: [Decimal: Decimal] = [:]
24+
var numberOfShares: Decimal = 0
25+
var totalCost: Decimal = 0
26+
transactions.forEach { transaction in
27+
pricePerPurchase[transaction.pricePerShareAtPurchase] = transaction.numberOfShares
28+
numberOfShares += transaction.numberOfShares
29+
totalCost += (transaction.pricePerShareAtPurchase * transaction.numberOfShares) + transaction.fees
30+
}
31+
let currentValue = numberOfShares * latestPrice
32+
let avgBuyPrice = pricePerPurchase.keys.reduce(0) { $0 + $1 } / Decimal(pricePerPurchase.count)
33+
let profit = currentValue - totalCost
34+
let totalReturn = (totalCost == 0 ? 0 : profit / totalCost).ro
35+
36+
return InvestmentDTO(name: name,
37+
shortName: ticker,
38+
avgBuyPrice: avgBuyPrice,
39+
latestPrice: latestPrice,
40+
totalReturn: totalReturn,
41+
profit: profit,
42+
value: currentValue,
43+
numberOfShares: numberOfShares,
44+
currency: currencyDTOMapper.currency(from: transactions.first!.currency))
45+
}
46+
47+
return investments
48+
}
49+
}

Sources/Grodt/DTOs/DTOMappers/PortfolioDTOMapper.swift

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,39 +2,34 @@ import Foundation
22
import CollectionConcurrencyKit
33

44
class PortfolioDTOMapper {
5-
private let transactionDTOMapper: TransactionDTOMapper
5+
private let investmentDTOMapper: InvestmentDTOMapper
66
private let currencyDTOMapper: CurrencyDTOMapper
77
private let performanceCalculator: PortfolioPerformanceCalculating
88

9-
init(transactionDTOMapper: TransactionDTOMapper,
9+
init(investmentDTOMapper: InvestmentDTOMapper,
1010
currencyDTOMapper: CurrencyDTOMapper,
1111
performanceCalculator: PortfolioPerformanceCalculating) {
12-
self.transactionDTOMapper = transactionDTOMapper
12+
self.investmentDTOMapper = investmentDTOMapper
1313
self.currencyDTOMapper = currencyDTOMapper
1414
self.performanceCalculator = performanceCalculator
1515
}
1616

1717
func portfolio(from portfolio: Portfolio) async throws -> PortfolioDTO {
1818

19+
let investments = try await investmentDTOMapper.investments(from: portfolio.transactions)
1920
return try await PortfolioDTO(id: portfolio.id?.uuidString ?? "",
2021
name: portfolio.name,
2122
currency: currencyDTOMapper.currency(from: portfolio.currency),
2223
performance: performance(for: portfolio),
23-
transactions: portfolio.transactions
24-
.sorted(by: { lhs, rhs in
25-
return lhs.purchaseDate > rhs.purchaseDate
26-
})
27-
.compactMap { transactionDTOMapper.transaction(from: $0) }
28-
)
24+
investments: investments)
2925
}
3026

3127
func portfolioInfo(from portfolio: Portfolio) async throws -> PortfolioInfoDTO {
3228

3329
return try await PortfolioInfoDTO(id: portfolio.id?.uuidString ?? "",
3430
name: portfolio.name,
3531
currency: currencyDTOMapper.currency(from: portfolio.currency),
36-
performance: performance(for: portfolio),
37-
transactions: portfolio.transactions.compactMap { $0.id?.uuidString }
32+
performance: performance(for: portfolio)
3833
)
3934
}
4035

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import Foundation
2+
3+
struct InvestmentDTO: Codable, Equatable {
4+
let name: String
5+
let shortName: String
6+
let avgBuyPrice: Decimal
7+
let latestPrice: Decimal
8+
let totalReturn: Decimal
9+
let profit: Decimal
10+
let value: Decimal
11+
let numberOfShares: Decimal
12+
let currency: CurrencyDTO
13+
}

Sources/Grodt/DTOs/PortfolioDTO.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,5 @@ struct PortfolioDTO: Codable, Equatable {
55
let name: String
66
let currency: CurrencyDTO
77
let performance: PortfolioPerformanceDTO
8-
let transactions: [TransactionDTO]
8+
let investments: [InvestmentDTO]
99
}

Sources/Grodt/DTOs/PortfolioInfoDTO.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,4 @@ struct PortfolioInfoDTO: Codable, Equatable {
55
let name: String
66
let currency: CurrencyDTO
77
let performance: PortfolioPerformanceDTO
8-
let transactions: [String]
98
}

Sources/Grodt/Persistency/Repositories/TickerRepository.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import Fluent
22

33
protocol TickerRepository {
44
func allTickers() async throws -> [Ticker]
5+
func tickers(for symbol: String) async throws -> Ticker?
56
}
67

78
class PostgresTickerRepository: TickerRepository {
@@ -15,4 +16,10 @@ class PostgresTickerRepository: TickerRepository {
1516
return try await Ticker.query(on: database)
1617
.all()
1718
}
19+
20+
func tickers(for symbol: String) async throws -> Ticker? {
21+
return try await Ticker.query(on: database)
22+
.filter(\Ticker.$symbol == symbol)
23+
.first()
24+
}
1825
}

0 commit comments

Comments
 (0)