前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >All in 凉凉!德州赔率怎么算?我用Python来搞定

All in 凉凉!德州赔率怎么算?我用Python来搞定

作者头像
量化投资与机器学习微信公众号
发布2020-05-14 17:55:35
发布2020-05-14 17:55:35
3.3K00
代码可运行
举报
运行总次数:0
代码可运行

编译:1+1=6

1

介绍

在今天的推文中,公众号将向大家展示如何在Python中表示基本的扑克元素,例如“手牌”和“组合牌”(Hands、Combos),以及如何计算扑克赔率,即在无限注德州扑克中获胜/平局/失败的可能性。我们将使用Poker包来表示手牌、组合和区间。

https://poker.readthedocs.io/en/latest/index.html

首先导入相关库文件:

代码语言:javascript
代码运行次数:0
运行
复制
from poker import Range
from poker.hand import Combo

import holdem_calc
import holdem_functions

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from IPython.core.display import display, HTML

hero_odds = []
hero_range_odds = []

2

翻牌(Flop)

K♠、J♣

我们将使用poker.hand来构造我们的手牌:

代码语言:javascript
代码运行次数:0
运行
复制
hero_hand = Combo('KsJc')
print(hero_hand)

我们不清楚翻牌前发生的事情以及我们的位置。我们只知道翻牌前有加注,而翻牌后只剩下两名玩家:我们自己和另外一个人。

我们现在领先了。翻牌出现了:

Q♣、10♠、J♠

是的,顺子!

假设没我们有事先了解对方的牌型,让我们计算翻牌后的赔率:

代码语言:javascript
代码运行次数:0
运行
复制
flop = ["Qc", "Th", "9s"] 
board = flop
villan_hand = None 
exact_calculation = True 
verbose = True 
num_sims = 1 
read_from_file = None 

odds = holdem_calc.calculate_odds_villan(board, exact_calculation, 
                            num_sims, read_from_file , 
                            hero_hand, villan_hand, 
                            verbose, print_elapsed_time = True)

Holdem_calc中的函数calculate_odds_villan计算出特定的德州扑克某手牌获胜的概率。通过运行蒙特卡罗方法可以估算出该概率,也可以通过模拟所有可能的手牌来精准计算。快速计算翻牌后的精准赔率,因此在这里我们不需要蒙特卡罗近似值。这是我们的赔率:

代码语言:javascript
代码运行次数:0
运行
复制
hero_odds.append(odds[0]['win'])

odds[0]
{'tie': 0.04138424018164999,
 'win': 0.9308440557284221,
 'lose': 0.027771704089927955}

此时,我们感觉还不错。与随机牌相比,我们只有2.77%的机会输,获胜的机会超过93%,这很乐观。

考虑到翻牌前有加注,对反很可能会有一些东西。我们称这种可能的手牌为一个区间。这是我们根据几个因素(包括对反的举止、位置、赌注大小等)做出的推论。该推论导致我们假设对反可能拥有一组手牌:

  • 一对7或更好
  • A /10或更好
  • K/J或更好

我们可以使用Class Range来表示该区间,如下所示:

代码语言:javascript
代码运行次数:0
运行
复制
villan_range = Range('77+, AT+, KJ+')
display(HTML(villan_range.to_html()))
print("#combo combinations:" + str(len(villan_range.combos)))

144

这使我们对手的牌组合从 51*52–1=2651 减少到 144 种组合。现在假设对手的区间来计算我们的赔率。

代码语言:javascript
代码运行次数:0
运行
复制
items = [holdem_calc.calculate_odds_villan(board, exact_calculation, 
                            num_sims, read_from_file , 
                            hero_hand, villan_hand, 
                            verbose, print_elapsed_time = False) for villan_hand in villan_range.combos]

odds = {}
[odds.update({odd_type: np.mean([res[0][odd_type] for res in items if res])}) for odd_type in ["tie", "win", "lose"]]
代码语言:javascript
代码运行次数:0
运行
复制
hero_range_odds.append(odds['win'])

odds
{'tie': 0.11423324150596878,
 'win': 0.8030711151923272,
 'lose': 0.08269564330170392}

在给定假定的区间内,我们的获胜几率从93%下降至80%。但是,损失8.2%的可能性仍然很低,但是我们应该打赌吗?我们绝对希望对手继续比赛并且不弃牌。但是他在翻牌后有一手好牌的可能性有多大?让我们看看如果我们继续玩到最后,他下一手牌的几率有多大。

代码语言:javascript
代码运行次数:0
运行
复制
for hand_ranking in holdem_functions.hand_rankings:
    print(hand_ranking +": " + str(np.mean([res[1][1][hand_ranking] for res in items if res])))
    
High Card: 0.06978879706152433
Pair: 0.3662891541679421
Two Pair: 0.23085399449035812
Three of a Kind: 0.09733700642791548
Straight: 0.18498112437506373
Flush: 0.0040608101214161816
Full House: 0.04205693296602388
Four of a Kind: 0.004560759106213652
Straight Flush: 2.0406081012141617e-05
Royal Flush: 5.101520253035404e-05

如果我们继续玩到河牌(第五张公共牌),对反就有很好的机会做出:

  • 一对(36%)
  • 两对(23%)

他极有可能命中:

  • 顺子(18%)
  • 一盘-暗三条(9.7%)
  • 满堂-三条+一对(4%)

由于对反很有可能拥有合理的手牌,因此我们决定下高注,大约底池的2/3。

3

转牌(Turn)

回合了来:

2♦

平局!

代码语言:javascript
代码运行次数:0
运行
复制
turn= ["2d"]
board = flop + turn
villan_hand = None

odds = holdem_calc.calculate_odds_villan(board, exact_calculation, 
                            num_sims, read_from_file , 
                            hero_hand, villan_hand, 
                            verbose, print_elapsed_time = True)
hero_odds.append(odds[0]['win'])

print(odds[0])

Time elapsed:  0.5410661697387695
{'tie': 0.0233201581027668, 'win': 0.9677206851119895, 'lose': 0.008959156785243741}

假设对手的牌是随机的,那么我们现在有96%的获胜几率。

但是,考虑到我们假定的对手区间,我们的获胜几率现在从翻牌时的80%上升到86%。我们再次下注,对手跟注,河牌来了。

代码语言:javascript
代码运行次数:0
运行
复制
items = [holdem_calc.calculate_odds_villan(board, exact_calculation, 
                            num_sims, read_from_file , 
                            hero_hand, villan_hand, 
                            verbose, print_elapsed_time = False) for villan_hand in villan_range.combos]

odds = {}
[odds.update({odd_type: np.mean([res[0][odd_type] for res in items if res])}) for odd_type in ["tie", "win", "lose"]]

hero_range_odds.append(odds['win'])
odds
{'tie': 0.10123966942148759,
 'win': 0.8615702479338843,
 'lose': 0.0371900826446281}

4

河牌(River)

K♣

河牌的梅花K使顺子更容易被对手抓住。所以这对我们来说是个坏消息。

现在,我们对随机牌的胜算从96%降至约87%。但是我仍然只是以1.2% 的极低概率输掉了比赛。

好吧,还有另外一个因素。对手在翻牌和河牌都跟我们下了大赌注。他可能有比我们想像的更好的牌。那么,我们应该调整我们的假设区间。

现在,我们认为对手不再拥有77或88的组合,否则,鉴于我们的高赌注,他不会走那么远。我们认为他可能要搭配99或更好的一对,才能与99、10或QQ配对。他可能还会有JJ、KK或者AA。由于所谓的隐含赔率,我们决定保留Ace 10或更好和King Jack或更好的组合。隐含赔率是对如果你打出一笔钱可以从投注中赢取多少钱的估计。因此,对手可能希望打一场平局。因此,我们将对手的更新区间定义如下:

代码语言:javascript
代码运行次数:0
运行
复制
villan_range = Range('99+, AT+, KJ+') 
display(HTML(villan_range.to_html()))
print("#combo combinations:" + str(len(villan_range.combos)))

现在,对手的组合数从144降低了132。让我们计算更新后的赔率。

代码语言:javascript
代码运行次数:0
运行
复制
tems = [holdem_calc.calculate_odds_villan(board, exact_calculation, 
                            num_sims, read_from_file , 
                            hero_hand, villan_hand, 
                            verbose, print_elapsed_time = False) for villan_hand in villan_range.combos]

odds = {}
[odds.update({odd_type: np.mean([res[0][odd_type] for res in items if res])}) for odd_type in ["tie", "win", "lose"]]

hero_range_odds.append(odds['win'])
odds
{'tie': 0.12, 'win': 0.72, 'lose': 0.16}

现在,我们有72%的胜算(从86%下降),失利几率从3.7%增加到16%。我们决定检查一下,对手全押,下注大约底池的70%。

一个基本和标准的河牌策略:

1、用你最薄弱的资产作为河牌虚张声势。

2、用你最强的资产作为价值押注(Value Bet)

3、用中的摊牌价值(Show down value,简称SDV)检查牌局,以期达到摊牌。

代码语言:javascript
代码运行次数:0
运行
复制
for hand_ranking in holdem_functions.hand_rankings:
    print(hand_ranking +": " + str(np.mean([res[1][1][hand_ranking] for res in items if res])))
    
High Card: 0.0
Pair: 0.5066666666666667
Two Pair: 0.08
Three of a Kind: 0.13333333333333333
Straight: 0.28
Flush: 0.0
Full House: 0.0
Four of a Kind: 0.0
Straight Flush: 0.0
Royal Flush: 0.0

根据赔率直方图,我们可以将对手的可能手分为3种类型:

1、虚张声势:他持有(高牌,对子)的几率为60.66%。

2、SDV:他持有(两对)的几率为0.8%。

3、最强持牌:他持有(三张相同的牌-顺子)的几率为41.33%。

对手的全押是有道理的。所以在这里,所以我认为他要么是在虚张声势,比如因为错过了一场平局,要么就是在胡说八道,这是一个价值押注。虚张声势或价值押注的基本策略有时被称为两极化押注。 这就是对手在做的事。

回顾每种类型的概率(虚张声势、SDV、价值押注),我们基本上应该至少赢得60.66%,这是一个保守的衡量标准,因为对手可能会价值押注三倍。但是我们应该跟注吗?

这是另一个称为底池赔率(Pot Odds)的概念。你可能会经常听大家常说到。跟注的成败比是3:1,遭遇对手1.5:1的全下,Pot Odds 在德州扑克里都是以比例的形式出现。

池赔率是指相对于底池大小进行下注的价格,它是期望回报与面临风险的比例的数学表达式。数学公式就是:

风险 : 回报

举个例子:底池Pot 80,对手加注40,此时的Pot Odds就是40:(40+80)=3:1,意思是你要跟注40,风险40你才能赢得

总而言之,如果我们赢得底池的概率大于底池限注价格和底池大小之间的比率,我们应该跟注。

那么对于这把牌局:

1、胜算 ≥ 60.66%(保守)

2、跟注价格 = 0.7 * 底池大小

3、跟注后底池大小 =(1 + 0.7 + 0.7)* 底池大小

4、底池赔率 = 跟注价格 跟注后底池大小 = 29%

我们获胜的机会至少是底池赔率的两倍。因此,我们继续跟注。结果如何?对手是:

A、J

呵呵!

4

结语

下面,我们向读者展示了我们的获胜几率是如何从翻牌变成转牌,然后是河牌(假设对手玩家随机出牌)。

代码语言:javascript
代码运行次数:0
运行
复制
df_odds = pd.DataFrame(
{"board" : ["flop","turn", "river"],
"hero_odds" : hero_odds,
"hero_range_odds" : hero_range_odds,
}

index = [1, 2, 3])
In [22]:
ax = plt.gca()
df_odds.plot(kind='line',x='board',y='hero_odds', marker='o', ax=ax)
df_odds.plot(kind='line',x='board',y='hero_range_odds',color='red' ,marker='o', ax=ax)
plt.show()

我们观察到,即使最终结果不利于我们,我们还是最有可能赢得这一平手牌。这就是为什么扑克玩家说:

你应该专注于做出的决定,而不是所取得的结果!

当然,本文中的所有分析都假设了一些区间和基本的扑克策略,这些策略构成了我们在玩游戏时的思维模型,并用Python实现。这手牌有很多玩法。我们也犯了一些错误。例如,在翻牌前加注的情况下,低估了对反持有A、J的机会。

希望大家在牌桌上有更好的表现!

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-05-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 量化投资与机器学习 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档