假想有一個小鎮住著 1000 人,每個人每週都會獲得當地幣值十元的淨收入。所以一個人十週共可存下 100 元,一年五十二週就是 520 元,沒有任何人例外。

這個小鎮最大的娛樂就是每個週末的賭馬了,每場比賽固定都會有兩匹馬參賽,勝率各為 50%,而且沒有人作弊或者具備內線消息,總之,押對的機率大概就和擲一枚公平硬幣一樣。

任何人只要出得起 1 元即可下注,押錯了就拿不回賭金,而押對的人則按照下注比例分配所有金錢。賽馬場完全不會抽成做手續費或選手獎金,純粹是非營利的大眾娛樂。舉例來說,假如下注兩匹馬的金額剛好為 5:4,有位鎮民押 1 號 5 元,如果他猜對了就可以拿回 9 元,猜錯了則一毛也拿不到。

在這個小鎮裡,大家的個性完全一致,只會拿自己總財產的十分之一下注。如果一個人財產的十分之一無法到達 1 元的下注門檻,那麼他寧可不賭,也不會掏更大的比例出來。

若今天每位鎮民的財產都是 0,今後每週都如上述得到 10 元的淨收入,而且會拿出總財產的十分之一賭馬,那麼 600 週之後整個小鎮的財產分佈會是什麼樣子呢?

這裡提示幾點明顯的事實:

  • 每週淨收入 10 元如果都存下來,600 週之後一位鎮民就會有 6000 元。
  • 賭馬只會使金錢在鎮內流動,不會使小鎮的總資產增減。因此不論 600 週之後鎮民的財產如何分佈,全鎮平均財產一定是 6000 元。
  • 賭馬的勝率大概約為 50%,有些人高一點,有些人低一點。數學告訴我們,經過 600 週之後鎮民賭勝的次數大致上會呈現二項式分佈,且多數人的勝率不會離 50% 太遠。(如果讀者沒學過二項式分佈,可以暫時想成一種像鐘型曲線的分佈)

基於好玩,所以我用 python 寫了一個小程式來模擬,我把程式附在最後讓有興趣的人玩玩,不過即使讀者不懂 python 也無礙於閱讀下面內容。

我做了很多次實驗,這裡舉其中一次實驗結果為例,畫成分佈圖。我發現最富有的一小撮人離群體太遠,為了避免圖形過於分散,所以採用下列方式處理:將財產超過前 5% 平均值的人全部劃分為一個等級,這個等級內的人財富差異其實很大。然後將前 5% 平均值 - 最貧窮分成十五個等差級距。

distr.png  

一些基本的數據:

  • 大約 770 人財產不到平均值 6000 元。這意味著,即使規則看起來很公平,如果有人選擇不賭,竟可以穩穩贏過 77% 的人。
  • 最富有的 20% 人口總共擁有小鎮 67% 的總財富,比另外 80% 的人總財產還多。
  • 最富有的 20% 人口的總財產,是最貧窮 20% 人口總財產的 23 倍。
  • 最富有的三個人財產分別為 127044.13、126577.76、115715.43
  • 最貧窮的三個人財產分別為 242.78、309.25、318.17
  • 勝率與財產的相關係數約為在 4.5 ~ 5.5 之間,而勝率與排名的相關係數則約為 7.1。
  • 第 500 週最富有的 100 人中,有 53 人到了第 600 週也在最富有的 100 人排行榜內。做過多次實驗後我發現這個數字挺穩定的,大約都在 1/2 上下。不過其他階層重複的人數就少了很多,換句話說,其他階層流動比較頻繁。

隨機挑 5 個人的財產變化如下圖,接近八成的人財產低於平均,跟這張圖是蠻吻合的,不過除此之外看不太出什麼規則。

trace_all.png  

另外我又從前 50 名當中隨機挑 5 個人,他們到後來都遠遠把平均拋到後方,不過似乎沒有什麼共同點。

trace_rich50.png  

雖然在動手實驗之前,我對結果會是什麼樣子已經有粗略的概念,但另一方面如此「平等」的規則可以造成這麼懸殊的落差,還是讓人感到有點違反直覺。

前述的的規則有點複雜,我還沒想到比較精確的分析方式,也不知是否有現成的隨機過程模型。原問題簡化之後,大致可以對這個趨勢做出一點解釋。

當財富累積到一定程度之後,每週收入對總財產的影響相對降低,所以賽馬對財產分佈具有決定性的影響。長期下來,押在兩匹馬的資金大致上是概等的,在這個賠率下,一個人贏的時候他的財產大約會變為原來的 1.1 倍,而輸的話財產大約會變為原來的 0.9 倍。注意這只是平均的情形,和實際情況有相當出入。

假設贏十次、輸十次的話,財產約變為原來的 (0.9 ^ 10) * (1.1 ^ 10) = 0.9044 倍。

注意到了嗎? 0.9 * 1.1 < 1。也就是說贏的場次必須比輸的場次多到某個程度,財富才有可能正成長。根據簡單的計算,勝率必須達到 0.53 才開始打平。

我們知道整體勝率大致上呈現二項式分佈,平均是 0.5,這意味著長期下來大部分的人所得低於期望值,而且玩的次數越多越靠近零。同樣是勝率 0.5,贏五十次、輸五十次的話,財產約變為原來的 (0.9 ^ 50) * (1.1 ^ 50) = 0.6 倍。低於期望值的部份就跑到勝率超過 0.53 的少數人身上。

這只是大致上的趨勢,由於收入是逐週獲得,因此實際上不會一下子就依照上述的分佈跑。還有些因素會帶來一些變化,例如倒楣鬼可能贏得時候不巧賠率都很低,輸倒是都很扎實。讓我覺得有趣的還有參加賽馬的門檻,原本我以為提高這個門檻會阻礙窮人利用賽馬翻身,實驗幾個數值之後我發現提高門檻其實會防止最窮的一群人輸到脫褲。

有一些因素我還沒試,例如是讓賽馬場抽稅,然會平均給每個人會如何呢?累進稅率呢?留給有興趣的人去玩玩吧。


import pickle
import random
from sets import Set

class Player:
    def __init__(self, id, init=0, riskTaking=0.1):
        self.id = id
        self.riskTaking = riskTaking
        self.money = init
        self.gameCount = 0
        self.winCount = 0

    def joinGame(self, game):
        stake = self.money * self.riskTaking
        game.add(self, self.bet(), stake)

    def bet(self):
        return 1 if random.random() > 0.5 else 0

    def snapshot(self):
        winRate = float(self.winCount) / self.gameCount if self.gameCount > 0 else 0
        return (self.id, self.money, winRate)

class GamePool:
    def __init__(self, thresh=1):
        self.thresh = thresh
        self.booking = [[], []]
        self.money = [0, 0]
        self.tax = 0

    def add(self, player, betting, stake):
        if stake >= self.thresh:
            self.booking[betting].append((player, stake))
            self.money[betting] += stake
            player.money -= stake
            player.gameCount += 1

    def run(self):
        random.seed()
        winner = 1 if random.random() > 0.5 else 0

        if self.money[winner] <= 0:
            return

        odd = (self.money[0] + self.money[1]) / self.money[winner]
        for player, stake in self.booking[winner]:
            player.money += stake * odd
            player.winCount += 1



if __name__ == '__main__':
    popSize = 1000
    maxEpoch = 600
    initMoney = 0
    epochIncome = 10
    redistribution  = 0
    gameThresh = 1

    recordList = []

    pop = [Player(i, init=initMoney) for i in xrange(0, popSize)]

    for epoch in xrange(1, maxEpoch + 1):
        for player in pop:
            player.money += epochIncome + redistribution

        game = GamePool(thresh=gameThresh)
        random.seed()

        for player in pop:
            player.joinGame(game)
        game.run()
        redistribution = game.tax / popSize

        if epoch % 10 == 0:
            snapshots = [player.snapshot() for player in pop]
            recordList.append((epoch, snapshots))

    file = open("recordList.dat", "w")
    pickle.dump(recordList, file)
    file.close()

novus 發表在 痞客邦 PIXNET 留言(1) 人氣()


留言列表 (1)

發表留言
  • 訪客
  • 有興趣
    已以網址連結方式轉貼到個人的facebook,如有不妥請告知
  • 如果有討論意見也歡迎回饋

    novus 於 2014/07/30 10:17 回覆