Expected Value (EV) Calculation

Command line options

-c, --custom          Run custom user-defined movers and betters that require special initialization.
--cores CORES         How many cores to use in the calculation. (default: 1, use -1 for all cores)
-m MOVER, --mover MOVER
                      Use a predefined mover. Can also be the name of the class of a user-defined mover. (possible values: card-count, basic-strategy-deviations, basic-strategy, perfect, simple;
                      default: card-count)
-b BETTER, --better BETTER
                      Use a predefined better. Can also be the name of the class of a user-defined better. (possible values: card-count, conservative-card-count,
                      wonging-card-count, wonging-conservative-card-count, simple; default: card-count)
-s SIMULATIONS, --simulations SIMULATIONS
                      How many simulations to run. Running more simulations gives more accurate results but they are slower to calculate. (default: 100,000)
--decks DECKS         How many decks the shoe starts with. (default: 6)
--deck-penetration DECK_PENETRATION
                      When to reshuffle the shoe. Reshuffles when cards remaining < starting cards * deck penetration. (default: 0.25)
--stand17             Dealer should stand on soft 17. (default: true)
--hit17               Dealer should hit on soft 17. (default: false)
--das                 Allow double after split. (default: true)
--no-das              Don't allow double after split. (default: false)
--peek                Dealer peeks for blackjack. (default: true)
--no-peek             Dealer doesn't peek for blackjack. (default: false)
--surrender           Allow surrendering. (default: true)
--no-surrender        Don't allow surrendering. (default: false)
--units UNITS         The number of units in total. (default: 200)
--hands-played HANDS_PLAYED
                      How many hands to play before checking the risk of ruin. (default: 1000)

See the help by running python expected_value.py -h.

Calculate the expected value

expected_value.expected_value(action_class, betting_class, simulations, deck_number=6, shoe_penetration=0.25, dealer_peeks_for_blackjack=True, das=True, dealer_stands_soft_17=True, surrender_allowed=True, units=200, hands_played=1000, plot_profits=True, print_info=True)

Estimate the expected value of a strategy.

Parameters:
  • action_class (BaseMover) – The class that chooses the action.

  • betting_class (BaseBetter) – The class that chooses the bet.

  • simulations (int) – How many hands to play.

  • deck_number (int) – The number of decks in the initial shoe.

  • shoe_penetration (float) – When to reshuffle the shoe. Reshuffles when cards remaining < starting cards * deck penetration.

  • dealer_peeks_for_blackjack (bool) – Whether the dealer peeks for blackjack.

  • das (bool) – Whether we can double after splitting.

  • dealer_stands_soft_17 (bool) – Whether the dealer stands on soft 17.

  • surrender_allowed (bool) – Whether the game rules allow surrendering.

  • units (int) – The number of units in total.

  • hands_played (int) – How many hands to play before checking the risk of ruin.

  • plot_profits (bool) – Whether a plot showing how the profit changed over time should be made at the end.

  • print_info (bool) – Whether to print information about the progress of the simulation. Disabled for multithreading.

Returns:

The total return, the average return of a game, the average bet size, and the risk of ruin.

Return type:

tuple[float, float, float, float, float]

Example:

from expected_value import expected_value
from action_strategies import BaseMover
from betting_strategies import BaseBetter
from random import randint, choice

class RandomBetter(BaseBetter):  # Create your own movers and betters.
    @staticmethod
    def get_bet(cards_seen: list[int], deck_number: int) -> int:
        return randint(1, 10)

class RandomMover(BaseMover):
    @staticmethod
    def get_move(hand_value: int, hand_has_ace: bool, dealer_up_card: int, can_double: bool, can_split: bool,
                 can_surrender: bool, can_insure: bool, hand_cards: list[int], cards_seen: list[int], deck_number: int,
                 dealer_peeks_for_blackjack: bool, das: bool, dealer_stands_soft_17: bool) -> tuple[str, bool]:
        return choice(["s", "h"]), False  # Randomly select between stand ("s") and hit ("h"), and never take insurance (False is don't take insurance).

mover = RandomMover()
better = RandomBetter()
average_profit = expected_value(action_class=mover, betting_class=better, simulations=1_000_000, deck_number=6,
                                shoe_penetration=.2, dealer_peeks_for_blackjack=True, das=True,
                                dealer_stands_soft_17=True, surrender_allowed=False, plot_profits=False)
expected_value.expected_value_multithreading(action_class, betting_class, total_simulations, cores=2, deck_number=6, shoe_penetration=0.25, dealer_peeks_for_blackjack=True, das=True, dealer_stands_soft_17=True, surrender_allowed=True, units=200, hands_played=1000)

Estimate the expected value of a strategy, using multithreading to speed up the process. Can’t plot the results.

Parameters:
  • action_class (BaseMover) – The class that chooses the action.

  • betting_class (BaseBetter) – The class that chooses the bet.

  • total_simulations (int) – How many hands to play in total.

  • cores (int) – How many cores to use for the simulation.

  • deck_number (int) – The number of decks in the initial shoe.

  • shoe_penetration (float) – When to reshuffle the shoe. Reshuffles when cards remaining < starting cards * deck penetration.

  • dealer_peeks_for_blackjack (bool) – Whether the dealer peeks for blackjack.

  • das (bool) – Whether we can double after splitting.

  • dealer_stands_soft_17 (bool) – Whether the dealer stands on soft 17.

  • surrender_allowed (bool) – Whether the game rules allow surrendering.

  • units (int) – The number of units in total.

  • hands_played (int) – How many hands to play before checking the risk of ruin.

Returns:

The total return, the average return of a game, the average bet size, and the risk of ruin.

Return type:

tuple[float, float, float, float]

Simulate one hand

expected_value.simulate_hand(action_class, cards, dealer_up_card, dealer_down_card, shoe, splits_remaining, deck_number, dealer_peeks_for_blackjack=True, das=True, dealer_stands_soft_17=True, surrender_allowed=True)

Play one hand.

Parameters:
  • action_class (BaseMover) – The class that chooses the action.

  • cards (list[int]) – The cards in our hand.

  • dealer_up_card (int) – The dealer’s up card.

  • dealer_down_card (int) – The dealer’s down card.

  • shoe (list[int]) – The shoe.

  • splits_remaining (int) – How many more splits we can do.

  • deck_number (int) – The number of decks in the initial shoe.

  • dealer_peeks_for_blackjack (bool) – Whether the dealer peeks for blackjack.

  • das (bool) – Whether we can double after splitting.

  • dealer_stands_soft_17 (bool) – Whether the dealer stands on soft 17.

  • surrender_allowed (bool) – Whether the game rules allow surrendering.

Returns:

The profit/loss from the hand, and how many times we split.

Return type:

float

expected_value.play_hand(action_class, hand_cards, dealer_up_card, dealer_down_card, shoe, splits_remaining, deck_number, dealer_peeks_for_blackjack=True, das=True, dealer_stands_soft_17=True)

Play hands but don’t play the dealer.

Parameters:
  • action_class (BaseMover) – The class that chooses the action.

  • hand_cards (list[list[int]]) – The cards in our hand.

  • dealer_up_card (int) – The dealer’s up card.

  • dealer_down_card (int) – The dealer’s down card.

  • shoe (list[int]) – The shoe.

  • splits_remaining (int) – How many more splits we can do.

  • deck_number (int) – The number of decks in the initial shoe.

  • dealer_peeks_for_blackjack (bool) – Whether the dealer peeks for blackjack.

  • das (bool) – Whether we can double after splitting.

  • dealer_stands_soft_17 (bool) – Whether the dealer stands on soft 17.

Returns:

The hands played out.

Return type:

tuple[list[list[int]], int]

expected_value.get_mover_and_better(mover_name, better_name)

Get the mover and the better from the arguments passed by the user.

Parameters:
  • mover_name (str) – The name of the mover to use. If it isn’t one of the recognized names, then in checks if there is a class of that name.

  • better_name (str) – The name of the better to use. If it isn’t one of the recognized names, then in checks if there is a class of that name.

Returns:

The mover and the better to use, already set up.

Return type:

tuple[BaseMover, BaseBetter]

expected_value.play_dealer(dealer_cards, shoe, dealer_stands_soft_17)

Play the dealers hand to get its final value.

Parameters:
  • dealer_cards (Iterable[int]) – The cards the dealer already has.

  • shoe (list[int]) – The shoe.

  • dealer_stands_soft_17 (bool) – Whether the dealer stands on soft 17.

Returns:

The final value of the dealer’s hand. If the dealer busted, the value is 0.

Return type:

int

Utilities

expected_value.get_card_from_shoe(shoe)

Get a card from the shoe. Always returns the last item from the shoe, so the shoe must be shuffled before.

Parameters:

shoe (list[int]) – The shoe to get a card from.

Returns:

The card we got from the shoe.

Return type:

int

class expected_value.Hand(cards)

Hold information about the hand of the player and the dealer.

Save the initial cards.

Parameters:

cards (Iterable[int]) – The cards the hand started with.

aces()

Return the number of aces that count as 11.

Returns:

The number of aces counted as 11.

Return type:

int

add_card(card)

Add a new card to the hand.

Parameters:

card (int) – The new card to add for the hand (an ace is symbolised as 11).

Return type:

None

value()

Return the value of a hand.

Returns:

The hand’s value.

Return type:

int

value_ace()

Return the value of a hand and how many aces that count as 11 it has.

Returns:

The hand’s value and how many aces are counted as 11 (0 or 1).

Return type:

tuple[int, int]

Wrapper for multithreading

expected_value._expected_value_multithreading_wrapper(results, action_class, betting_class, simulations, deck_number=6, shoe_penetration=0.25, dealer_peeks_for_blackjack=True, das=True, dealer_stands_soft_17=True, surrender_allowed=True, units=200, hands_played=1000)

Estimate the expected value of a strategy. Used inside multithreading. Don’t use this function directly.

Parameters:
  • action_class (action_strategies.BaseMover) – The class that chooses the action.

  • betting_class (betting_strategies.BaseBetter) – The class that chooses the bet.

  • simulations (int) – How many hands to play.

  • deck_number (int) – The number of decks in the initial shoe.

  • shoe_penetration (float) – When to reshuffle the shoe. Reshuffles when cards remaining < starting cards * deck penetration.

  • dealer_peeks_for_blackjack (bool) – Whether the dealer peeks for blackjack.

  • das (bool) – Whether we can double after splitting.

  • dealer_stands_soft_17 (bool) – Whether the dealer stands on soft 17.

  • surrender_allowed (bool) – Whether the game rules allow surrendering.

  • units (int) – The number of units in total.

  • hands_played (int) – How many hands to play before checking the risk of ruin.

  • results (multiprocessing.Queue[tuple[float, float, float, float, float]])

Return type:

None