diff --git a/app/src/main/java/net/pokeranalytics/android/calculus/Stat.kt b/app/src/main/java/net/pokeranalytics/android/calculus/Stat.kt index 3ac2f0ca..0a216d13 100644 --- a/app/src/main/java/net/pokeranalytics/android/calculus/Stat.kt +++ b/app/src/main/java/net/pokeranalytics/android/calculus/Stat.kt @@ -14,6 +14,7 @@ import net.pokeranalytics.android.util.extensions.formatted import net.pokeranalytics.android.util.extensions.formattedHourlyDuration import net.pokeranalytics.android.util.extensions.toCurrency import java.util.* +import kotlin.math.exp class StatFormattingException(message: String) : Exception(message) { @@ -128,6 +129,19 @@ enum class Stat : RowRepresentable { return netBB / numberOfHands * 100 } + fun riskOfRuin(hourlyRate: Double, hourlyStandardDeviation: Double, bankrollValue: Double) : Double? { + + if (bankrollValue <= 0.0) { + return null + } + + val numerator = -2 * hourlyRate * bankrollValue + val denominator = Math.pow(hourlyStandardDeviation, 2.0) + val ratio = numerator / denominator + return exp(ratio) + + } + } override val resId: Int? diff --git a/app/src/main/java/net/pokeranalytics/android/calculus/bankroll/BankrollCalculator.kt b/app/src/main/java/net/pokeranalytics/android/calculus/bankroll/BankrollCalculator.kt index fdc95863..76eea4e7 100644 --- a/app/src/main/java/net/pokeranalytics/android/calculus/bankroll/BankrollCalculator.kt +++ b/app/src/main/java/net/pokeranalytics/android/calculus/bankroll/BankrollCalculator.kt @@ -1,9 +1,14 @@ package net.pokeranalytics.android.calculus.bankroll import io.realm.Realm +import net.pokeranalytics.android.calculus.Calculator +import net.pokeranalytics.android.calculus.ComputableGroup +import net.pokeranalytics.android.calculus.ComputedResults +import net.pokeranalytics.android.calculus.Stat import net.pokeranalytics.android.model.realm.Filter import net.pokeranalytics.android.model.realm.Session import net.pokeranalytics.android.model.realm.Transaction +import net.pokeranalytics.android.model.realm.TransactionType class BankrollCalculator { @@ -14,22 +19,58 @@ class BankrollCalculator { val realm = Realm.getDefaultInstance() val report = BankrollReport(setup) + val useRatedValues = (setup.bankroll == null) - val sessions = Filter.queryOn(realm, setup.queryConditions) - val transactions = Filter.queryOn(realm, setup.queryConditions) + val queryConditions = setup.queryConditions + val sessions = Filter.queryOn(realm, queryConditions) + val transactions = Filter.queryOn(realm, queryConditions) - val sessionsNet = sessions.sum("result.net") val transactionsNet = transactions.sum("value") + report.addDatedItems(transactions) transactions.forEach { report.addTransaction(it) } + val group = ComputableGroup("", queryConditions, listOf(Stat.NET_RESULT, Stat.HOURLY_RATE, Stat.STANDARD_DEVIATION_HOURLY)) + val result = Calculator.compute(realm, group, Calculator.Options()) + result.computedStat(Stat.NET_RESULT)?.let { + report.netResult = it.value + } + + report.transactionsNet = transactionsNet.toDouble() + + this.computeRiskOfRuin(report, result) + + val depositType = TransactionType.getByValue(TransactionType.Value.DEPOSIT, realm) + report.transactionBuckets[depositType.id]?.let { bucket -> + report.depositTotal = bucket.transactions.sumByDouble { it.amount } + } + + val withdrawalType = TransactionType.getByValue(TransactionType.Value.WITHDRAWAL, realm) + report.transactionBuckets[withdrawalType.id]?.let { bucket -> + report.withdrawalTotal = bucket.transactions.sumByDouble { it.amount } + } + + realm.close() return report } + fun computeRiskOfRuin(report: BankrollReport, results: ComputedResults) { + + val hourlyRate = results.computedStat(Stat.HOURLY_RATE)?.value + val hourlyStandardDeviation = results.computedStat(Stat.STANDARD_DEVIATION_HOURLY)?.value + + if (hourlyRate != null && hourlyStandardDeviation != null) { + + report.riskOfRuin = Stat.riskOfRuin(hourlyRate, hourlyStandardDeviation, report.total) + + } + + } + } } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/calculus/bankroll/BankrollReport.kt b/app/src/main/java/net/pokeranalytics/android/calculus/bankroll/BankrollReport.kt index 8968968c..9804bd0b 100644 --- a/app/src/main/java/net/pokeranalytics/android/calculus/bankroll/BankrollReport.kt +++ b/app/src/main/java/net/pokeranalytics/android/calculus/bankroll/BankrollReport.kt @@ -1,12 +1,109 @@ package net.pokeranalytics.android.calculus.bankroll -import net.pokeranalytics.android.calculus.interfaces.DatableValue import net.pokeranalytics.android.model.filter.QueryCondition +import net.pokeranalytics.android.model.interfaces.DatedValue import net.pokeranalytics.android.model.realm.Bankroll import net.pokeranalytics.android.model.realm.Transaction import java.util.* import kotlin.collections.HashMap + +class BankrollReport(setup: BankrollReportSetup) { + + /** + * The setup used to compute the report + */ + var setup: BankrollReportSetup = setup + + /** + * The value of the bankroll + */ + var total: Double = 0.0 + private set + + /** + * The net result from poker computables + */ + var netResult: Double = 0.0 + set(value) { + field = value + total = this.netResult + this.transactionsNet + } + + /** + * The net result from transactions + */ + var transactionsNet: Double = 0.0 + set(value) { + field = value + total = this.netResult + this.transactionsNet + } + + /** + * The sum of all deposits + */ + var depositTotal: Double = 0.0 + set(value) { + field = value + this.netBanked = this.depositTotal + this.withdrawalTotal + } + + /** + * The sum of all withdrawals + */ + var withdrawalTotal: Double = 0.0 + set(value) { + field = value + this.netBanked = this.depositTotal + this.withdrawalTotal + } + + /** + * The difference between withdrawals and deposits + */ + var netBanked: Double = 0.0 + private set + + /** + * The risk of ruin + */ + var riskOfRuin: Double? = null + + var transactions: List = mutableListOf() + private set + + var transactionBuckets: HashMap = HashMap() + private set + + var evolutionPoints: MutableList = mutableListOf() + var evolutionItems: MutableList = mutableListOf() + private set + + fun addDatedItems(items: Collection) { + this.evolutionItems.addAll(items) + } + + fun addTransaction(transaction: Transaction) { + + transaction.type?.let { type -> + + var bucket = this.transactionBuckets[type.id] + + if (bucket == null) { + val b = TransactionBucket(this.setup.bankroll == null) + this.transactionBuckets[type.id] = b + bucket = b + } + + bucket.addTransaction(transaction) + + } ?: run { + throw Exception("Transaction has no type") + } + + } + +} + /** * A class describing the parameters required to launch a bankroll report * @@ -60,72 +157,8 @@ class TransactionBucket(useRate: Boolean = false) { } -class BRGraphPoint { +data class BRGraphPoint(var value: Double = 0.0, var date: Date? = null, var data: Any? = null) { - var value: Double = 0.0 var variation: Double = 0.0 - var date: Date? = null } - -class BankrollReport(setup: BankrollReportSetup) { - - /** - * The setup used to compute the report - */ - var setup: BankrollReportSetup = setup - - /** - * The value of the bankroll - */ - var total: Double = 0.0 - private set - - /** - * The net result from poker computables - */ - var netResult: Double = 0.0 - private set - - /** - * The difference between withdrawals and deposits - */ - var netBanked: Double = 0.0 - private set - - /** - * The risk of ruin - */ - var riskOfRuin: Double = 0.0 - private set - - var transactions: List = mutableListOf() - private set - - var transactionBuckets: HashMap = HashMap() - - var evolutionPoints: Array = arrayOf() - var evolutionItems: Array = arrayOf() - - fun addTransaction(transaction: Transaction) { - - transaction.type?.let { type -> - - var bucket = this.transactionBuckets[type.id] - - if (bucket == null) { - val b = TransactionBucket(this.setup.bankroll == null) - this.transactionBuckets[type.id] = b - bucket = b - } - - bucket.addTransaction(transaction) - - } ?: run { - throw Exception("Transaction has no type") - } - - } - - -} \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/calculus/interfaces/Datable.kt b/app/src/main/java/net/pokeranalytics/android/calculus/interfaces/Datable.kt deleted file mode 100644 index 7e8855f2..00000000 --- a/app/src/main/java/net/pokeranalytics/android/calculus/interfaces/Datable.kt +++ /dev/null @@ -1,11 +0,0 @@ -package net.pokeranalytics.android.calculus.interfaces - -import java.util.* - -interface Datable { - var date: Date -} - -interface DatableValue : Datable { - var value: Double -} \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/model/interfaces/Dated.kt b/app/src/main/java/net/pokeranalytics/android/model/interfaces/Dated.kt new file mode 100644 index 00000000..86cea43b --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/model/interfaces/Dated.kt @@ -0,0 +1,15 @@ +package net.pokeranalytics.android.model.interfaces + +import java.util.* + +interface Dated { + + var date: Date + +} + +interface DatedValue : Dated { + + var amount: Double + +} \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/Transaction.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/Transaction.kt index d7291dc7..481a9444 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/Transaction.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/Transaction.kt @@ -7,6 +7,7 @@ import io.realm.annotations.PrimaryKey import net.pokeranalytics.android.R import net.pokeranalytics.android.model.filter.Filterable import net.pokeranalytics.android.model.filter.QueryCondition +import net.pokeranalytics.android.model.interfaces.DatedValue import net.pokeranalytics.android.model.interfaces.Manageable import net.pokeranalytics.android.model.interfaces.SaveValidityStatus import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource @@ -17,7 +18,7 @@ import java.util.* import kotlin.collections.ArrayList -open class Transaction : RealmObject(), Manageable, StaticRowRepresentableDataSource, RowRepresentable, Filterable { +open class Transaction : RealmObject(), Manageable, StaticRowRepresentableDataSource, RowRepresentable, Filterable, DatedValue { companion object { val rowRepresentation: List by lazy { @@ -42,10 +43,10 @@ open class Transaction : RealmObject(), Manageable, StaticRowRepresentableDataSo var bankroll: Bankroll? = null // The amount of the transaction - var amount: Double = 0.0 + override var amount: Double = 0.0 // The date of the transaction - var date: Date = Date() + override var date: Date = Date() // The type of the transaction var type: TransactionType? = null diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/TransactionType.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/TransactionType.kt index eba342a5..714c934a 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/TransactionType.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/TransactionType.kt @@ -14,65 +14,77 @@ import kotlin.collections.ArrayList open class TransactionType : RealmObject(), NameManageable, StaticRowRepresentableDataSource, RowRepresentable { - companion object { - val rowRepresentation : List by lazy { - val rows = ArrayList() - rows.add(SimpleRow.NAME) - rows.addAll(TransactionTypeRow.values()) - rows - } - } - - @PrimaryKey - override var id = UUID.randomUUID().toString() - - // The name of the transaction type - override var name: String = "" - - // Whether or not the amount is added, or subtracted to the bankroll total - var additive: Boolean = false - - // Whether or not the type can be deleted by the user - var lock: Boolean = false - - // The predefined kind, if necessary, like: Withdrawal, deposit, or tips - var kind: Int? = null - - override fun getDisplayName(): String { - return this.name - } - - override fun adapterRows(): List? { - return TransactionType.rowRepresentation - } - - override fun stringForRow(row: RowRepresentable): String { - return when (row) { - SimpleRow.NAME -> this.name - else -> return super.stringForRow(row) - } - } - - override fun editDescriptors(row: RowRepresentable): ArrayList? { - return row.editingDescriptors(mapOf("defaultValue" to this.name)) - } - - override fun updateValue(value: Any?, row: RowRepresentable) { - when (row) { - SimpleRow.NAME -> this.name = value as String? ?: "" - } - } - - override fun isValidForDelete(realm: Realm): Boolean { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. - } - - override fun getFailedDeleteMessage(): Int { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. - } -} -enum class TransactionKind { - WITHDRAWAL, - DEPOSIT + enum class Value { + WITHDRAWAL, + DEPOSIT, + BONUS + } + + companion object { + val rowRepresentation: List by lazy { + val rows = ArrayList() + rows.add(SimpleRow.NAME) + rows.addAll(TransactionTypeRow.values()) + rows + } + + fun getByValue(value: Value, realm: Realm): TransactionType { + val type = realm.where(TransactionType::class.java).equalTo("kind", value.ordinal).findFirst() + type?.let { + return it + } + throw IllegalStateException("Transaction type ${value.name} should exist in database!") + } + + } + + @PrimaryKey + override var id = UUID.randomUUID().toString() + + // The name of the transaction type + override var name: String = "" + + // Whether or not the amount is added, or subtracted to the bankroll total + var additive: Boolean = false + + // Whether or not the type can be deleted by the user + var lock: Boolean = false + + // The predefined kind, if necessary, like: Withdrawal, deposit, or tips + var kind: Int? = null + + override fun getDisplayName(): String { + return this.name + } + + override fun adapterRows(): List? { + return rowRepresentation + } + + override fun stringForRow(row: RowRepresentable): String { + return when (row) { + SimpleRow.NAME -> this.name + else -> return super.stringForRow(row) + } + } + + override fun editDescriptors(row: RowRepresentable): ArrayList? { + return row.editingDescriptors(mapOf("defaultValue" to this.name)) + } + + override fun updateValue(value: Any?, row: RowRepresentable) { + when (row) { + SimpleRow.NAME -> this.name = value as String? ?: "" + } + } + + override fun isValidForDelete(realm: Realm): Boolean { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun getFailedDeleteMessage(): Int { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } } +