This commit is contained in:
2025-11-02 12:07:24 +03:00
parent 9b440f0ee8
commit de2c272158
7 changed files with 205 additions and 17 deletions
+32
View File
@@ -0,0 +1,32 @@
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### IntelliJ IDEA ###
.idea/
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store
@@ -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,4 +1,4 @@
package org.example.model
package dev.darktale.model
interface StoragePlace {
var name: String
-14
View File
@@ -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)),
)
}
}