You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
poker-analytics/app/src/main/java/net/pokeranalytics/android/calculus/Stat.kt

278 lines
7.7 KiB

package net.pokeranalytics.android.calculus
import android.content.Context
import net.pokeranalytics.android.R
import net.pokeranalytics.android.exceptions.FormattingException
import net.pokeranalytics.android.model.interfaces.Timed
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType
import net.pokeranalytics.android.util.NULL_TEXT
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) {
}
class ObjectIdentifier(var id: String, var clazz: Class<out Timed>) {
}
/**
* An enum representing all the types of Session statistics
*/
enum class Stat : RowRepresentable {
NET_RESULT,
BB_NET_RESULT,
HOURLY_RATE,
AVERAGE,
NUMBER_OF_SETS,
NUMBER_OF_GAMES,
HOURLY_DURATION,
AVERAGE_HOURLY_DURATION,
NET_BB_PER_100_HANDS,
HOURLY_RATE_BB,
AVERAGE_NET_BB,
WIN_RATIO,
AVERAGE_BUYIN,
ROI,
STANDARD_DEVIATION,
STANDARD_DEVIATION_HOURLY,
STANDARD_DEVIATION_BB_PER_100_HANDS,
HANDS_PLAYED,
LOCATIONS_PLAYED,
LONGEST_STREAKS,
MAXIMUM_NETRESULT,
MINIMUM_NETRESULT,
MAXIMUM_DURATION,
DAYS_PLAYED,
WINNING_SESSION_COUNT,
BB_SESSION_COUNT,
TOTAL_BUYIN,
;
companion object {
fun returnOnInvestment(netResult: Double, buyin: Double): Double? {
if (buyin == 0.0) {
return null
}
return netResult / buyin
}
fun netBBPer100Hands(netBB: Double, numberOfHands: Double): Double? {
if (numberOfHands == 0.0) {
return null
}
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?
get() {
return when (this) {
NET_RESULT -> R.string.net_result
BB_NET_RESULT -> R.string.total_net_result_bb_
HOURLY_RATE -> R.string.average_hour_rate
AVERAGE -> R.string.average
NUMBER_OF_SETS -> R.string.number_of_sessions
NUMBER_OF_GAMES -> R.string.number_of_records
HOURLY_DURATION -> R.string.duration
AVERAGE_HOURLY_DURATION -> R.string.average_hours_played
NET_BB_PER_100_HANDS -> R.string.net_result_bb_per_100_hands
HOURLY_RATE_BB -> R.string.average_hour_rate_bb_
AVERAGE_NET_BB -> R.string.average_net_result_bb_
WIN_RATIO -> R.string.win_ratio
AVERAGE_BUYIN -> R.string.average_buyin
ROI -> R.string.tournament_roi
STANDARD_DEVIATION -> R.string.standard_deviation
STANDARD_DEVIATION_HOURLY -> R.string.standard_deviation_per_hour
STANDARD_DEVIATION_BB_PER_100_HANDS -> R.string.standard_deviation_bb_per_100_hands
HANDS_PLAYED -> R.string.number_of_hands
LOCATIONS_PLAYED -> R.string.locations_played
LONGEST_STREAKS -> R.string.longest_streaks
MAXIMUM_NETRESULT -> R.string.max_net_result
MINIMUM_NETRESULT -> R.string.min_net_result
MAXIMUM_DURATION -> R.string.longest_session
DAYS_PLAYED -> R.string.days_played
else -> throw IllegalStateException("Stat ${this.name} name required but undefined")
}
}
/**
* Formats the value of the stat to be suitable for display
*/
fun format(value: Double, secondValue: Double? = null, currency: Currency? = null): TextFormat {
if (value.isNaN()) {
return TextFormat(NULL_TEXT, R.color.white)
}
when (this) {
// Amounts + red/green
NET_RESULT, HOURLY_RATE, AVERAGE, MAXIMUM_NETRESULT, MINIMUM_NETRESULT -> {
val color = if (value >= this.threshold) R.color.green else R.color.red
return TextFormat(value.toCurrency(currency), color)
}
// Red/green numericValues
HOURLY_RATE_BB, AVERAGE_NET_BB, NET_BB_PER_100_HANDS -> {
val color = if (value >= this.threshold) R.color.green else R.color.red
return TextFormat(value.formatted(), color)
}
// white integers
NUMBER_OF_SETS, NUMBER_OF_GAMES, HANDS_PLAYED, LOCATIONS_PLAYED, DAYS_PLAYED -> {
return TextFormat("${value.toInt()}")
} // white durations
HOURLY_DURATION, AVERAGE_HOURLY_DURATION, MAXIMUM_DURATION -> {
return TextFormat(value.formattedHourlyDuration())
} // red/green percentages
WIN_RATIO, ROI -> {
val color = if (value * 100 >= this.threshold) R.color.green else R.color.red
return TextFormat("${(value * 100).formatted()}%", color)
} // white amountsr
AVERAGE_BUYIN, STANDARD_DEVIATION, STANDARD_DEVIATION_HOURLY,
STANDARD_DEVIATION_BB_PER_100_HANDS -> {
return TextFormat(value.toCurrency(currency))
}
LONGEST_STREAKS -> {
return TextFormat("${value.toInt()}W / ${secondValue!!.toInt()}L")
}
else -> throw FormattingException("Stat formatting of ${this.name} not handled")
}
}
val threshold: Double
get() {
return when (this) {
WIN_RATIO -> 50.0
else -> 0.0
}
}
fun cumulativeLabelResId(context: Context): String {
val resId = when (this) {
AVERAGE, AVERAGE_HOURLY_DURATION, NET_BB_PER_100_HANDS,
HOURLY_RATE_BB, AVERAGE_NET_BB, ROI, HOURLY_RATE -> R.string.average
NUMBER_OF_SETS -> R.string.number_of_sessions
NUMBER_OF_GAMES -> R.string.number_of_records
NET_RESULT -> R.string.total
STANDARD_DEVIATION -> R.string.net_result
STANDARD_DEVIATION_HOURLY -> R.string.hour_rate_without_pauses
STANDARD_DEVIATION_BB_PER_100_HANDS -> R.string.net_result_bb_per_100_hands
WIN_RATIO, HOURLY_DURATION -> return this.localizedTitle(context)
else -> null
}
resId?.let {
return context.getString(it)
} ?: run {
return NULL_TEXT
}
}
val aggregationTypes: List<AggregationType>
get() {
return when (this) {
NET_RESULT -> listOf(
AggregationType.SESSION,
AggregationType.MONTH,
AggregationType.YEAR,
AggregationType.DURATION
)
NUMBER_OF_GAMES, NUMBER_OF_SETS -> listOf(AggregationType.MONTH, AggregationType.YEAR)
else -> listOf(AggregationType.SESSION, AggregationType.MONTH, AggregationType.YEAR)
}
}
val hasEvolutionGraph: Boolean
get() {
return when (this) {
HOURLY_DURATION, AVERAGE_HOURLY_DURATION,
STANDARD_DEVIATION, STANDARD_DEVIATION_HOURLY, STANDARD_DEVIATION_BB_PER_100_HANDS -> false
else -> true
}
}
val significantIndividualValue: Boolean
get() {
return when (this) {
WIN_RATIO, NUMBER_OF_SETS, NUMBER_OF_GAMES, STANDARD_DEVIATION, HOURLY_DURATION -> false
else -> true
}
}
val shouldShowNumberOfSessions: Boolean
get() {
return when (this) {
NUMBER_OF_GAMES, NUMBER_OF_SETS -> false
else -> true
}
}
val showXAxisZero: Boolean
get() {
return when (this) {
HOURLY_DURATION -> true
else -> false
}
}
val showYAxisZero: Boolean
get() {
return when (this) {
HOURLY_DURATION -> true
else -> false
}
}
override val viewType: Int = RowViewType.TITLE_VALUE.ordinal
}
/**
* ComputedStat contains a [stat] and their associated [value]
*/
class ComputedStat(var stat: Stat, var value: Double, var secondValue: Double? = null, var currency: Currency? = null) {
constructor(stat: Stat, value: Double, previousValue: Double?) : this(stat, value) {
if (previousValue != null) {
this.variation = (value - previousValue) / previousValue
}
}
/**
* The value used to get evolution dataset
*/
var progressValue: Double? = null
/**
* The variation of the stat
*/
var variation: Double? = null
/**
* Formats the value of the stat to be suitable for display
*/
fun format(): TextFormat {
return this.stat.format(this.value, this.secondValue, this.currency)
}
}