社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  Python

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

量化投资与机器学习 • 4 年前 • 688 次点击  


标星★置顶公众号     爱你们   

编译:1+1=6


1

介绍 

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


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


首先导入相关库文件:


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来构造我们的手牌:


hero_hand = Combo('KsJc')
print(hero_hand)


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


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


Q♣、10、J


是的,顺子!


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


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


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来表示该区间,如下所示:


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 种组合。现在假设对手的区间来计算我们的赔率。


items = [holdem_calc.calculate_odds_villan(board, exact_calculation, 
                            num_sims, read_from_file , 
                            hero_hand, villan_hand, 
                            verbose, print_elapsed_time = Falsefor 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.11423324150596878,
 'win'0.8030711151923272,
 'lose'0.08269564330170392}


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


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♦


平局!


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%。我们再次下注,对手跟注,河牌来了。


items = [holdem_calc.calculate_odds_villan(board, exact_calculation, 
                            num_sims, read_from_file , 
                            hero_hand, villan_hand, 
                            verbose, print_elapsed_time = Falsefor 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或更好的组合。隐含赔率是对如果你打出一笔钱可以从投注中赢取多少钱的估计。因此,对手可能希望打一场平局。因此,我们将对手的更新区间定义如下:


villan_range = Range('99+, AT+, KJ+'
display(HTML(villan_range.to_html()))
print("#combo combinations:"  + str(len(villan_range.combos)))


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


tems = [holdem_calc.calculate_odds_villan(board, exact_calculation, 
                            num_sims, read_from_file , 
                            hero_hand, villan_hand, 
                            verbose, print_elapsed_time = Falsefor 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)检查牌局,以期达到摊牌。


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你才能赢得$120的回报。



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


那么对于这把牌局:


1、胜算 ≥ 60.66%(保守)


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


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


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


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


A、J


呵呵!


4

结语

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


df_odds = pd.DataFrame(
{"board" : ["flop","turn""river"],
"hero_odds" : hero_odds,
"hero_range_odds" : hero_range_odds,
}

index = [123])
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的机会。


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


牌品见人品!


量化投资与机器学习微信公众号,是业内垂直于Quant、MFE、Fintech、AI、ML等领域的量化类主流自媒体。公众号拥有来自公募、私募、券商、期货、银行、保险资管、海外等众多圈内18W+ 关注者。每日发布行业前沿研究成果和最新量化资讯。

你点的每个“在看”,都是对我们最大的鼓励
Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/62814
 
688 次点击