316 lines
12 KiB
Python
316 lines
12 KiB
Python
import copy
|
|
from enum import Enum, auto
|
|
from random import uniform
|
|
|
|
from millify import millify
|
|
|
|
#from TwitchChannelPointsMiner.utils import char_decision_as_index, float_round
|
|
from TwitchChannelPointsMiner.utils import float_round
|
|
|
|
|
|
class Strategy(Enum):
|
|
MOST_VOTED = auto()
|
|
HIGH_ODDS = auto()
|
|
PERCENTAGE = auto()
|
|
SMART_MONEY = auto()
|
|
SMART = auto()
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
|
|
class Condition(Enum):
|
|
GT = auto()
|
|
LT = auto()
|
|
GTE = auto()
|
|
LTE = auto()
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
|
|
class OutcomeKeys(object):
|
|
# Real key on Bet dict ['']
|
|
PERCENTAGE_USERS = "percentage_users"
|
|
ODDS_PERCENTAGE = "odds_percentage"
|
|
ODDS = "odds"
|
|
TOP_POINTS = "top_points"
|
|
# Real key on Bet dict [''] - Sum()
|
|
TOTAL_USERS = "total_users"
|
|
TOTAL_POINTS = "total_points"
|
|
# This key does not exist
|
|
DECISION_USERS = "decision_users"
|
|
DECISION_POINTS = "decision_points"
|
|
|
|
|
|
class DelayMode(Enum):
|
|
FROM_START = auto()
|
|
FROM_END = auto()
|
|
PERCENTAGE = auto()
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
|
|
class FilterCondition(object):
|
|
__slots__ = [
|
|
"by",
|
|
"where",
|
|
"value",
|
|
]
|
|
|
|
def __init__(self, by=None, where=None, value=None, decision=None):
|
|
self.by = by
|
|
self.where = where
|
|
self.value = value
|
|
|
|
def __repr__(self):
|
|
return f"FilterCondition(by={self.by.upper()}, where={self.where}, value={self.value})"
|
|
|
|
|
|
class BetSettings(object):
|
|
__slots__ = [
|
|
"strategy",
|
|
"percentage",
|
|
"percentage_gap",
|
|
"max_points",
|
|
"minimum_points",
|
|
"stealth_mode",
|
|
"filter_condition",
|
|
"delay",
|
|
"delay_mode",
|
|
]
|
|
|
|
def __init__(
|
|
self,
|
|
strategy: Strategy = None,
|
|
percentage: int = None,
|
|
percentage_gap: int = None,
|
|
max_points: int = None,
|
|
minimum_points: int = None,
|
|
stealth_mode: bool = None,
|
|
filter_condition: FilterCondition = None,
|
|
delay: float = None,
|
|
delay_mode: DelayMode = None,
|
|
):
|
|
self.strategy = strategy
|
|
self.percentage = percentage
|
|
self.percentage_gap = percentage_gap
|
|
self.max_points = max_points
|
|
self.minimum_points = minimum_points
|
|
self.stealth_mode = stealth_mode
|
|
self.filter_condition = filter_condition
|
|
self.delay = delay
|
|
self.delay_mode = delay_mode
|
|
|
|
def default(self):
|
|
self.strategy = self.strategy if self.strategy is not None else Strategy.SMART
|
|
self.percentage = self.percentage if self.percentage is not None else 5
|
|
self.percentage_gap = (
|
|
self.percentage_gap if self.percentage_gap is not None else 20
|
|
)
|
|
self.max_points = self.max_points if self.max_points is not None else 50000
|
|
self.minimum_points = (
|
|
self.minimum_points if self.minimum_points is not None else 0
|
|
)
|
|
self.stealth_mode = (
|
|
self.stealth_mode if self.stealth_mode is not None else False
|
|
)
|
|
self.delay = self.delay if self.delay is not None else 6
|
|
self.delay_mode = (
|
|
self.delay_mode if self.delay_mode is not None else DelayMode.FROM_END
|
|
)
|
|
|
|
def __repr__(self):
|
|
return f"BetSettings(strategy={self.strategy}, percentage={self.percentage}, percentage_gap={self.percentage_gap}, max_points={self.max_points}, minimum_points={self.minimum_points}, stealth_mode={self.stealth_mode})"
|
|
|
|
|
|
class Bet(object):
|
|
__slots__ = ["outcomes", "decision", "total_users", "total_points", "settings"]
|
|
|
|
def __init__(self, outcomes: list, settings: BetSettings):
|
|
self.outcomes = outcomes
|
|
self.__clear_outcomes()
|
|
self.decision: dict = {}
|
|
self.total_users = 0
|
|
self.total_points = 0
|
|
self.settings = settings
|
|
|
|
def update_outcomes(self, outcomes):
|
|
for index in range(0, len(self.outcomes)):
|
|
self.outcomes[index][OutcomeKeys.TOTAL_USERS] = int(
|
|
outcomes[index][OutcomeKeys.TOTAL_USERS]
|
|
)
|
|
self.outcomes[index][OutcomeKeys.TOTAL_POINTS] = int(
|
|
outcomes[index][OutcomeKeys.TOTAL_POINTS]
|
|
)
|
|
if outcomes[index]["top_predictors"] != []:
|
|
# Sort by points placed by other users
|
|
outcomes[index]["top_predictors"] = sorted(
|
|
outcomes[index]["top_predictors"],
|
|
key=lambda x: x["points"],
|
|
reverse=True,
|
|
)
|
|
# Get the first elements (most placed)
|
|
top_points = outcomes[index]["top_predictors"][0]["points"]
|
|
self.outcomes[index][OutcomeKeys.TOP_POINTS] = top_points
|
|
|
|
# Inefficient, but otherwise outcomekeys are represented wrong
|
|
self.total_points = 0
|
|
self.total_users = 0
|
|
for index in range(0, len(self.outcomes)):
|
|
self.total_users += self.outcomes[index][OutcomeKeys.TOTAL_USERS]
|
|
self.total_points += self.outcomes[index][OutcomeKeys.TOTAL_POINTS]
|
|
|
|
if (
|
|
self.total_users > 0
|
|
and self.total_points > 0
|
|
):
|
|
for index in range(0, len(self.outcomes)):
|
|
self.outcomes[index][OutcomeKeys.PERCENTAGE_USERS] = float_round(
|
|
(100 * self.outcomes[index][OutcomeKeys.TOTAL_USERS]) / self.total_users
|
|
)
|
|
self.outcomes[index][OutcomeKeys.ODDS] = float_round(
|
|
#self.total_points / max(self.outcomes[index][OutcomeKeys.TOTAL_POINTS], 1)
|
|
0
|
|
if self.outcomes[index][OutcomeKeys.TOTAL_POINTS] == 0
|
|
else self.total_points / self.outcomes[index][OutcomeKeys.TOTAL_POINTS]
|
|
)
|
|
self.outcomes[index][OutcomeKeys.ODDS_PERCENTAGE] = float_round(
|
|
#100 / max(self.outcomes[index][OutcomeKeys.ODDS], 1)
|
|
0
|
|
if self.outcomes[index][OutcomeKeys.ODDS] == 0
|
|
else 100 / self.outcomes[index][OutcomeKeys.ODDS]
|
|
)
|
|
|
|
self.__clear_outcomes()
|
|
|
|
def __repr__(self):
|
|
return f"Bet(total_users={millify(self.total_users)}, total_points={millify(self.total_points)}), decision={self.decision})\n\t\tOutcome A({self.get_outcome(0)})\n\t\tOutcome B({self.get_outcome(1)})"
|
|
|
|
def get_decision(self, parsed=False):
|
|
#decision = self.outcomes[0 if self.decision["choice"] == "A" else 1]
|
|
decision = self.outcomes[self.decision["choice"]]
|
|
return decision if parsed is False else Bet.__parse_outcome(decision)
|
|
|
|
@staticmethod
|
|
def __parse_outcome(outcome):
|
|
return f"{outcome['title']} ({outcome['color']}), Points: {millify(outcome[OutcomeKeys.TOTAL_POINTS])}, Users: {millify(outcome[OutcomeKeys.TOTAL_USERS])} ({outcome[OutcomeKeys.PERCENTAGE_USERS]}%), Odds: {outcome[OutcomeKeys.ODDS]} ({outcome[OutcomeKeys.ODDS_PERCENTAGE]}%)"
|
|
|
|
def get_outcome(self, index):
|
|
return Bet.__parse_outcome(self.outcomes[index])
|
|
|
|
def __clear_outcomes(self):
|
|
for index in range(0, len(self.outcomes)):
|
|
keys = copy.deepcopy(list(self.outcomes[index].keys()))
|
|
for key in keys:
|
|
if key not in [
|
|
OutcomeKeys.TOTAL_USERS,
|
|
OutcomeKeys.TOTAL_POINTS,
|
|
OutcomeKeys.TOP_POINTS,
|
|
OutcomeKeys.PERCENTAGE_USERS,
|
|
OutcomeKeys.ODDS,
|
|
OutcomeKeys.ODDS_PERCENTAGE,
|
|
"title",
|
|
"color",
|
|
"id",
|
|
]:
|
|
del self.outcomes[index][key]
|
|
for key in [
|
|
OutcomeKeys.PERCENTAGE_USERS,
|
|
OutcomeKeys.ODDS,
|
|
OutcomeKeys.ODDS_PERCENTAGE,
|
|
OutcomeKeys.TOP_POINTS,
|
|
]:
|
|
if key not in self.outcomes[index]:
|
|
self.outcomes[index][key] = 0
|
|
|
|
'''def __return_choice(self, key) -> str:
|
|
return "A" if self.outcomes[0][key] > self.outcomes[1][key] else "B"'''
|
|
|
|
def __return_choice(self, key) -> int:
|
|
largest=0
|
|
for index in range(0, len(self.outcomes)):
|
|
if self.outcomes[index][key] > self.outcomes[largest][key]:
|
|
largest = index
|
|
return largest
|
|
|
|
def skip(self) -> bool:
|
|
if self.settings.filter_condition is not None:
|
|
# key == by , condition == where
|
|
key = self.settings.filter_condition.by
|
|
condition = self.settings.filter_condition.where
|
|
value = self.settings.filter_condition.value
|
|
|
|
fixed_key = (
|
|
key
|
|
if key not in [OutcomeKeys.DECISION_USERS, OutcomeKeys.DECISION_POINTS]
|
|
else key.replace("decision", "total")
|
|
)
|
|
if key in [OutcomeKeys.TOTAL_USERS, OutcomeKeys.TOTAL_POINTS]:
|
|
compared_value = (
|
|
self.outcomes[0][fixed_key] + self.outcomes[1][fixed_key]
|
|
)
|
|
else:
|
|
#outcome_index = char_decision_as_index(self.decision["choice"])
|
|
outcome_index = self.decision["choice"]
|
|
compared_value = self.outcomes[outcome_index][fixed_key]
|
|
|
|
# Check if condition is satisfied
|
|
if condition == Condition.GT:
|
|
if compared_value > value:
|
|
return False, compared_value
|
|
elif condition == Condition.LT:
|
|
if compared_value < value:
|
|
return False, compared_value
|
|
elif condition == Condition.GTE:
|
|
if compared_value >= value:
|
|
return False, compared_value
|
|
elif condition == Condition.LTE:
|
|
if compared_value <= value:
|
|
return False, compared_value
|
|
return True, compared_value # Else skip the bet
|
|
else:
|
|
return False, 0 # Default don't skip the bet
|
|
|
|
def calculate(self, balance: int) -> dict:
|
|
self.decision = {"choice": None, "amount": 0, "id": None}
|
|
if self.settings.strategy == Strategy.MOST_VOTED:
|
|
self.decision["choice"] = self.__return_choice(OutcomeKeys.TOTAL_USERS)
|
|
elif self.settings.strategy == Strategy.HIGH_ODDS:
|
|
self.decision["choice"] = self.__return_choice(OutcomeKeys.ODDS)
|
|
elif self.settings.strategy == Strategy.PERCENTAGE:
|
|
self.decision["choice"] = self.__return_choice(OutcomeKeys.ODDS_PERCENTAGE)
|
|
elif self.settings.strategy == Strategy.SMART_MONEY:
|
|
self.decision["choice"] = self.__return_choice(OutcomeKeys.TOP_POINTS)
|
|
elif self.settings.strategy == Strategy.SMART:
|
|
difference = abs(
|
|
self.outcomes[0][OutcomeKeys.PERCENTAGE_USERS]
|
|
- self.outcomes[1][OutcomeKeys.PERCENTAGE_USERS]
|
|
)
|
|
self.decision["choice"] = (
|
|
self.__return_choice(OutcomeKeys.ODDS)
|
|
if difference < self.settings.percentage_gap
|
|
else self.__return_choice(OutcomeKeys.TOTAL_USERS)
|
|
)
|
|
|
|
if self.decision["choice"] is not None:
|
|
#index = char_decision_as_index(self.decision["choice"])
|
|
index = self.decision["choice"]
|
|
self.decision["id"] = self.outcomes[index]["id"]
|
|
self.decision["amount"] = min(
|
|
int(balance * (self.settings.percentage / 100)),
|
|
self.settings.max_points,
|
|
)
|
|
if (
|
|
self.settings.stealth_mode is True
|
|
and self.decision["amount"]
|
|
>= self.outcomes[index][OutcomeKeys.TOP_POINTS]
|
|
):
|
|
reduce_amount = uniform(1, 5)
|
|
self.decision["amount"] = (
|
|
self.outcomes[index][OutcomeKeys.TOP_POINTS] - reduce_amount
|
|
)
|
|
self.decision["amount"] = int(self.decision["amount"])
|
|
return self.decision
|