Blackjack: What To Expect?
Blackjack is my favorite casino game. It has the best odds of winning. Realistically, the odds are still against you and you will lose money over the long term*, but it is a great way to pass the time and have your money last as you enjoy the free drinks. You also tend to meet some colorful characters.
For a quick refresher on how the game works:
- Everybody at the table plays against the dealer. Everyone gets two cards. One of the dealers cards is face down.
- The goal is to get as close to 21 without going over. Face cards are 10 and aces are 1 or 11, your choice.
- The dealer plays by a set of rules as to when they "hit" (get another card), giving the player an edge because the dealer is predictable.
- After you get your first two cards, you can ask for as many more as you want.
- If you go over 21 you lose. If the sum of the dealer's hand is less than 21 and higher than your hand, you lose.
- If you double down, you double your bet and only get one more card. This is helpful because it allows you to bet more money once you know you have a good hand.
- If you have two identical cards, you can split them into two hands, again creating an advantage if both of these hands are better than average .
- There are slight variations to the game by casino, but typically the house advantage is .62%
This is a very brief summary. There are entire books written on rules, variations, and strategies. For more information, check out Playing Blackjack to Win by Baldwin, Cantey, Maisel and McDermott. I essentially use their strategy. The math they first derived in the 1950s hasn't changed (as math is prone to do).
*Unless you count cards, which I will not get into. To any casino or government employees reading this, I officially do not know how to count cards. I mean, how could you possibly keep track of the low and high cards already played and use this knowledge to your advantage?
What Should I Expect to Win
Like roulette, you shouldn't. End of post.
Blackjack players have more of an active role than roulette players. They can decide when to hit, double down, and split. This gives you more control and the potential to be a better than the average player (but still worse than the house).
Since the math is already solved for this game, and is a bit more complicated than roulette, I decided to make a simulation following 'The Best Strategy" presented in Playing Blackjack to Win. I also put the code at the bottom of the page so you can test and tweak it yourself.
Tweaking the code can be of great benefit to the casual player if they want to learn variations of the strategies. To play your best, you need to stick to the rules and know the strategy. Do you double down on 11 when the dealer has a 10 showing? Do you ever split 7s? Do you hit on a soft 16 against a dealer showing a 5? (Yes, sometimes, no). All of this logic is in the code but more than I think the casual reader cares for me to articulate. After 100,000 simulations:
100,000 Hands Played. Payout Percent: -0.4345%
95% Confidence Interval: -1.15% to 0.28%
-6.0 2 0.00%
-5.0 4 0.00%
-4.0 47 0.05%
-3.0 213 0.21%
-2.0 4243 4.24%
-1.0 43401 43.40%
0.0 8681 8.68%
1.0 32747 32.75%
1.5 4505 4.51%
2.0 5785 5.79%
3.0 268 0.27%
4.0 89 0.09%
5.0 13 0.01%
6.0 2 0.00%
So after playing 100,000 hands, betting $100,000, you end up losing $434.50. You hit 4,505 blackjacks. And on 4 occasions you split your hand twice and doubled down each hand, losing 2 and winning 2.
Note, these results may not be the same as the exact mathematical proof due to:
- It is a simulation, not a calculation of every hand that could occur
- It is limited by my own understanding. I like this because if I coded this wrong, I am playing the game wrong, and this is what I will actually experience from my game play.
How the Rest of the Night/Year Will Go
Let's imagine you are a serious player. You go out every week and play 100 hands a night. Considering Blackjack's variability, this would actually be a pretty volatile sample, so I will examine one simulation (maybe a few) and leave the code for you to create you own simulations. But let's consider:
- How many weeks do you have a winning night?
- How much do you win on the good nights?
- How bad are the bad nights?
We'll assume each hand is $20 (a low priced to moderate Vegas average) and blackjack payout is 1.5x the bet.
So for our first simulation:
Total Payout: -820.0
Weekly Average: -15.76923076923077
Winning Nights: 24
Winning Nights Average: 156.25
Loosing Nights: 28
Loosing Nights Average: -163.21428571428572
History: [-10.0, 90.0, -190.0, -440.0, -80.0, -30.0, 320.0, 140.0, 420.0, 310.0, 20.0, -90.0, 260.0, -130.0, -260.0, 70.0, 10.0, -80.0, -70.0, -380.0, -50.0, -90.0, -140.0, 330.0, -180.0, -60.0, -310.0, 80.0, -30.0, -70.0, -390.0, 70.0, -280.0, 270.0, -420.0, -60.0, -340.0, 220.0, 210.0, -30.0, 100.0, 40.0, 150.0, 80.0, 50.0, 80.0, 270.0, 70.0, -140.0, -10.0, 90.0, -210.0]
So not horrible. Loosing 15 bucks a week for a night out of entertainment is about the price of a movie. 100 games could take anywhere from one to three hours, so its about the same.
Let's look at another one:
Total Payout: 200.0
Weekly Average: 3.8461538461538463
Winning Nights: 29
Winning Nights Average: 168.9655172413793
Loosing Nights: 25
Loosing Nights Average: -188.0
History: [110.0, -40.0, 380.0, 190.0, -260.0, 140.0, 110.0, -100.0, 30.0, -50.0, -90.0, 350.0, -120.0, -340.0, -210.0, 350.0, 180.0, 190.0, -30.0, -380.0, 340.0, 0.0, 550.0, 70.0, -110.0, 320.0, -200.0, -180.0, 290.0, 60.0, -250.0, 40.0, -240.0, 0.0, 80.0, -360, 180.0, 150.0, 120.0, -390.0, 110.0, -230.0, 50.0, -20.0, 160.0, -350.0, -520.0, -130.0, 280.0, 60.0, 10.0, -100.0]
Oh! We won this time! $4 a night! about $2 an hour!
Let's look at one more:
Total Payout: -90.0
Weekly Average: -1.7307692307692308
Winning Nights: 26
Winning Nights Average: 176.15384615384616
Loosing Nights: 27
Loosing Nights Average: -172.96296296296296
History: [370.0, 110.0, -90.0, -340.0, 110.0, 40.0, 240.0, 270.0, -140.0, -140.0, 300.0, -460.0, -70.0, 50.0, 70.0, -200.0, -160.0, -140.0, 50.0, -480.0, -130.0, -70.0, 250.0, -20.0, -20.0, -320.0, 290.0, 170.0, -10.0, 260.0, -180.0, 90.0, -180.0, 110.0, 0.0, -240.0, -240.0, -130.0, 100.0, 160.0, -170.0, -30.0, 280.0, -180.0, 170.0, 210.0, -370.0, -160.0, 180.0, 130.0, 380.0, 190.0]
Looks like we lost again...
Warnings on Variations
Some casinos have variations to make the game stacked even more against you. The one I most recently came across was Encore in Boston blackjack payout. In most casinos, if you get a natural blackjack with your first two cards, they give you 1.5 your bet. At encore, they lower this to 1.2.
Now considering this only happened in our 100,000 simulation 4,504 times, and each time you win 30 cents less, the casino took an extra $1,351.20 (or you lost an extra $1,351.20). So our -0.43% payout decreased to -1.79%.
Final Thoughts on Blackjack
I love playing blackjack and would like to think I have made a small amount of money over the time I have played it, though this is probably false. I often can't remember all the rules and find myself asking the dealer or nice people at my table. The more free drinks they give me the more i abandon my strategy and feel lucky. It certainly isn't a way to make money, but it's not the worst way to lose it.
Code
Some quick notes on the program:
- It was written in Python 3 using Jupiter Notebook.
- I made a simplifying assumption that you can only split a hand twice (for a max of three hands).
- I assumed the deck was infinite, in other words having two aces doesn't decrease the chance the next card will be an ace. Since casinos use quiet a few decks, I figured this was fair.
- The dataframe created has a lot of useful information and I hope to go back and look at some other statistics. For example, the number of double downs and splits is recorded, but I didn't really analyze or use that information above.
Some notes on my coding:
- I am not the best coder
- If I planned the code better it would have been a lot smoother:
- The bjsum function was introduced way too late and would have made my life easier had I planned ahead and thought of how to sum a hand when a card can be both 1 and 11.
- I originally had a hand that would track J,Q,K,A and the numeric equivalents separately. That was scrapped as it was useless.
Here is the code to import my libraries and set up a dataframe for when to split:
#imports and initial condition
import pandas as pd
from random import randrange
import math
#function for summing hands (consdiering Aces)
def bjsum(list_cards):
if 1 in list_cards and sum(list_cards)+10<=21:
return sum(list_cards)+10
else:
return sum(list_cards)
#create dataframe to reflect when to split
split = pd.DataFrame(columns = ['Card','D1','D2','D3','D4','D5','D6','D7','D8','D9','D10'])
split.loc[1] = (1,'Y','Y','Y','Y','Y','Y','Y','Y','Y','Y')
split.loc[2] = (2,'N','Y','Y','Y','Y','Y','Y','N','N','N')
split.loc[3] = (3,'N','Y','Y','Y','Y','Y','Y','N','N','N')
split.loc[4] = (4,'N','N','N','N','N','N','N','N','N','N')
split.loc[5] = (5,'N','N','N','N','N','N','N','N','N','N')
split.loc[6] = (6,'N','Y','Y','Y','Y','Y','Y','N','N','N')
split.loc[7] = (7,'N','Y','Y','Y','Y','Y','Y','N','N','N')
split.loc[8] = (8,'Y','Y','Y','Y','Y','Y','Y','Y','Y','Y')
split.loc[9] = (9,'N','Y','Y','Y','Y','Y','N','Y','Y','N')
split.loc[10] = (10,'N','N','N','N','N','N','N','N','N','N')
split.loc[11] = (11,'N','N','N','N','N','N','N','N','N','N')
split.loc[12] = (12,'N','N','N','N','N','N','N','N','N','N')
split.loc[13] = (13,'N','N','N','N','N','N','N','N','N','N')
Here is the code for the basic function of simulating a hand:
#simulate hands
def runSim(printOut = True,bet = 1,bj_pay = 1.5,hands = 100):
handtracker = pd.DataFrame(columns = ['PH1','PH2','PH3','PH1_tot','PH2_tot','PH3_tot',
'DH', 'DH_tot','Bet','W_L','Payout','Split','DD','Bust','BJ'])
for x in range(0,hands):
#creates the initial hand for the player
PH1_list = [randrange(13)+1,randrange(13)+1]
PH1_list = [10 if x>10 else x for x in PH1_list]
PH2_list = []
PH3_list = []
#creates hand for the dealer
DH_list = [randrange(13)+1,randrange(13)+1]
DH_list = [10 if x>10 else x for x in DH_list]
#record initial conditions
handtracker.at[x,'Bet']=bet
handtracker.at[x,'BJ']=0
handtracker.at[x,'DD']=0
handtracker.at[x,'Split']=0
handtracker.at[x,'Bust']=0
handtracker.at[x,'Payout']=0
handtracker.at[x,'W_L']=""
#check for both having blackjack
if bjsum(PH1_list)==21 and bjsum(DH_list)==21:
handtracker.at[x,'BJ']=1
handtracker.at[x,'Payout']=0
handtracker.at[x,'W_L']='P'
handtracker.at[x,'PH1']=PH1_list
handtracker.at[x,'PH2']=PH2_list
handtracker.at[x,'PH3']=PH3_list
handtracker.at[x,'PH1_tot']=bjsum(PH1_list)
handtracker.at[x,'PH2_tot']=bjsum(PH2_list)
handtracker.at[x,'PH3_tot']=bjsum(PH3_list)
handtracker.at[x,'DH']=DH_list
handtracker.at[x,'DH_tot']=bjsum(DH_list)
continue
#check for player blackjack
if bjsum(PH1_list)==21 :
handtracker.at[x,'BJ']=1
handtracker.at[x,'Payout']=bet*bj_pay
handtracker.at[x,'W_L']='W'
handtracker.at[x,'PH1']=PH1_list
handtracker.at[x,'PH2']=PH2_list
handtracker.at[x,'PH3']=PH3_list
handtracker.at[x,'PH1_tot']=bjsum(PH1_list)
handtracker.at[x,'PH2_tot']=bjsum(PH2_list)
handtracker.at[x,'PH3_tot']=bjsum(PH3_list)
handtracker.at[x,'DH']=DH_list
handtracker.at[x,'DH_tot']=bjsum(DH_list)
continue
#check for dealer blackjack
if bjsum(DH_list)==21:
handtracker.at[x,'BJ']=0
handtracker.at[x,'Payout']=-bet
handtracker.at[x,'W_L']='L'
handtracker.at[x,'PH1']=PH1_list
handtracker.at[x,'PH2']=PH2_list
handtracker.at[x,'PH3']=PH3_list
handtracker.at[x,'PH1_tot']=bjsum(PH1_list)
handtracker.at[x,'PH2_tot']=bjsum(PH2_list)
handtracker.at[x,'PH3_tot']=bjsum(PH3_list)
handtracker.at[x,'DH']=DH_list
handtracker.at[x,'DH_tot']=21
continue
#play the dealer hand
while bjsum(DH_list)<17:
DH_list.append(min(randrange(13)+1,10))
#split?
if PH1_list[0] == PH1_list[1] and split.loc[PH1_list[0]][DH_list[0]]=='Y':
handtracker.at[x,'Split']=1
PH1_list[1] = min(randrange(13)+1,10)
PH2_list.append(PH1_list[0])
PH2_list.append(min(randrange(13)+1,10))
split_two = "N"
#check if first hand resplit
if PH1_list[0] == PH1_list[1] and split.loc[PH1_list[0]][DH_list[0]]=='Y':
split_two = "Y"
handtracker.at[x,'Split']=2
PH1_list[1] = min(randrange(13)+1,10)
PH3_list.append(PH1_list[0])
PH3_list.append(min(randrange(13)+1,10))
#check if second hand resplit
if PH2_list[0] == PH2_list[1] and split.loc[PH2_list[0]][DH_list[0]]=='Y' and split_two=="N":
handtracker.at[x,'Split']=2
PH2_list[1] = min(randrange(13)+1,10)
PH3_list.append(PH2_list[0])
PH3_list.append(min(randrange(13)+1,10))
split_two="N"
#loop through hands, check for DD, then adjudicate the hand
for i in [PH1_list,PH2_list,PH3_list]:
#pass if hand is not played
if bjsum(i)==0:continue
#check for ace, if have, play soft rules
if 1 in i:
#check for double down
DD = 'N'
if bjsum(i)== 12 and DH_list[0] >=5 and DH_list[0]<=5: DD = 'Y'
if bjsum(i)== 13 and DH_list[0] >=5 and DH_list[0]<=6: DD = 'Y'
if bjsum(i)== 14 and DH_list[0] >=5 and DH_list[0]<=6: DD = 'Y'
if bjsum(i)== 15 and DH_list[0] >=5 and DH_list[0]<=6: DD = 'Y'
if bjsum(i)== 16 and DH_list[0] >=5 and DH_list[0]<=6: DD = 'Y'
if bjsum(i)== 17 and DH_list[0] >=3 and DH_list[0]<=6: DD = 'Y'
if bjsum(i)== 18 and DH_list[0] >=4 and DH_list[0]<=6: DD = 'Y'
if DD =='Y':handtracker.at[x,'DD']=handtracker.at[x,'DD']+1
#playout player hand. One card for double down, and keep hitting while conditions met
if DD=='Y':
handtracker.at[x,'Bet']=bet*2
i.append(min(randrange(13)+1,10))
else:
H='Y'
while H == 'Y':
H = 'N'
#test if it is still soft
if bjsum(i)!=sum(i):
if bjsum(i)<= 17: H = 'Y'
elif bjsum(i)<= 18 and (DH_list[0]>=9): H = 'Y'
else:
if bjsum(i)<= 11: H = 'Y'
elif bjsum(i)<= 12 and (DH_list[0] <=3 or DH_list[0]>=7): H = 'Y'
elif bjsum(i)<= 16 and (DH_list[0] <=1 or DH_list[0]>=7): H = 'Y'
if H=='Y':
i.append(min(randrange(13)+1,10))
else:
#check for double down
DD = 'N'
if bjsum(i)== 9 and DH_list[0] >=2 and DH_list[0]<=6: DD = 'Y'
if bjsum(i)== 10 and DH_list[0] >=2 and DH_list[0]<=9: DD = 'Y'
if bjsum(i)== 11 and DH_list[0] >=2 : DD = 'Y'
if DD =='Y':handtracker.at[x,'DD']=handtracker.at[x,'DD']+1
#playout player hand. One card for double down, and keep hitting while conditions met
if DD=='Y':
handtracker.at[x,'Bet']=bet*2
i.append(min(randrange(13)+1,10))
else:
H='Y'
while H == 'Y':
H = 'N'
if bjsum(i)<= 11: H = 'Y'
elif bjsum(i)<= 12 and (DH_list[0] <=3 or DH_list[0]>=7): H = 'Y'
elif bjsum(i)<= 16 and (DH_list[0] <=1 or DH_list[0]>=7): H = 'Y'
if H=='Y':
i.append(min(randrange(13)+1,10))
#decide handoutcome
#player bust
if bjsum(i)>21:
handtracker.at[x,'Bust']=handtracker.at[x,'Bust'] +1
handtracker.at[x,'W_L']=handtracker.at[x,'W_L'] +'L'
if DD=='Y': handtracker.at[x,'Payout']=handtracker.at[x,'Payout']-2*bet
else:handtracker.at[x,'Payout']=handtracker.at[x,'Payout']-bet
#Dealer has higher hand and didn't bust
if bjsum(i)<bjsum(DH_list) and bjsum(DH_list)<=21:
handtracker.at[x,'W_L']=handtracker.at[x,'W_L'] +'L'
if DD=='Y': handtracker.at[x,'Payout']=handtracker.at[x,'Payout']-2*bet
else:handtracker.at[x,'Payout']=handtracker.at[x,'Payout']-bet
#push and no one busts
if bjsum(i)==bjsum(DH_list) and bjsum(i)<=21 and bjsum(DH_list)<=21:
handtracker.at[x,'W_L']=handtracker.at[x,'W_L'] +'P'
#Dealer busts
if bjsum(i)<=21 and bjsum(DH_list)>21:
handtracker.at[x,'W_L']=handtracker.at[x,'W_L'] +'W'
if DD=='Y': handtracker.at[x,'Payout']=handtracker.at[x,'Payout']+2*bet
else:handtracker.at[x,'Payout']=handtracker.at[x,'Payout']+bet
#Player has better hand and no one busts
if bjsum(i)>bjsum(DH_list) and bjsum(i)<=21 and bjsum(DH_list)<=21:
handtracker.at[x,'W_L']=handtracker.at[x,'W_L'] +'W'
if DD=='Y': handtracker.at[x,'Payout']=handtracker.at[x,'Payout']+2*bet
else:handtracker.at[x,'Payout']=handtracker.at[x,'Payout']+bet
handtracker.at[x,'PH1']=PH1_list
handtracker.at[x,'PH2']=PH2_list
handtracker.at[x,'PH3']=PH3_list
handtracker.at[x,'PH1_tot']=bjsum(PH1_list)
handtracker.at[x,'PH2_tot']=bjsum(PH2_list)
handtracker.at[x,'PH3_tot']=bjsum(PH3_list)
handtracker.at[x,'DH']=DH_list
handtracker.at[x,'DH_tot']=bjsum(DH_list)
if printOut:
print(hands, " Hands Played. Payout Percent:",handtracker['Payout'].mean())
#print("Standard Deviation:","{0:.4f}".format(handtracker['Payout'].std()))
low =handtracker['Payout'].mean()-1.96*handtracker['Payout'].std()/math.sqrt(hands)
high = handtracker['Payout'].mean()+1.96*handtracker['Payout'].std()/math.sqrt(hands)
print("95% Confidence Interval:","{0:.4f}".format(low)," to " ,"{0:.4f}".format(high))
print(handtracker['Payout'].value_counts().sort_index())
return handtracker['Payout'].sum()
Here is the code to run the simulation multiple times:
#master run for "true dist"
runSim(printOut = True,bet = 1,bj_pay = 1.5,hands = 100000)
Here is the code to run the simulation for a given year:
#simulation of a night
nightly_pay = []
for night in range(52):
nightly_pay.append(runSim(printOut = False,bet = 20,bj_pay = 1.5,hands = 100))
win_sum = 0
loss_sum =0
for pay in nightly_pay:
if pay> 0:
win_sum = win_sum+ pay
else:
loss_sum = loss_sum+pay
print("Total Payout: ",sum(nightly_pay))
print("Weekly Average: ",sum(nightly_pay)/52)
print("Winning Nights: ",sum(num >= 0 for num in nightly_pay))
print("Winning Nights Average: ",win_sum/sum(num >= 0 for num in nightly_pay))
print("Loosing Nights: ",sum(num <= 0 for num in nightly_pay))
print("Loosing Nights Average: ",loss_sum/sum(num <= 0 for num in nightly_pay))
print("History: ",nightly_pay)