parent
4c91b27275
commit
effe1db03e
@ -1,2 +1,125 @@ |
|||||||
package net.pokeranalytics.android.calculus.optimalduration |
package net.pokeranalytics.android.calculus.optimalduration |
||||||
|
|
||||||
|
import io.realm.Realm |
||||||
|
import net.pokeranalytics.android.calculus.Calculator |
||||||
|
import net.pokeranalytics.android.calculus.Stat |
||||||
|
import net.pokeranalytics.android.exceptions.PAIllegalStateException |
||||||
|
import net.pokeranalytics.android.model.filter.Query |
||||||
|
import net.pokeranalytics.android.model.filter.QueryCondition |
||||||
|
import net.pokeranalytics.android.model.realm.Session |
||||||
|
import org.apache.commons.math3.fitting.PolynomialCurveFitter |
||||||
|
import org.apache.commons.math3.fitting.WeightedObservedPoints |
||||||
|
import java.util.* |
||||||
|
import kotlin.math.pow |
||||||
|
|
||||||
|
class OptimalDurationCalculator { |
||||||
|
|
||||||
|
companion object { |
||||||
|
|
||||||
|
fun start(session: Session, completion: (Double) -> (Unit)) { |
||||||
|
|
||||||
|
if (!session.isCashGame()) { |
||||||
|
throw PAIllegalStateException("this only makes sense for cash game sessions") |
||||||
|
} |
||||||
|
|
||||||
|
val realm = Realm.getDefaultInstance() |
||||||
|
|
||||||
|
val isLive = session.bankroll?.live ?: true |
||||||
|
|
||||||
|
val query = Query().add(QueryCondition.IsCash) // cash game |
||||||
|
query.add(if (isLive) { QueryCondition.IsLive } else { QueryCondition.IsOnline }) // live / online |
||||||
|
query.add(QueryCondition.EndDateNotNull) // ended |
||||||
|
query.add(QueryCondition.BigBlindNotNull) // has BB value |
||||||
|
|
||||||
|
val sessions = query.queryWith(realm.where(Session::class.java)).findAll() |
||||||
|
val sessionsByDuration = sessions.groupBy { |
||||||
|
it.netDuration // TODO select a value per 15 min or something... |
||||||
|
} |
||||||
|
|
||||||
|
// define if we have enough sessions |
||||||
|
if (sessions.size < 50) { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
val options = Calculator.Options() |
||||||
|
options.query = query |
||||||
|
val report = Calculator.computeStats(realm, options) |
||||||
|
val stdBB = report.results.firstOrNull()?.computedStat(Stat.STANDARD_DEVIATION_BB)?.value |
||||||
|
|
||||||
|
val p = polynomialRegression(sessions, stdBB) |
||||||
|
|
||||||
|
var bestAverage = 0.0 |
||||||
|
var bestHourlyRate = 0.0 |
||||||
|
var bestDuration = 0.0 |
||||||
|
var maxDuration = 0.0 |
||||||
|
|
||||||
|
sessionsByDuration.keys.forEach { |
||||||
|
|
||||||
|
val duration = it.toDouble() |
||||||
|
if (duration < 0) { // TODO define whats valid |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
val averageResult = getBB(duration, p) |
||||||
|
val hourly = averageResult / duration |
||||||
|
if (averageResult > bestAverage && hourly > 2 / 3 * bestHourlyRate) { |
||||||
|
bestAverage = averageResult |
||||||
|
bestDuration = duration |
||||||
|
} |
||||||
|
|
||||||
|
if (duration > 0 && hourly > bestHourlyRate) { |
||||||
|
bestHourlyRate = hourly |
||||||
|
} |
||||||
|
if (duration > maxDuration){ |
||||||
|
maxDuration = duration |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
if (bestDuration > 0.0) { |
||||||
|
completion(bestDuration) |
||||||
|
} |
||||||
|
|
||||||
|
realm.close() |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
private fun getBB(netDuration: Double, polynomial: DoubleArray): Double { |
||||||
|
var y = 0.0 |
||||||
|
for (i in polynomial.indices) { |
||||||
|
y += polynomial[i] * netDuration.pow(i) |
||||||
|
} |
||||||
|
return y |
||||||
|
} |
||||||
|
|
||||||
|
private fun polynomialRegression(sessions: List<Session>, bbStandardDeviation: Double?): DoubleArray { |
||||||
|
|
||||||
|
val stdBB = bbStandardDeviation ?: Double.MAX_VALUE |
||||||
|
|
||||||
|
val points = WeightedObservedPoints() |
||||||
|
val now = Date().time |
||||||
|
|
||||||
|
sessions.forEach { |
||||||
|
var weight = 5.0 |
||||||
|
|
||||||
|
val endTime = it.endDate?.time ?: 0L |
||||||
|
|
||||||
|
val age = now - endTime |
||||||
|
if (age > 2 * 365 * 24 * 3600 * 1000L) { // if more than 2 years loses 1 point |
||||||
|
weight -= 1.0 |
||||||
|
} |
||||||
|
if (it.bbNet > 2 * stdBB) { // if very big result loses 3 points |
||||||
|
weight -= 3.0 |
||||||
|
} |
||||||
|
|
||||||
|
points.add(weight, it.netDuration.toDouble(), it.bbNet) |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
// polynomial of 7 degree, same as iOS |
||||||
|
return PolynomialCurveFitter.create(7).fit(points.toList()) |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
Loading…
Reference in new issue