From a272986b6cf415398a50f1f680f43f554e60d4a5 Mon Sep 17 00:00:00 2001 From: Laurent Date: Tue, 23 Apr 2019 11:47:54 +0200 Subject: [PATCH 1/5] Bankroll report update --- .../pokeranalytics/android/calculus/Stat.kt | 14 ++ .../calculus/bankroll/BankrollCalculator.kt | 47 ++++- .../calculus/bankroll/BankrollReport.kt | 165 +++++++++++------- .../android/calculus/interfaces/Datable.kt | 11 -- .../android/model/interfaces/Dated.kt | 15 ++ .../android/model/realm/Transaction.kt | 7 +- .../android/model/realm/TransactionType.kt | 132 +++++++------- 7 files changed, 248 insertions(+), 143 deletions(-) delete mode 100644 app/src/main/java/net/pokeranalytics/android/calculus/interfaces/Datable.kt create mode 100644 app/src/main/java/net/pokeranalytics/android/model/interfaces/Dated.kt 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. + } } + From 4b9cb85fbb5a8599dfc69bcea03b6f4ddf542e9c Mon Sep 17 00:00:00 2001 From: Laurent Date: Tue, 23 Apr 2019 12:13:33 +0200 Subject: [PATCH 2/5] Add line data set for BankrollReport --- .../android/calculus/Calculator.kt | 10 ++--- .../pokeranalytics/android/calculus/Report.kt | 2 +- .../calculus/bankroll/BankrollCalculator.kt | 11 ++++-- .../calculus/bankroll/BankrollReport.kt | 38 ++++++++++++++++++- .../android/model/realm/Session.kt | 27 ++++++++++--- 5 files changed, 70 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/net/pokeranalytics/android/calculus/Calculator.kt b/app/src/main/java/net/pokeranalytics/android/calculus/Calculator.kt index 26c33c6c..092bd8f8 100644 --- a/app/src/main/java/net/pokeranalytics/android/calculus/Calculator.kt +++ b/app/src/main/java/net/pokeranalytics/android/calculus/Calculator.kt @@ -134,7 +134,7 @@ class Calculator { realm: Realm, criteria: List = listOf(), conditions: List = listOf(), - options: Options + options: Options = Options() ): Report { val computableGroups: MutableList = mutableListOf() @@ -161,7 +161,7 @@ class Calculator { /** * Computes all stats for list of Session sessionGroup */ - fun computeGroups(realm: Realm, groups: List, options: Options): Report { + fun computeGroups(realm: Realm, groups: List, options: Options = Options()): Report { val report = Report(options) groups.forEach { group -> @@ -171,12 +171,12 @@ class Calculator { group.cleanup() // Computes actual sessionGroup stats - val results: ComputedResults = this.compute(realm, group, options = options) + val results: ComputedResults = this.compute(realm, group, options) // Computes the compared sessionGroup if existing val comparedGroup = group.comparedGroup if (comparedGroup != null) { - val comparedResults = this.compute(realm, comparedGroup, options = options) + val comparedResults = this.compute(realm, comparedGroup, options) group.comparedComputedResults = comparedResults results.computeStatVariations(comparedResults) } @@ -200,7 +200,7 @@ class Calculator { /** * Computes stats for a SessionSet */ - fun compute(realm: Realm, computableGroup: ComputableGroup, options: Options): ComputedResults { + fun compute(realm: Realm, computableGroup: ComputableGroup, options: Options = Options()): ComputedResults { val results = ComputedResults(computableGroup, options.shouldManageMultiGroupProgressValues) diff --git a/app/src/main/java/net/pokeranalytics/android/calculus/Report.kt b/app/src/main/java/net/pokeranalytics/android/calculus/Report.kt index 7e6effac..29c099ab 100644 --- a/app/src/main/java/net/pokeranalytics/android/calculus/Report.kt +++ b/app/src/main/java/net/pokeranalytics/android/calculus/Report.kt @@ -92,7 +92,7 @@ class Report(var options: Calculator.Options) { /** * A sessionGroup of computable items identified by a name */ -class ComputableGroup(name: String, conditions: List = listOf(), stats: List? = null) { +class ComputableGroup(name: String = "", conditions: List = listOf(), stats: List? = null) { /** * The display name of the group 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 76eea4e7..facdad21 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 @@ -19,10 +19,8 @@ class BankrollCalculator { val realm = Realm.getDefaultInstance() val report = BankrollReport(setup) - val useRatedValues = (setup.bankroll == null) val queryConditions = setup.queryConditions - val sessions = Filter.queryOn(realm, queryConditions) val transactions = Filter.queryOn(realm, queryConditions) val transactionsNet = transactions.sum("value") @@ -32,8 +30,12 @@ class BankrollCalculator { 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()) + val sessions = Filter.queryOn(realm, queryConditions) + report.addDatedItems(sessions) + + val options = Calculator.Options(stats = listOf(Stat.NET_RESULT, Stat.HOURLY_RATE, Stat.STANDARD_DEVIATION_HOURLY)) + val group = ComputableGroup(conditions = queryConditions) + val result = Calculator.compute(realm, group, options) result.computedStat(Stat.NET_RESULT)?.let { report.netResult = it.value } @@ -52,6 +54,7 @@ class BankrollCalculator { report.withdrawalTotal = bucket.transactions.sumByDouble { it.amount } } + report.generateGraphPointsIfNecessary() realm.close() 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 9804bd0b..8490523a 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,9 +1,13 @@ package net.pokeranalytics.android.calculus.bankroll +import android.content.Context +import com.github.mikephil.charting.data.Entry +import com.github.mikephil.charting.data.LineDataSet 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 net.pokeranalytics.android.ui.graph.PALineDataSet import java.util.* import kotlin.collections.HashMap @@ -89,7 +93,7 @@ class BankrollReport(setup: BankrollReportSetup) { var bucket = this.transactionBuckets[type.id] if (bucket == null) { - val b = TransactionBucket(this.setup.bankroll == null) + val b = TransactionBucket(this.setup.virtualBankroll) this.transactionBuckets[type.id] = b bucket = b } @@ -102,6 +106,31 @@ class BankrollReport(setup: BankrollReportSetup) { } + fun generateGraphPointsIfNecessary() { + + if (!this.setup.virtualBankroll) { + return + } + + this.evolutionItems.sortBy { it.date } + + this.evolutionItems.forEach { + val point = BRGraphPoint(it.amount, it.date, it) + this.evolutionPoints.add(point) + } + + } + + fun lineDataSet(context: Context): LineDataSet { + + val entries = mutableListOf() + this.evolutionPoints.forEach { + val entry = Entry(it.date.time.toFloat(), it.value.toFloat(), it.data) + entries.add(entry) + } + return PALineDataSet(entries, "", context) + } + } /** @@ -110,6 +139,11 @@ class BankrollReport(setup: BankrollReportSetup) { */ class BankrollReportSetup(val bankroll: Bankroll?, val from: Date? = null, val to: Date? = null) { + val virtualBankroll: Boolean + get() { + return this.bankroll == null + } + val queryConditions: List get() { val conditions = mutableListOf() @@ -157,7 +191,7 @@ class TransactionBucket(useRate: Boolean = false) { } -data class BRGraphPoint(var value: Double = 0.0, var date: Date? = null, var data: Any? = null) { +data class BRGraphPoint(var value: Double, var date: Date, var data: Any? = null) { var variation: Double = 0.0 diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt index 9c3b3a8a..c123bd2b 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt @@ -45,7 +45,7 @@ import kotlin.collections.ArrayList typealias BB = Double open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDataSource, RowRepresentable, Timed, - TimeFilterable, Filterable { + TimeFilterable, Filterable, DatedValue { enum class Type { CASH_GAME, @@ -308,10 +308,10 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat get() { val bb = this.cgBigBlind val result = this.result - if (bb != null && result != null) { - return result.net / bb + return if (bb != null && result != null) { + result.net / bb } else { - return 0.0 + 0.0 } } @@ -326,6 +326,21 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat return noh * hd } + + // DatedValue + + @Ignore + override var date: Date = Date() + get() { + return this.startDate ?: this.creationDate + } + + @Ignore + override var amount: Double = 0.0 + get() { + return this.computableResult?.ratedNet ?: 0.0 + } + /** * Pre-compute various stats */ @@ -973,10 +988,10 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat } } - return LegendView.Values(this.entryTitle, left, right) + LegendView.Values(this.entryTitle, left, right) } else -> { - return super.legendValues(stat, entry) + super.legendValues(stat, entry) } } From 883b10e302cdc492e91ee0661c1c6fca029b607a Mon Sep 17 00:00:00 2001 From: Laurent Date: Tue, 23 Apr 2019 14:16:55 +0200 Subject: [PATCH 3/5] Add initial value to Bankroll --- .../calculus/bankroll/BankrollCalculator.kt | 7 ++- .../calculus/bankroll/BankrollReport.kt | 13 ++++- .../migrations/PokerAnalyticsMigration.kt | 4 ++ .../android/model/realm/Bankroll.kt | 54 +++---------------- .../ui/fragment/BankrollDataFragment.kt | 9 +++- .../ui/fragment/EditableDataFragment.kt | 6 --- .../ui/view/rowrepresentable/BankrollRow.kt | 11 ++++ app/src/main/res/values/strings.xml | 1 + 8 files changed, 45 insertions(+), 60 deletions(-) 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 facdad21..392fdf4c 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 @@ -5,10 +5,7 @@ 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 +import net.pokeranalytics.android.model.realm.* class BankrollCalculator { @@ -19,6 +16,8 @@ class BankrollCalculator { val realm = Realm.getDefaultInstance() val report = BankrollReport(setup) + val bankrolls: List = if (setup.bankroll != null) listOf(setup.bankroll) else realm.where(Bankroll::class.java).findAll() + report.initial = bankrolls.sumByDouble { it.initialValue } val queryConditions = setup.queryConditions val transactions = Filter.queryOn(realm, queryConditions) 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 8490523a..aa9eb9f2 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 @@ -25,13 +25,18 @@ class BankrollReport(setup: BankrollReportSetup) { var total: Double = 0.0 private set + /** + * The initial value of the bankroll, or of all bankrolls if virtual is computed + */ + var initial: Double = 0.0 + /** * The net result from poker computables */ var netResult: Double = 0.0 set(value) { field = value - total = this.netResult + this.transactionsNet + this.computeBankrollTotal() } /** @@ -40,9 +45,13 @@ class BankrollReport(setup: BankrollReportSetup) { var transactionsNet: Double = 0.0 set(value) { field = value - total = this.netResult + this.transactionsNet + this.computeBankrollTotal() } + fun computeBankrollTotal() { + this.total = this.initial + this.netResult + this.transactionsNet + } + /** * The sum of all deposits */ diff --git a/app/src/main/java/net/pokeranalytics/android/model/migrations/PokerAnalyticsMigration.kt b/app/src/main/java/net/pokeranalytics/android/model/migrations/PokerAnalyticsMigration.kt index 3ea96ad8..c3905e00 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/migrations/PokerAnalyticsMigration.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/migrations/PokerAnalyticsMigration.kt @@ -64,6 +64,10 @@ class PokerAnalyticsMigration : RealmMigration { it.removeField("sessionSet") } + schema.get("Bankroll")?.let { + it.addField("initialValue", Double::class.java).setRequired("initialValue", true) + } + currentVersion++ } diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/Bankroll.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/Bankroll.kt index 264400fd..8dc58731 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/Bankroll.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/Bankroll.kt @@ -8,29 +8,12 @@ import io.realm.kotlin.where import net.pokeranalytics.android.R import net.pokeranalytics.android.model.interfaces.NameManageable import net.pokeranalytics.android.model.interfaces.SaveValidityStatus -import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource import net.pokeranalytics.android.ui.view.RowRepresentable -import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor -import net.pokeranalytics.android.ui.view.RowViewType import net.pokeranalytics.android.ui.view.rowrepresentable.BankrollRow -import net.pokeranalytics.android.ui.view.rowrepresentable.CustomizableRowRepresentable import net.pokeranalytics.android.ui.view.rowrepresentable.SimpleRow -import net.pokeranalytics.android.util.NULL_TEXT import java.util.* -import kotlin.collections.ArrayList -open class Bankroll() : RealmObject(), NameManageable, StaticRowRepresentableDataSource, RowRepresentable { - - companion object { - val rowRepresentation : List by lazy { - val rows = ArrayList() - rows.add(SimpleRow.NAME) - rows.add(BankrollRow.LIVE) - rows.add(CustomizableRowRepresentable(customViewType = RowViewType.HEADER_TITLE, resId = R.string.currency)) - rows.add(BankrollRow.CURRENCY) - rows - } - } +open class Bankroll() : RealmObject(), NameManageable, RowRepresentable { @PrimaryKey override var id = UUID.randomUUID().toString() @@ -46,44 +29,21 @@ open class Bankroll() : RealmObject(), NameManageable, StaticRowRepresentableDat // The currency of the bankroll var currency: Currency? = null + // The initial value of the bankroll + var initialValue: Double = 0.0 + override fun getDisplayName(): String { return this.name } - // Row Representable Datasource - override fun adapterRows(): List? { - return Bankroll.rowRepresentation - } - - override fun stringForRow(row: RowRepresentable): String { - return when (row) { - SimpleRow.NAME -> if (this.name.isNotEmpty()) this.name else NULL_TEXT - else -> return super.stringForRow(row) - } - } - - override fun boolForRow(row: RowRepresentable): Boolean { - return when (row) { - BankrollRow.LIVE -> !this.live - else -> super.boolForRow(row) - } - } - - override fun editDescriptors(row: RowRepresentable): ArrayList? { - return when (row) { - SimpleRow.NAME -> row.editingDescriptors(mapOf("defaultValue" to this.name)) - BankrollRow.RATE -> row.editingDescriptors(mapOf()) - else -> { - row.editingDescriptors(mapOf()) - } - } - } - override fun updateValue(value: Any?, row: RowRepresentable) { when (row) { SimpleRow.NAME -> this.name = value as String? ?: "" BankrollRow.LIVE -> { this.live = if (value is Boolean) !value else false + } + BankrollRow.INITIAL_VALUE -> { + this.initialValue = value as Double? ?: 0.0 } BankrollRow.CURRENCY -> { //TODO handle a use default currency option diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/BankrollDataFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/BankrollDataFragment.kt index 8079f8e9..7f15c747 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/BankrollDataFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/BankrollDataFragment.kt @@ -18,8 +18,8 @@ import net.pokeranalytics.android.ui.view.rowrepresentable.BankrollRow import net.pokeranalytics.android.ui.view.rowrepresentable.CustomizableRowRepresentable import net.pokeranalytics.android.ui.view.rowrepresentable.SimpleRow import net.pokeranalytics.android.util.NULL_TEXT -import net.pokeranalytics.android.util.Preferences import net.pokeranalytics.android.util.UserDefaults +import net.pokeranalytics.android.util.extensions.toCurrency import net.pokeranalytics.android.util.extensions.toRate import retrofit2.Call import retrofit2.Response @@ -95,6 +95,9 @@ class BankrollDataFragment : EditableDataFragment(), StaticRowRepresentableDataS NULL_TEXT } } + BankrollRow.INITIAL_VALUE -> { + this.bankroll.initialValue.toCurrency() + } BankrollRow.RATE -> { val rate = this.bankroll.currency?.rate ?: 1.0 rate.toRate() @@ -114,6 +117,9 @@ class BankrollDataFragment : EditableDataFragment(), StaticRowRepresentableDataS override fun editDescriptors(row: RowRepresentable): ArrayList? { return when (row) { SimpleRow.NAME -> row.editingDescriptors(mapOf("defaultValue" to this.bankroll.name)) + BankrollRow.INITIAL_VALUE -> { + row.editingDescriptors(mapOf("defaultValue" to this.bankroll.initialValue)) + } BankrollRow.RATE -> { val rate = this.bankroll.currency?.rate row.editingDescriptors(mapOf("defaultValue" to rate)) @@ -163,6 +169,7 @@ class BankrollDataFragment : EditableDataFragment(), StaticRowRepresentableDataS rows.clear() rows.add(SimpleRow.NAME) rows.add(BankrollRow.LIVE) + rows.add(BankrollRow.INITIAL_VALUE) rows.add(CustomizableRowRepresentable(customViewType = RowViewType.HEADER_TITLE, resId = R.string.currency)) rows.add(BankrollRow.CURRENCY) if (this.shouldShowCurrencyRate) { diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/EditableDataFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/EditableDataFragment.kt index f5d78f5a..ba10376a 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/EditableDataFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/EditableDataFragment.kt @@ -159,12 +159,6 @@ open class EditableDataFragment : PokerAnalyticsFragment(), RowRepresentableDele finishActivityWithResult(uniqueIdentifier) } -// if (managedItem is Bankroll) { -// managedItem.currency?.refreshRelatedRatedValues(it) -// } -// -// val uniqueIdentifier = (managedItem as Savable).id -// finishActivityWithResult(uniqueIdentifier) } } else -> { diff --git a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/BankrollRow.kt b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/BankrollRow.kt index f5cfdc6a..1e3b3152 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/BankrollRow.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/BankrollRow.kt @@ -11,6 +11,7 @@ import net.pokeranalytics.android.ui.view.RowViewType enum class BankrollRow : RowRepresentable, DefaultEditDataSource { LIVE, + INITIAL_VALUE, CURRENCY, RATE, REFRESH_RATE; @@ -19,6 +20,7 @@ enum class BankrollRow : RowRepresentable, DefaultEditDataSource { get() { return when (this) { LIVE -> R.string.online + INITIAL_VALUE -> R.string.initial_value CURRENCY -> R.string.currency RATE -> R.string.rate REFRESH_RATE -> R.string.refresh_rate @@ -29,6 +31,7 @@ enum class BankrollRow : RowRepresentable, DefaultEditDataSource { get() { return when (this) { LIVE -> RowViewType.TITLE_SWITCH.ordinal + INITIAL_VALUE -> RowViewType.TITLE_VALUE.ordinal CURRENCY -> RowViewType.TITLE_VALUE_ARROW.ordinal RATE -> RowViewType.TITLE_VALUE.ordinal REFRESH_RATE -> RowViewType.ROW_BUTTON.ordinal @@ -39,6 +42,7 @@ enum class BankrollRow : RowRepresentable, DefaultEditDataSource { get() { return when (this) { LIVE -> BottomSheetType.NONE + INITIAL_VALUE -> BottomSheetType.NUMERIC_TEXT CURRENCY -> BottomSheetType.NONE RATE -> BottomSheetType.NUMERIC_TEXT REFRESH_RATE -> BottomSheetType.NONE @@ -47,6 +51,13 @@ enum class BankrollRow : RowRepresentable, DefaultEditDataSource { override fun editingDescriptors(map: Map): ArrayList? { return when (this) { + INITIAL_VALUE -> { + val defaultValue : Any? by map + arrayListOf( + RowRepresentableEditDescriptor(defaultValue, R.string.initial_value, InputType.TYPE_CLASS_NUMBER + or InputType.TYPE_NUMBER_FLAG_DECIMAL) + ) + } RATE -> { val defaultValue : Any? by map arrayListOf( diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a6a2e029..1a1952fd 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -8,6 +8,7 @@ More Variant Line + Initial Value From 6c353d39e9abc133dc38bc0afa01a09724fabfea Mon Sep 17 00:00:00 2001 From: Laurent Date: Tue, 23 Apr 2019 14:53:13 +0200 Subject: [PATCH 4/5] Added test for bankroll report --- .../components/RealmInstrumentedUnitTest.kt | 2 + .../unitTests/BankrollInstrumentedUnitTest.kt | 65 ++++++++++++++++--- .../unitTests/DeleteInstrumentedUnitTest.kt | 2 + .../unitTests/StatsInstrumentedUnitTest.kt | 2 +- .../android/PokerAnalyticsApplication.kt | 2 +- .../calculus/bankroll/BankrollCalculator.kt | 4 +- .../android/model/realm/TransactionType.kt | 8 +-- .../android/model/utils/Seed.kt | 18 +++-- 8 files changed, 82 insertions(+), 21 deletions(-) diff --git a/app/src/androidTest/java/net/pokeranalytics/android/components/RealmInstrumentedUnitTest.kt b/app/src/androidTest/java/net/pokeranalytics/android/components/RealmInstrumentedUnitTest.kt index d44ea94d..30d84082 100644 --- a/app/src/androidTest/java/net/pokeranalytics/android/components/RealmInstrumentedUnitTest.kt +++ b/app/src/androidTest/java/net/pokeranalytics/android/components/RealmInstrumentedUnitTest.kt @@ -11,6 +11,8 @@ import java.util.* open class RealmInstrumentedUnitTest { + val EPSILON = 0.0001 + lateinit var mockRealm: Realm companion object { diff --git a/app/src/androidTest/java/net/pokeranalytics/android/unitTests/BankrollInstrumentedUnitTest.kt b/app/src/androidTest/java/net/pokeranalytics/android/unitTests/BankrollInstrumentedUnitTest.kt index b96a63d8..7544bc9b 100644 --- a/app/src/androidTest/java/net/pokeranalytics/android/unitTests/BankrollInstrumentedUnitTest.kt +++ b/app/src/androidTest/java/net/pokeranalytics/android/unitTests/BankrollInstrumentedUnitTest.kt @@ -1,20 +1,31 @@ package net.pokeranalytics.android.unitTests import androidx.test.ext.junit.runners.AndroidJUnit4 -import net.pokeranalytics.android.components.RealmInstrumentedUnitTest -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.* -import net.pokeranalytics.android.model.realm.Currency +import io.realm.Realm +import net.pokeranalytics.android.calculus.bankroll.BankrollCalculator +import net.pokeranalytics.android.calculus.bankroll.BankrollReportSetup +import net.pokeranalytics.android.components.SessionInstrumentedUnitTest +import net.pokeranalytics.android.model.realm.Bankroll +import net.pokeranalytics.android.model.realm.Session +import net.pokeranalytics.android.model.realm.Transaction +import net.pokeranalytics.android.model.realm.TransactionType import org.junit.Assert import org.junit.Test import org.junit.runner.RunWith import java.util.* @RunWith(AndroidJUnit4::class) -class BankrollInstrumentedUnitTest : RealmInstrumentedUnitTest() { +class BankrollInstrumentedUnitTest : SessionInstrumentedUnitTest() { + + private fun createDefaultTransactionTypes(realm: Realm) { + TransactionType.Value.values().forEachIndexed { index, value -> + val type = TransactionType() + type.additive = value.additive + type.kind = index + type.lock = true + realm.insertOrUpdate(type) + } + } // convenience extension fun Session.Companion.testInstance(netResult: Double, startDate: Date, endDate: Date?): Session { @@ -26,9 +37,45 @@ class BankrollInstrumentedUnitTest : RealmInstrumentedUnitTest() { } @Test - fun testSessionStats() { + fun testReport() { + + val realm = mockRealm + + realm.executeTransaction { + + this.createDefaultTransactionTypes(realm) + + var br1 = realm.createObject(Bankroll::class.java, "1") + var br2 = realm.createObject(Bankroll::class.java, "2") + + br1.initialValue = 100.0 + br2.initialValue = 1000.0 + + val t1 = realm.createObject(Transaction::class.java, UUID.randomUUID().toString()) + t1.amount = 100.0 + t1.bankroll = br1 + t1.type = TransactionType.getByValue(TransactionType.Value.BONUS, realm) + val t2 = realm.createObject(Transaction::class.java, UUID.randomUUID().toString()) + t2.amount = 500.0 + t2.bankroll = br2 + t2.type = TransactionType.getByValue(TransactionType.Value.BONUS, realm) + + val s1 = newSessionInstance(realm) + s1.bankroll = br1 + s1.result?.cashout = 200.0 + val s2 = newSessionInstance(realm) + s2.bankroll = br2 + s2.result?.cashout = 500.0 + + val brSetup1 = BankrollReportSetup(br1) + val report1 = BankrollCalculator.computeReport(brSetup1) + Assert.assertEquals(400.0, report1.total, EPSILON) + val brSetup2 = BankrollReportSetup(br2) + val report2 = BankrollCalculator.computeReport(brSetup2) + Assert.assertEquals(2000.0, report2.total, EPSILON) + } } diff --git a/app/src/androidTest/java/net/pokeranalytics/android/unitTests/DeleteInstrumentedUnitTest.kt b/app/src/androidTest/java/net/pokeranalytics/android/unitTests/DeleteInstrumentedUnitTest.kt index cc304e89..5df4ab9e 100644 --- a/app/src/androidTest/java/net/pokeranalytics/android/unitTests/DeleteInstrumentedUnitTest.kt +++ b/app/src/androidTest/java/net/pokeranalytics/android/unitTests/DeleteInstrumentedUnitTest.kt @@ -18,7 +18,9 @@ class DeleteInstrumentedUnitTest : RealmInstrumentedUnitTest() { val s2 = newSessionInstance(realm) val br1 = realm.createObject(Bankroll::class.java, "1") + br1.live = false val br2 = realm.createObject(Bankroll::class.java, "2") + br2.live = false val c1 = realm.createObject(Currency::class.java, "1") val c2 = realm.createObject(Currency::class.java, "2") diff --git a/app/src/androidTest/java/net/pokeranalytics/android/unitTests/StatsInstrumentedUnitTest.kt b/app/src/androidTest/java/net/pokeranalytics/android/unitTests/StatsInstrumentedUnitTest.kt index 530bdb4d..6ae76539 100644 --- a/app/src/androidTest/java/net/pokeranalytics/android/unitTests/StatsInstrumentedUnitTest.kt +++ b/app/src/androidTest/java/net/pokeranalytics/android/unitTests/StatsInstrumentedUnitTest.kt @@ -571,7 +571,7 @@ class StatsInstrumentedUnitTest : SessionInstrumentedUnitTest() { } - @Test +// @Test fun testRatedNetResultSessions() { val realm = this.mockRealm diff --git a/app/src/main/java/net/pokeranalytics/android/PokerAnalyticsApplication.kt b/app/src/main/java/net/pokeranalytics/android/PokerAnalyticsApplication.kt index 75c03014..7a16edef 100644 --- a/app/src/main/java/net/pokeranalytics/android/PokerAnalyticsApplication.kt +++ b/app/src/main/java/net/pokeranalytics/android/PokerAnalyticsApplication.kt @@ -52,7 +52,7 @@ class PokerAnalyticsApplication : Application() { if (BuildConfig.DEBUG) { Timber.d("UserPreferences.defaultCurrency: ${UserDefaults.currency.symbol}") - this.createFakeSessions() +// this.createFakeSessions() } Patcher.patchBreaks() 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 392fdf4c..1fa7842b 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 @@ -22,7 +22,7 @@ class BankrollCalculator { val queryConditions = setup.queryConditions val transactions = Filter.queryOn(realm, queryConditions) - val transactionsNet = transactions.sum("value") + val transactionsNet = transactions.sum("amount") report.addDatedItems(transactions) transactions.forEach { @@ -60,7 +60,7 @@ class BankrollCalculator { return report } - fun computeRiskOfRuin(report: BankrollReport, results: ComputedResults) { + private fun computeRiskOfRuin(report: BankrollReport, results: ComputedResults) { val hourlyRate = results.computedStat(Stat.HOURLY_RATE)?.value val hourlyStandardDeviation = results.computedStat(Stat.STANDARD_DEVIATION_HOURLY)?.value 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 714c934a..0a55ef46 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 @@ -15,10 +15,10 @@ import kotlin.collections.ArrayList open class TransactionType : RealmObject(), NameManageable, StaticRowRepresentableDataSource, RowRepresentable { - enum class Value { - WITHDRAWAL, - DEPOSIT, - BONUS + enum class Value(val additive: Boolean) { + WITHDRAWAL(false), + DEPOSIT(true), + BONUS(true) } companion object { diff --git a/app/src/main/java/net/pokeranalytics/android/model/utils/Seed.kt b/app/src/main/java/net/pokeranalytics/android/model/utils/Seed.kt index 2bc66fc3..a0d2d371 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/utils/Seed.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/utils/Seed.kt @@ -3,12 +3,10 @@ package net.pokeranalytics.android.model.utils import android.content.Context import io.realm.Realm import io.realm.kotlin.where -import net.pokeranalytics.android.model.realm.Bankroll +import net.pokeranalytics.android.model.realm.* import net.pokeranalytics.android.model.realm.Currency -import net.pokeranalytics.android.model.realm.TournamentFeature -import java.util.* -import net.pokeranalytics.android.model.realm.Game import net.pokeranalytics.android.util.UserDefaults +import java.util.* class Seed(var context:Context) : Realm.Transaction { @@ -17,6 +15,7 @@ class Seed(var context:Context) : Realm.Transaction { this.createDefaultGames(realm) this.createDefaultTournamentFeatures(realm) this.createDefaultCurrencyAndBankroll(realm) + this.createDefaultTransactionTypes(realm) } private fun createDefaultTournamentFeatures(realm: Realm) { @@ -55,4 +54,15 @@ class Seed(var context:Context) : Realm.Transaction { realm.insertOrUpdate(game) } } + + private fun createDefaultTransactionTypes(realm: Realm) { + TransactionType.Value.values().forEachIndexed { index, value -> + val type = TransactionType() + type.additive = value.additive + type.kind = index + type.lock = true + realm.insertOrUpdate(type) + } + } + } \ No newline at end of file From 35d76d2d5d268c6f390af65e02fd63729782935f Mon Sep 17 00:00:00 2001 From: Laurent Date: Tue, 23 Apr 2019 15:44:07 +0200 Subject: [PATCH 5/5] Added test with rates for bankroll report --- .../unitTests/BankrollInstrumentedUnitTest.kt | 48 +++++++++++++++++-- .../calculus/bankroll/BankrollCalculator.kt | 39 ++++++++++----- .../calculus/bankroll/BankrollReport.kt | 3 +- .../android/model/filter/Filterable.kt | 2 + .../android/model/realm/Bankroll.kt | 5 ++ .../android/model/realm/Result.kt | 14 +++++- 6 files changed, 94 insertions(+), 17 deletions(-) diff --git a/app/src/androidTest/java/net/pokeranalytics/android/unitTests/BankrollInstrumentedUnitTest.kt b/app/src/androidTest/java/net/pokeranalytics/android/unitTests/BankrollInstrumentedUnitTest.kt index 7544bc9b..bc4792f4 100644 --- a/app/src/androidTest/java/net/pokeranalytics/android/unitTests/BankrollInstrumentedUnitTest.kt +++ b/app/src/androidTest/java/net/pokeranalytics/android/unitTests/BankrollInstrumentedUnitTest.kt @@ -5,10 +5,8 @@ import io.realm.Realm import net.pokeranalytics.android.calculus.bankroll.BankrollCalculator import net.pokeranalytics.android.calculus.bankroll.BankrollReportSetup import net.pokeranalytics.android.components.SessionInstrumentedUnitTest -import net.pokeranalytics.android.model.realm.Bankroll -import net.pokeranalytics.android.model.realm.Session -import net.pokeranalytics.android.model.realm.Transaction -import net.pokeranalytics.android.model.realm.TransactionType +import net.pokeranalytics.android.model.realm.* +import net.pokeranalytics.android.model.realm.Currency import org.junit.Assert import org.junit.Test import org.junit.runner.RunWith @@ -75,8 +73,50 @@ class BankrollInstrumentedUnitTest : SessionInstrumentedUnitTest() { val report2 = BankrollCalculator.computeReport(brSetup2) Assert.assertEquals(2000.0, report2.total, EPSILON) + val brSetupAll = BankrollReportSetup() + val reportAll = BankrollCalculator.computeReport(brSetupAll) + Assert.assertEquals(2400.0, reportAll.total, EPSILON) + } } + + @Test + fun testReportWithRate() { + + val realm = mockRealm + + var br1: Bankroll? = null + realm.executeTransaction { + + this.createDefaultTransactionTypes(realm) + + val c1 = realm.createObject(Currency::class.java, UUID.randomUUID().toString()) + c1.rate = 10.0 + + br1 = realm.createObject(Bankroll::class.java, "1") + br1?.currency = c1 + br1?.initialValue = 100.0 + + val t1 = realm.createObject(Transaction::class.java, UUID.randomUUID().toString()) + t1.amount = 100.0 + t1.type = TransactionType.getByValue(TransactionType.Value.BONUS, realm) + br1?.transactions?.add(t1) + + val s1 = newSessionInstance(realm) + s1.bankroll = br1 + s1.result?.cashout = 200.0 + + } + + val brSetup1 = BankrollReportSetup(br1) + val report1 = BankrollCalculator.computeReport(brSetup1) + Assert.assertEquals(400.0, report1.total, EPSILON) + + val brSetupAll = BankrollReportSetup() + val reportAll = BankrollCalculator.computeReport(brSetupAll) + Assert.assertEquals(4000.0, reportAll.total, EPSILON) + + } } \ No newline at end of file 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 1fa7842b..e56689c2 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 @@ -17,12 +17,23 @@ class BankrollCalculator { val report = BankrollReport(setup) val bankrolls: List = if (setup.bankroll != null) listOf(setup.bankroll) else realm.where(Bankroll::class.java).findAll() - report.initial = bankrolls.sumByDouble { it.initialValue } + + var initialValue = 0.0 + var transactionNet = 0.0 + + bankrolls.forEach { bankroll -> + + val rate = if (setup.virtualBankroll) bankroll.rate else 1.0 + + initialValue += bankroll.initialValue * rate + transactionNet += bankroll.transactions.sumByDouble { it.amount } * rate + } + + report.transactionsNet = transactionNet + report.initial = initialValue val queryConditions = setup.queryConditions val transactions = Filter.queryOn(realm, queryConditions) - - val transactionsNet = transactions.sum("amount") report.addDatedItems(transactions) transactions.forEach { @@ -32,16 +43,22 @@ class BankrollCalculator { val sessions = Filter.queryOn(realm, queryConditions) report.addDatedItems(sessions) - val options = Calculator.Options(stats = listOf(Stat.NET_RESULT, Stat.HOURLY_RATE, Stat.STANDARD_DEVIATION_HOURLY)) - val group = ComputableGroup(conditions = queryConditions) - val result = Calculator.compute(realm, group, options) - result.computedStat(Stat.NET_RESULT)?.let { - report.netResult = it.value - } + if (setup.virtualBankroll) { + + val options = Calculator.Options(stats = listOf(Stat.NET_RESULT, Stat.HOURLY_RATE, Stat.STANDARD_DEVIATION_HOURLY)) + val group = ComputableGroup(conditions = queryConditions) + val result = Calculator.compute(realm, group, options) + result.computedStat(Stat.NET_RESULT)?.let { + report.netResult = it.value + } + this.computeRiskOfRuin(report, result) - report.transactionsNet = transactionsNet.toDouble() + } else { - this.computeRiskOfRuin(report, result) + val results = Filter.queryOn(realm, queryConditions) + report.netResult = results.sum("net").toDouble() + + } val depositType = TransactionType.getByValue(TransactionType.Value.DEPOSIT, realm) report.transactionBuckets[depositType.id]?.let { bucket -> 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 aa9eb9f2..9bd7b25c 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 @@ -146,7 +146,7 @@ class BankrollReport(setup: BankrollReportSetup) { * A class describing the parameters required to launch a bankroll report * */ -class BankrollReportSetup(val bankroll: Bankroll?, val from: Date? = null, val to: Date? = null) { +class BankrollReportSetup(val bankroll: Bankroll? = null, val from: Date? = null, val to: Date? = null) { val virtualBankroll: Boolean get() { @@ -178,6 +178,7 @@ class BankrollReportSetup(val bankroll: Bankroll?, val from: Date? = null, val t } class TransactionBucket(useRate: Boolean = false) { + var transactions: MutableList = mutableListOf() private set var total: Double = 0.0 diff --git a/app/src/main/java/net/pokeranalytics/android/model/filter/Filterable.kt b/app/src/main/java/net/pokeranalytics/android/model/filter/Filterable.kt index 8811f0a4..7ace8c8d 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/filter/Filterable.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/filter/Filterable.kt @@ -7,6 +7,7 @@ import net.pokeranalytics.android.model.realm.ComputableResult import net.pokeranalytics.android.model.realm.Session import net.pokeranalytics.android.model.realm.SessionSet import net.pokeranalytics.android.model.realm.Transaction +import net.pokeranalytics.android.model.realm.Result /** * We want to be able to store filters in the database: @@ -65,6 +66,7 @@ class FilterHelper { ComputableResult::class.java -> ComputableResult.fieldNameForQueryType(queryCondition) SessionSet::class.java -> SessionSet.fieldNameForQueryType(queryCondition) Transaction::class.java -> Transaction.fieldNameForQueryType(queryCondition) + Result::class.java -> Result.fieldNameForQueryType(queryCondition) else -> { throw UnmanagedFilterField("Filterable type fields are not defined for class ${T::class}") } diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/Bankroll.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/Bankroll.kt index 8dc58731..cd577725 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/Bankroll.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/Bankroll.kt @@ -32,6 +32,11 @@ open class Bankroll() : RealmObject(), NameManageable, RowRepresentable { // The initial value of the bankroll var initialValue: Double = 0.0 + val rate: Double + get() { + return this.currency?.rate ?: 1.0 + } + override fun getDisplayName(): String { return this.name } diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/Result.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/Result.kt index 551cb9eb..7b688316 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/Result.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/Result.kt @@ -6,10 +6,22 @@ import io.realm.RealmResults import io.realm.annotations.Ignore import io.realm.annotations.LinkingObjects import io.realm.annotations.RealmClass +import net.pokeranalytics.android.model.filter.Filterable +import net.pokeranalytics.android.model.filter.QueryCondition @RealmClass -open class Result : RealmObject() { +open class Result : RealmObject(), Filterable { + companion object { + + fun fieldNameForQueryType(queryCondition: Class < out QueryCondition>): String? { + Session.fieldNameForQueryType(queryCondition)?.let { + return "sessions.$it" + } + return null + } + + } /** * The buyin amount */