Added stats + calculation

dev_raz_wip
Laurent 7 years ago
parent cf70562735
commit 1ccd36edc2
  1. 112
      app/src/main/java/net/pokeranalytics/android/calculus/Calculator.kt
  2. 4
      app/src/main/java/net/pokeranalytics/android/calculus/Computable.kt
  3. 35
      app/src/main/java/net/pokeranalytics/android/calculus/Stat.kt
  4. 2
      app/src/main/java/net/pokeranalytics/android/model/realm/Result.kt
  5. 73
      app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt
  6. 61
      app/src/main/java/net/pokeranalytics/android/model/realm/TimeFrame.kt
  7. 15
      app/src/main/java/net/pokeranalytics/android/model/realm/TimeFrameGroup.kt
  8. 43
      app/src/test/java/net/pokeranalytics/android/ExampleUnitTest.kt

@ -1,5 +1,6 @@
package net.pokeranalytics.android.calculus package net.pokeranalytics.android.calculus
import net.pokeranalytics.android.calculus.Stat.*
import net.pokeranalytics.android.model.realm.TimeFrameGroup import net.pokeranalytics.android.model.realm.TimeFrameGroup
@ -25,6 +26,15 @@ class Calculator {
var evolutionValues: EvolutionValues = EvolutionValues.NONE var evolutionValues: EvolutionValues = EvolutionValues.NONE
var displayedStats: List<Stat> = listOf() var displayedStats: List<Stat> = listOf()
fun shouldComputeStandardDeviation() : Boolean {
this.displayedStats.forEach { stat ->
when (stat) {
STANDARD_DEVIATION, STANDARD_DEVIATION_HOURLY, STANDARD_DEVIATION_BB_PER_100_HANDS -> return true
}
}
return false
}
// var aggregation: Aggregation? = null // var aggregation: Aggregation? = null
} }
@ -62,44 +72,114 @@ class Calculator {
fun compute(group: SessionGroup, options: Options) : ComputedResults { fun compute(group: SessionGroup, options: Options) : ComputedResults {
val sessions: List<SessionInterface> = group.sessionGroup val sessions: List<SessionInterface> = group.sessionGroup
val series: Set<TimeFrameGroup> = setOf() // @todo get unique list of serie val sessionGroups: Set<TimeFrameGroup> = setOf() // @todo get unique list of serie
var results: ComputedResults = ComputedResults() var results: ComputedResults = ComputedResults()
var sum: Double = 0.0 var sum: Double = 0.0
var duration: Double = 0.0 var totalHands: Double = 0.0
var bbSum: Double = 0.0
var bbSessionCount: Int = 0
var winningSessionCount: Int = 0
var totalBuyin: Double = 0.0
// @todo add all stats // @todo add all stats
// Compute for each session // Compute for each session
sessions.forEach { item -> var index: Int = 0
sum += item.value sessions.forEach { s ->
index++;
when (options.evolutionValues) { bbSessionCount += s.bigBlindSessionCount
Options.EvolutionValues.STANDARD -> { if (s.value >= 0) {
results.addEvolutionValue(sum, Stat.NETRESULT) winningSessionCount++
}
Options.EvolutionValues.DATED -> {
results.addEvolutionValue(sum, duration, Stat.NETRESULT)
} }
totalBuyin += s.buyin
if (options.evolutionValues != Options.EvolutionValues.NONE) {
sum += s.value
totalHands += s.estimatedHands
bbSum += s.bbNetResult
results.addEvolutionValue(sum / index, AVERAGE)
results.addEvolutionValue(index.toDouble(), NUMBER_OF_GAMES)
results.addEvolutionValue(bbSum / bbSessionCount, AVERAGE_NET_BB)
results.addEvolutionValue((winningSessionCount / index).toDouble(), WIN_RATIO)
results.addEvolutionValue(totalBuyin / index, AVERAGE_BUYIN)
results.addEvolutionValue(Stat.returnOnInvestment(sum, totalBuyin), ROI)
} }
} }
// Compute for each serie // Compute for each serie
series.forEach { serie -> var duration: Double = 0.0
duration += serie.duration var hourlyRate: Double = 0.0; var hourlyRateBB: Double = 0.0
index = 0; sum = 0.0; totalHands = 0.0; bbSum = 0.0;
sessionGroups.forEach { group ->
index++
duration += group.duration
sum += group.netResult
totalHands += group.estimatedHands
bbSum += group.bbNetResult
hourlyRate = sum / duration * 3600.0
hourlyRateBB = bbSum / duration * 3600.0
if (options.evolutionValues != Options.EvolutionValues.NONE) { if (options.evolutionValues != Options.EvolutionValues.NONE) {
results.addEvolutionValue(duration, Stat.DURATION) results.addEvolutionValue(sum, duration, NETRESULT)
results.addEvolutionValue(sum / duration * 3600.0, duration, HOURLY_RATE)
results.addEvolutionValue(Stat.netBBPer100Hands(bbSum, totalHands), duration, NET_BB_PER_100_HANDS)
results.addEvolutionValue(hourlyRate, duration, HOURLY_RATE)
results.addEvolutionValue(index.toDouble(), duration, NUMBER_OF_GROUPS)
results.addEvolutionValue(group.duration, duration, DURATION)
results.addEvolutionValue(duration / index, duration, AVERAGE_DURATION)
results.addEvolutionValue(hourlyRateBB, duration, HOURLY_RATE_BB)
} }
} }
val average: Double = sum / sessions.size
// Create stats // Create stats
results.addStats(setOf( results.addStats(setOf(
ComputedStat(Stat.NETRESULT, sum), ComputedStat(NETRESULT, sum),
ComputedStat(Stat.AVERAGE,sum / sessions.size), ComputedStat(HOURLY_RATE, hourlyRate),
ComputedStat(Stat.DURATION, duration) ComputedStat(AVERAGE, average),
ComputedStat(DURATION, duration),
ComputedStat(NUMBER_OF_GROUPS, sessionGroups.size.toDouble()),
ComputedStat(NUMBER_OF_GAMES, sessions.size.toDouble()),
ComputedStat(AVERAGE_DURATION, (duration / 3600.0) / sessions.size),
ComputedStat(NET_BB_PER_100_HANDS, Stat.netBBPer100Hands(bbSum, totalHands)),
ComputedStat(HOURLY_RATE_BB, bbSum / duration * 3600.0),
ComputedStat(AVERAGE_NET_BB, bbSum / bbSessionCount),
ComputedStat(WIN_RATIO, (winningSessionCount / sessions.size).toDouble()),
ComputedStat(AVERAGE_BUYIN, totalBuyin / sessions.size),
ComputedStat(ROI, Stat.returnOnInvestment(sum, totalBuyin))
)) ))
// Standard Deviation
if (options.shouldComputeStandardDeviation()) {
var stdSum: Double = 0.0
sessions.forEach { s ->
stdSum += Math.pow(s.value - average, 2.0)
}
val standardDeviation: Double = Math.sqrt(stdSum / sessions.size)
var hourlyStdSum: Double = 0.0
sessionGroups.forEach { sg ->
hourlyStdSum += Math.pow(sg.hourlyRate - hourlyRate, 2.0)
}
val hourlyStandardDeviation: Double = Math.sqrt(hourlyStdSum / sessionGroups.size)
results.addStats(setOf(
ComputedStat(STANDARD_DEVIATION, standardDeviation),
ComputedStat(Stat.STANDARD_DEVIATION_HOURLY, hourlyStandardDeviation)
))
}
return results return results
} }

@ -10,6 +10,10 @@ interface Summable {
// An interface describing some class that can be computed // An interface describing some class that can be computed
interface SessionInterface : Summable { interface SessionInterface : Summable {
var serie: TimeFrameGroup var serie: TimeFrameGroup
var estimatedHands: Double
var bbNetResult: Double
var bigBlindSessionCount: Int // 0 or 1
var buyin: Double
} }
/** /**

@ -7,25 +7,48 @@ interface AnyStat {
enum class Stat : AnyStat { enum class Stat : AnyStat {
NETRESULT, NETRESULT,
HOURLYRATE, HOURLY_RATE,
DURATION,
AVERAGE, AVERAGE,
STANDARDDEVIATION, NUMBER_OF_GROUPS,
HOURLYSTANDARDDEVIATION; NUMBER_OF_GAMES,
DURATION,
AVERAGE_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;
fun label() : String = when (this) { fun label() : String = when (this) {
NETRESULT -> "" NETRESULT -> ""
HOURLYRATE -> "" HOURLY_RATE -> ""
else -> throw IllegalArgumentException("Label not defined") else -> throw IllegalArgumentException("Label not defined")
} }
fun hasDistributionSorting() : Boolean { fun hasDistributionSorting() : Boolean {
when (this) { when (this) {
STANDARDDEVIATION, HOURLYSTANDARDDEVIATION -> return true STANDARD_DEVIATION, STANDARD_DEVIATION_HOURLY, STANDARD_DEVIATION_BB_PER_100_HANDS -> return true
else -> return false else -> return false
} }
} }
companion object {
fun returnOnInvestment(netResult: Double, buyin: Double) : Double {
return netResult / buyin
}
fun netBBPer100Hands(netBB: Double, numberOfHands: Double) : Double {
return netBB / numberOfHands * 100
}
}
} }
enum class CashSessionStat : AnyStat { enum class CashSessionStat : AnyStat {

@ -36,7 +36,7 @@ open class Result : RealmObject() {
} }
// The net (readonly) // The net (readonly)
var net: Double? = null var net: Double = 0.0
// The transactions associated with the Result, impacting the result // The transactions associated with the Result, impacting the result
var transactions: RealmList<Transaction> = RealmList() var transactions: RealmList<Transaction> = RealmList()

@ -1,18 +1,13 @@
package net.pokeranalytics.android.model.realm package net.pokeranalytics.android.model.realm
import io.realm.RealmList import io.realm.*
import io.realm.Realm import io.realm.annotations.Ignore
import io.realm.RealmObject
import io.realm.RealmResults
import io.realm.Sort
import io.realm.annotations.PrimaryKey import io.realm.annotations.PrimaryKey
import net.pokeranalytics.android.*
import net.pokeranalytics.android.ui.adapter.components.DynamicRowDelegate import net.pokeranalytics.android.ui.adapter.components.DynamicRowDelegate
import net.pokeranalytics.android.ui.adapter.components.DynamicRowInterface import net.pokeranalytics.android.ui.adapter.components.DynamicRowInterface
import net.pokeranalytics.android.ui.adapter.components.SessionRow import net.pokeranalytics.android.ui.adapter.components.SessionRow
import net.pokeranalytics.android.util.data.sessionDao import net.pokeranalytics.android.util.data.sessionDao
import java.util.* import java.util.*
import java.util.UUID.randomUUID
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
@ -21,6 +16,9 @@ open class Session(comment: String = "") : RealmObject(), DynamicRowDelegate {
@PrimaryKey @PrimaryKey
var id = UUID.randomUUID().toString() var id = UUID.randomUUID().toString()
// The result of the main user
var result: Result? = null
// The time frame of the Session, i.e. the start & end date // The time frame of the Session, i.e. the start & end date
var timeFrame: TimeFrame? = null var timeFrame: TimeFrame? = null
@ -30,30 +28,27 @@ open class Session(comment: String = "") : RealmObject(), DynamicRowDelegate {
// the date of creation of the app // the date of creation of the app
var creationDate: Date = Date() var creationDate: Date = Date()
// The bankroll hosting the results
var bankroll: Bankroll? = null
// The limit type: NL, PL... // The limit type: NL, PL...
var limit: Int? = null var limit: Int? = null
// The number of tables played at the same time // The game played during the Session
var numberOfTables: Int = 1 var game: Game? = null
// The number of players at the table // The number of players at the table
var tableSize: Int? = null var tableSize: Int? = null
// The game played during the Session // the location where the session is played
var game: Game? = null var location: Location? = null
// The bankroll hosting the results // The number of tables played at the same time
var bankroll: Bankroll? = null var numberOfTables: Int = 1
// The hands list associated with the Session // The hands list associated with the Session
var hands: RealmList<HandHistory> = RealmList() var hands: RealmList<HandHistory> = RealmList()
// the location where the session is played
var location: Location? = null
// The result of the main user
var result: Result? = null
// The list of opponents who participated to the session // The list of opponents who participated to the session
var opponents: RealmList<Player> = RealmList() var opponents: RealmList<Player> = RealmList()
@ -85,6 +80,46 @@ open class Session(comment: String = "") : RealmObject(), DynamicRowDelegate {
// The features of the tournament, like Knockout, Shootout, Turbo... // The features of the tournament, like Knockout, Shootout, Turbo...
var tournamentFeatures: RealmList<TournamentFeature> = RealmList() var tournamentFeatures: RealmList<TournamentFeature> = RealmList()
companion object {
fun newInstance() : Session {
var session: Session = Session()
session.result = Result()
session.timeFrame = TimeFrame()
return session
}
}
@Ignore
var estimatedHands: Double = 0.0
@Ignore
var bbNetResult: Double = 0.0
get() {
this.cgBigBlind?.let { bb ->
this.result?.let { result ->
return result.net / bb
}
}
return 0.0
}
@Ignore
var bigBlindSessionCount: Int = if (this.cgBigBlind != null) 1 else 0
// get() = if (this.cgBigBlind != null) 1 else 0
@Ignore
var buyin: Double = 0.0
get() {
this.result?.let {
it.buyin?.let {
return it
}
}
return 0.0
}
override fun adapterRows(): ArrayList<DynamicRowInterface> { override fun adapterRows(): ArrayList<DynamicRowInterface> {
val rows = ArrayList<DynamicRowInterface>() val rows = ArrayList<DynamicRowInterface>()

@ -1,24 +1,79 @@
package net.pokeranalytics.android.model.realm package net.pokeranalytics.android.model.realm
import io.realm.RealmObject import io.realm.RealmObject
import io.realm.annotations.PrimaryKey import io.realm.RealmResults
import io.realm.annotations.LinkingObjects
import java.util.* import java.util.*
open class TimeFrame : RealmObject() { open class TimeFrame : RealmObject() {
// A start date // A start date
var startDate: Date = Date() var startDate: Date = Date()
set(value) {
field = value
this.computeDuration()
if (this.session != null) {
this.notifyDateChange()
}
}
// An end date // An end date
var endDate: Date? = null var endDate: Date? = null
set(value) {
field = value
this.computeDuration()
if (this.session != null) {
this.notifyDateChange()
}
}
// The break duration // The break duration
var breakDuration: Double = 0.0 var breakDuration: Long = 0L
set(value) {
field = value
this.computeDuration()
}
// the total duration // the total duration
var duration: Double = 0.0 var duration: Long = 0L
private set
// indicates a state of pause // indicates a state of pause
var paused: Boolean = false var paused: Boolean = false
@LinkingObjects("timeFrame")
private val session: RealmResults<Session>? = null
@LinkingObjects("timeFrame")
private val group: RealmResults<TimeFrameGroup>? = null
private fun computeDuration() {
var endDate: Date
if (this.endDate != null) {
endDate = this.endDate!!
} else {
endDate = Date()
}
this.duration = endDate.time - startDate.time - this.breakDuration
}
private fun notifyDateChange() {
// val realm = Realm.getDefaultInstance()
//
// var query: RealmQuery<TimeFrame> = realm.where(TimeFrame::class.java)
// query.greaterThan("startDate", this.startDate.time)
//
// this.endDate?.let { endDate ->
// query.or()
// .greaterThan("startDate", endDate.time)
// .lessThan("endDate", endDate.time)
// }
//
//
// realm.close()
}
} }

@ -3,8 +3,6 @@ package net.pokeranalytics.android.model.realm
import io.realm.RealmList import io.realm.RealmList
import io.realm.RealmObject import io.realm.RealmObject
import io.realm.annotations.Ignore import io.realm.annotations.Ignore
import io.realm.annotations.PrimaryKey
import java.util.*
open class TimeFrameGroup() : RealmObject() { open class TimeFrameGroup() : RealmObject() {
@ -13,10 +11,21 @@ open class TimeFrameGroup() : RealmObject() {
var timeFrame: TimeFrame? = null var timeFrame: TimeFrame? = null
// The list of Session played within the group, i.e. played within the same time frame // The list of Session played within the group, i.e. played within the same time frame
var timeFrames: RealmList<Session> = RealmList() var sessions: RealmList<Session> = RealmList()
@Ignore // a duration shortcut @Ignore // a duration shortcut
var duration: Double = 0.0 var duration: Double = 0.0
@Ignore // a netResult shortcut
var netResult: Double = 0.0
@Ignore // a duration shortcut
var hourlyRate: Double = 0.0
@Ignore
var estimatedHands: Double = 0.0
@Ignore
var bbNetResult: Double = 0.0
} }

@ -1,17 +1,52 @@
package net.pokeranalytics.android package net.pokeranalytics.android
import net.pokeranalytics.android.calculus.*
import net.pokeranalytics.android.model.realm.TimeFrameGroup
import org.junit.Assert.fail
import org.junit.Test import org.junit.Test
import org.junit.Assert.*
/** /**
* Example local unit test, which will execute on the development machine (host). * Example local unit test, which will execute on the development machine (host).
* *
* See [testing documentation](http://d.android.com/tools/testing). * See [testing documentation](http://d.android.com/tools/testing).
*/ */
class ExampleUnitTest { class ExampleUnitTest {
class Grade(someValue: Double) : SessionInterface {
// override var serie: Serie = Serie(TimeFrame())
override var value: Double = someValue
override var serie: TimeFrameGroup = TimeFrameGroup()
override var estimatedHands: Double = 0.0
override var bbNetResult: Double = 0.0
override var bigBlindSessionCount: Int = 0 // 0 or 1
override var buyin: Double = 0.0
}
@Test @Test
fun addition_isCorrect() { fun testStats() {
assertEquals(4, 2 + 2)
val grades: List<Grade> = listOf(Grade(someValue = 10.0), Grade(someValue = 20.0))
val group = SessionGroup(name = "test", sessions = grades)
val results: ComputedResults = Calculator.compute(group, Calculator.Options())
val sum = results.computedStat(Stat.NETRESULT)
if (sum != null) {
assert(sum.value == 30.0)
} else {
fail("No Net result stat")
}
val average = results.computedStat(Stat.AVERAGE)
if (average != null) {
assert(average.value == 15.0)
} else {
fail("No AVERAGE stat")
} }
}
} }

Loading…
Cancel
Save