draft
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
package dev.darktale
|
||||
|
||||
import dev.darktale.model.Asset
|
||||
import dev.darktale.model.CompoundInterestDeposit
|
||||
import dev.darktale.model.Deposit
|
||||
import java.math.BigDecimal
|
||||
import java.time.LocalDate
|
||||
|
||||
interface AccrualCalculator {
|
||||
fun calculateAccrual(
|
||||
asset: Asset,
|
||||
dateFrom: LocalDate? = null,
|
||||
dateTo: LocalDate
|
||||
): BigDecimal
|
||||
}
|
||||
|
||||
class DepositAccrualCalculator : AccrualCalculator {
|
||||
override fun calculateAccrual(
|
||||
asset: Asset,
|
||||
dateFrom: LocalDate?,
|
||||
dateTo: LocalDate,
|
||||
): BigDecimal {
|
||||
val deposit = asset as Deposit
|
||||
val startDate = asset.dateFrom
|
||||
var curDateFrom = startDate
|
||||
var curDateTo: LocalDate? = deposit.nextAccrualDate(curDateFrom)
|
||||
var calculatedAccrual = BigDecimal.ZERO
|
||||
while (curDateTo != null && curDateTo <= dateTo) {
|
||||
if (curDateFrom.year == curDateTo.year)
|
||||
calculatedAccrual += accrualBetween(
|
||||
curDateFrom, curDateTo,
|
||||
asset.quantity + calculatedAccrual,
|
||||
deposit.percent
|
||||
)
|
||||
else
|
||||
calculatedAccrual += (accrualBetween(
|
||||
curDateFrom, LocalDate.of(curDateFrom.year, 12, 31),
|
||||
asset.quantity + calculatedAccrual,
|
||||
deposit.percent
|
||||
) + accrualBetween(
|
||||
LocalDate.of(curDateTo.year, 1, 1), curDateTo,
|
||||
asset.quantity + calculatedAccrual,
|
||||
deposit.percent
|
||||
))
|
||||
curDateFrom = curDateTo
|
||||
curDateTo = deposit.nextAccrualDate(curDateFrom)
|
||||
}
|
||||
return calculatedAccrual - if (dateFrom != null && dateFrom > asset.dateFrom)
|
||||
calculateAccrual(asset, asset.dateFrom, startDate) else BigDecimal.ZERO
|
||||
}
|
||||
|
||||
private fun accrualBetween(
|
||||
dateFrom: LocalDate,
|
||||
dateTo: LocalDate,
|
||||
baseAmount: BigDecimal,
|
||||
percent: BigDecimal,
|
||||
) = baseAmount * percent *
|
||||
BigDecimal.valueOf((dateTo.dayOfYear - dateFrom.dayOfYear + 1).toLong()) /
|
||||
BigDecimal.valueOf(dateFrom.lengthOfYear().toLong())
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
package org.example
|
||||
|
||||
package dev.darktale
|
||||
fun main() {
|
||||
println("Hello World!")
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package dev.darktale.model
|
||||
|
||||
import java.math.BigDecimal
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalTime
|
||||
|
||||
interface Asset {
|
||||
val assetType: AssetType
|
||||
var quantity: BigDecimal
|
||||
var storagePlace: StoragePlace
|
||||
val dateFrom: LocalDate
|
||||
var dateTo: LocalDate?
|
||||
}
|
||||
|
||||
enum class AssetType {
|
||||
TRADING_INSTRUMENT,
|
||||
DEPOSIT;
|
||||
}
|
||||
|
||||
interface Deposit : Asset {
|
||||
override val assetType: AssetType
|
||||
get() = AssetType.DEPOSIT
|
||||
var percent: BigDecimal
|
||||
|
||||
fun nextAccrualDate(onDate: LocalDate): LocalDate?
|
||||
}
|
||||
|
||||
class CompoundInterestDeposit(
|
||||
override var quantity: BigDecimal,
|
||||
override var storagePlace: StoragePlace,
|
||||
override var dateFrom: LocalDate,
|
||||
override var dateTo: LocalDate?,
|
||||
override var percent: BigDecimal,
|
||||
var accrualDay: Int,
|
||||
) : Deposit {
|
||||
override fun nextAccrualDate(onDate: LocalDate): LocalDate? {
|
||||
val currentMonthAccrualDate = LocalDate.of(
|
||||
onDate.year,
|
||||
onDate.month,
|
||||
accrualDay.coerceAtMost(onDate.lengthOfMonth())
|
||||
)
|
||||
val calculatedDate = if (currentMonthAccrualDate > onDate) currentMonthAccrualDate
|
||||
else currentMonthAccrualDate.plusMonths(1)
|
||||
return calculatedDate.takeIf { it < dateTo }
|
||||
}
|
||||
}
|
||||
|
||||
class SimpleInterestDeposit(
|
||||
override var quantity: BigDecimal,
|
||||
override var storagePlace: StoragePlace,
|
||||
override var dateFrom: LocalDate,
|
||||
override var dateTo: LocalDate?,
|
||||
override var percent: BigDecimal,
|
||||
) : Deposit {
|
||||
override fun nextAccrualDate(onDate: LocalDate) = dateTo.takeIf { it != null && it > onDate }
|
||||
|
||||
override fun toString() = "$quantity since $dateFrom till $dateTo with ${percent*BigDecimal.valueOf(100L)}%"
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package org.example.model
|
||||
package dev.darktale.model
|
||||
|
||||
interface StoragePlace {
|
||||
var name: String
|
||||
@@ -1,14 +0,0 @@
|
||||
package org.example.model
|
||||
|
||||
import java.math.BigDecimal
|
||||
|
||||
interface Asset {
|
||||
var quantity: BigDecimal
|
||||
var assetType: AssetType
|
||||
var storagePlace: StoragePlace
|
||||
}
|
||||
|
||||
enum class AssetType {
|
||||
TRADING_INSTRUMENT,
|
||||
DEPOSIT;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package dev.darktale
|
||||
|
||||
import dev.darktale.model.Deposit
|
||||
import dev.darktale.model.SimpleInterestDeposit
|
||||
import dev.darktale.model.StoragePlace
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.extension.ExtensionContext
|
||||
import org.junit.jupiter.params.ParameterizedTest
|
||||
import org.junit.jupiter.params.provider.Arguments
|
||||
import org.junit.jupiter.params.provider.ArgumentsProvider
|
||||
import org.junit.jupiter.params.provider.ArgumentsSource
|
||||
import java.math.BigDecimal
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalTime
|
||||
import java.util.stream.Stream
|
||||
|
||||
class DepositAccrualCalculatorTest {
|
||||
@ParameterizedTest
|
||||
@ArgumentsSource(DepositProvider::class)
|
||||
fun testAccrual(deposit: Deposit, dateFrom: LocalDate, dateTo:LocalDate, expected: BigDecimal) {
|
||||
val accrued = DepositAccrualCalculator().calculateAccrual(deposit, dateFrom, dateTo).stripTrailingZeros()
|
||||
|
||||
assertEquals(
|
||||
0, expected.compareTo(accrued),
|
||||
"expected $expected, actual $accrued"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class DepositProvider : ArgumentsProvider {
|
||||
private val testStoragePlace = object : StoragePlace {
|
||||
override var name: String = "Qwe"
|
||||
}
|
||||
|
||||
override fun provideArguments(context: ExtensionContext): Stream<Arguments> {
|
||||
val yearStart = LocalDate.of(2024, 1, 1)
|
||||
val yearEnd = LocalDate.of(2024, 12, 31)
|
||||
|
||||
val deposit = SimpleInterestDeposit(
|
||||
BigDecimal.valueOf(1000L),
|
||||
testStoragePlace,
|
||||
yearStart,
|
||||
yearEnd,
|
||||
BigDecimal.valueOf(0.1),
|
||||
)
|
||||
|
||||
return Stream.of(
|
||||
Arguments.of(deposit, yearStart, yearEnd, BigDecimal.valueOf(100L)),
|
||||
Arguments.of(deposit, yearStart, yearEnd.minusDays(1), BigDecimal.ZERO),
|
||||
Arguments.of(deposit, yearEnd.minusDays(1), yearEnd, BigDecimal.valueOf(100L)),
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user