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 pdfrom random import randrangeimport 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 = 0loss_sum =0for 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)