基于Python的人工股票市場(chǎng)模擬

為了對(duì)證券所的交易制度和股票的價(jià)格的機(jī)理進(jìn)行更加深刻的理解们童,在機(jī)緣巧合之下,我用python參考一篇論文【1】構(gòu)建了一個(gè)模擬的人工股票市場(chǎng)绘面,在這里給大家分享一下模型的搭建過(guò)程裁良,以便于感興趣的讀者進(jìn)行更深層次的研究。

1. 研究方法

隨著復(fù)雜性科學(xué)和計(jì)算機(jī)模擬技術(shù)的發(fā)展此叠,自20世紀(jì)90年代起,自上而下的多主體建模(基于主體的建模)方法開(kāi)始逐步被引入金融領(lǐng)域随珠,主要應(yīng)用于金融市場(chǎng)的微觀結(jié)構(gòu)和交易策略的研究灭袁,并因其依靠數(shù)學(xué)建模和人工模擬的方法創(chuàng)新成為金融市場(chǎng)研究的熱點(diǎn)。

本文擬基于多主體建模的方法窗看,自下而上地構(gòu)建貼近實(shí)際的人工股票市場(chǎng)茸歧,并且含有投資者的財(cái)富信息,以便研究股票市場(chǎng)中投資者的財(cái)富分布和演化显沈,或者對(duì)整體財(cái)富分布進(jìn)行基尼系數(shù)的測(cè)算软瞎。

2. 模型設(shè)計(jì)

為了簡(jiǎn)化模型的設(shè)計(jì)過(guò)程,假定市場(chǎng)中只有一支可以和現(xiàn)金交換的股票拉讯,股票在市場(chǎng)中的流通總數(shù)不變且無(wú)紅利發(fā)放涤浇,現(xiàn)金的收益率為0(即不存在時(shí)間價(jià)值,無(wú)法利用其持有的資金進(jìn)行其他投資)魔慷。

2.1 基本市場(chǎng)框架

中國(guó)證券交易采用計(jì)算機(jī)集合競(jìng)價(jià)和連續(xù)競(jìng)價(jià)兩種方式只锭,由于集合競(jìng)價(jià)在每日交易中占據(jù)的時(shí)間較短,并且參與主體也有所不同院尔,所以只關(guān)注每日交易中的連續(xù)競(jìng)價(jià)過(guò)程蜻展,直接用前一天的收盤價(jià)作為當(dāng)天開(kāi)盤價(jià)的近似,并用最后一筆成交價(jià)格作為當(dāng)日的收盤價(jià)格邀摆。

具體地纵顾,關(guān)注人工股票市場(chǎng)的T個(gè)交易日,在每個(gè)交易日隧熙,市場(chǎng)中的N個(gè)投資者按隨機(jī)順序進(jìn)入片挂,并根據(jù)自己對(duì)股票未來(lái)價(jià)格的預(yù)測(cè)提交訂單。假定每個(gè)投資者每日有且僅有一次機(jī)會(huì)進(jìn)入市場(chǎng)贞盯,這樣以投資者的申報(bào)為間隔音念,每個(gè)交易日包含N個(gè)交易區(qū)間。市場(chǎng)根據(jù)投資者的申報(bào)按照價(jià)格優(yōu)先與時(shí)間優(yōu)先的順序撮合成交躏敢。未成交的訂單暫存在當(dāng)日的指令簿中闷愤,在交易日結(jié)束時(shí),仍未成交的訂單會(huì)被清空件余。

2.2 交易參與主體——異質(zhì)投資者

每日有N位投資者參與市場(chǎng)交易讥脐,與傳統(tǒng)金融理論所假設(shè)的同質(zhì)代理人不同,這里強(qiáng)調(diào)了三個(gè)異質(zhì)主體啼器,主要體現(xiàn)在根據(jù)自身的不同稟賦對(duì)股票未來(lái)價(jià)格有不同的預(yù)測(cè)及策略行為旬渠。

2.2.1 投資者的初始稟賦

投資者初始稟賦的不同在模型的初始設(shè)定時(shí)主要體現(xiàn)在兩個(gè)方面,一是投資者的風(fēng)險(xiǎn)厭惡程度不同端壳,以α(i)標(biāo)識(shí)告丢。它決定了投資者每次申購(gòu)時(shí)拿出多少比例的現(xiàn)有可利用財(cái)富進(jìn)行買賣報(bào)量,該比例假設(shè)服從均勻分布 α(i)~U[α1损谦,α2]; 二是投資者對(duì)信息資源的獲取與處理能力不同岖免。大致分為3類:1) 能夠獲取較為準(zhǔn)確的信息并有 較強(qiáng)處理能力的機(jī)構(gòu)投資者,體現(xiàn)在他們既能夠得到上市公司股票的較為準(zhǔn)確的價(jià)值信息vt照捡,又掌握通過(guò)以往市場(chǎng)信息推導(dǎo)價(jià)格變動(dòng)趨勢(shì)的技術(shù)手段颅湘。2) 僅能夠利用股票價(jià)格的歷史信息進(jìn)行處理并推導(dǎo)未來(lái)價(jià)格變動(dòng)趨勢(shì)的 趨勢(shì)交易者。現(xiàn)實(shí)中利用市場(chǎng)歷史信息進(jìn)行技術(shù)交易的種類繁多栗精,交易策略也很豐富闯参。這里僅采用最常見(jiàn)的趨勢(shì)交易作為初步探討。3)不以股市盈利為目標(biāo)的噪聲交易者悲立,該類交易者往往忽視股票市場(chǎng)中的所有信息赢赊,他們參與市場(chǎng)是為了流動(dòng)性或其它便利性的需求。 事實(shí)上级历,不同投資者進(jìn)入市場(chǎng)時(shí)的財(cái)富也不盡相同释移,而不同的財(cái)富會(huì)使得投資者可以進(jìn)行的交易種類(如有些交易會(huì)有資金的門檻)及摩擦費(fèi)用(如傭金、稅收等)有所差異寥殖,這自然會(huì)增加模型的維度及復(fù)雜性玩讳。為了簡(jiǎn)化,這里賦予所有投資者相同的財(cái)富(由現(xiàn)金與股票構(gòu)成)嚼贡。

2.2.2 投資者的策略行為

2.2.2.1投資者對(duì)未來(lái)價(jià)格的預(yù)測(cè)

依據(jù)投資者對(duì)信息資源的獲取稟賦與處理能力的不同熏纯,分別闡述機(jī)構(gòu)投資者、趨勢(shì)交易者與噪聲交易者3類不同投資者對(duì)未來(lái)價(jià)格的預(yù)測(cè)方式粤策。 第1類是機(jī)構(gòu)投資者樟澜。他們擁有最準(zhǔn)確的信息資源———股票的價(jià)值vt,可以進(jìn)行價(jià)值投資。該投資理念最早由格雷厄姆提出秩贰,其核心在于對(duì)企業(yè)價(jià)值的研究霹俺,認(rèn)為股票價(jià)格會(huì)圍繞其自身價(jià)值上下波動(dòng),即當(dāng)股價(jià)低于(高)于價(jià)值時(shí)將會(huì)上漲(下跌)毒费。據(jù)此丙唧,第i個(gè)機(jī)構(gòu)投資者在第t日采取價(jià)值投資時(shí)對(duì)當(dāng)日股票收盤價(jià)的預(yù)測(cè)pe 為式(1)所示:

式(1)

假設(shè)股票的價(jià)值vt服從幾何隨機(jī)游走過(guò)程:

ηt為服從正態(tài)分布的隨機(jī)噪聲
d(i)1標(biāo)識(shí)不同機(jī)構(gòu)投資者對(duì)價(jià)格回復(fù)到價(jià)值速度快慢的不同看法,假設(shè)其服從均勻分布
e(i)t標(biāo)識(shí)機(jī)構(gòu)投資者在獲取價(jià)值信息時(shí)所受到的隨機(jī)干擾觅玻,假設(shè)其服從

的正態(tài)分布想际。然而,現(xiàn)階段中國(guó)股票市場(chǎng)還不夠成熟溪厘,很多機(jī)構(gòu)投資者也會(huì)捕捉某些明確的市場(chǎng)走勢(shì)胡本,順勢(shì)而為,采取趨勢(shì)追隨策略以獲取短期收益畸悬。據(jù)此侧甫,該文認(rèn)為機(jī)構(gòu)投資者有時(shí)也會(huì)根據(jù)市場(chǎng)走勢(shì)通過(guò)追隨趨勢(shì)預(yù)測(cè)價(jià)格,具體形式 如式(2)所示傻昙。

式(2)

類似地闺骚,

為機(jī)構(gòu)投資者追隨趨勢(shì)的強(qiáng)烈程度(d(i)2>0)。
為第i個(gè)機(jī)構(gòu)投資者所記錄的前L(i)1期的平均移動(dòng)價(jià)格妆档,其中僻爽,L(i)1為機(jī)構(gòu)投資者用來(lái)追蹤價(jià)格趨勢(shì)所選取的移動(dòng)均線的不同時(shí)間尺度,假設(shè)其服從均勻分布:
而其誤差為服從

的正態(tài)分布贾惦,反映機(jī)構(gòu)投資者對(duì)自身分析的趨勢(shì)信息的把握程度胸梆。需要注意的是,這里的方差為時(shí)變的方差须板,代表投資者對(duì)其關(guān)注的近期價(jià)格趨勢(shì)在時(shí)間尺度L(i)1內(nèi)的市場(chǎng)波動(dòng)性的估計(jì)(如式(3)所示)碰镜。一般來(lái)說(shuō),當(dāng)市場(chǎng)波動(dòng)性大時(shí)习瑰,投資者對(duì)自身預(yù)測(cè)的把握程度會(huì)降低绪颖,其給出的價(jià)格預(yù)測(cè)的區(qū)間會(huì)更加寬泛。
式(3)

既然機(jī)構(gòu)投資者在獲取上市公司的價(jià)值信息和分析市場(chǎng)的價(jià)格趨勢(shì)上都有能力與優(yōu)勢(shì)甜奄,那么具體到每日交 易柠横,機(jī)構(gòu)投資者選擇哪種方式來(lái)形成價(jià)格預(yù)測(cè)呢?一個(gè)最直觀的假設(shè)是看哪種預(yù)測(cè)方式給出的信號(hào)最為強(qiáng)烈或在近期表現(xiàn)得最好课兄。具體地牍氛,采取式(4)的形式。


式(4)

機(jī)構(gòu)投資者對(duì)預(yù)測(cè)形式的選擇主要體現(xiàn)在g(i)t這個(gè)二值函數(shù)上烟阐,其具體形式為

其中:

以價(jià)值投資的角度衡量?jī)r(jià)格偏離價(jià)值的深度搬俊,它決定了價(jià)值投資的獲利空間有多大紊扬;而Score(i)t則以技術(shù)分析的角度通過(guò)計(jì)算追隨趨勢(shì)的加權(quán)平均累積收益來(lái)記錄其穩(wěn)定獲利的能力,如式(5):
式(5)

其中sign()為一符號(hào)函數(shù)唉擂,當(dāng)前一日收盤價(jià)大于(小于)移動(dòng)均線值時(shí)餐屎,趨勢(shì)追隨者預(yù)測(cè)該趨勢(shì)仍會(huì)持續(xù),給出買入(賣出)的信號(hào)sign = 1(sign = -1)楔敌。而該信號(hào)是否準(zhǔn)確啤挎,要根據(jù)下一期真實(shí)實(shí)現(xiàn)的市場(chǎng)收益 rt = log(pt) - log(p(t-1))來(lái)判斷驻谆。但是因?yàn)楸酒跓o(wú)法知道市場(chǎng)的收盤價(jià)卵凑,因此采用往期數(shù)據(jù)的得分累計(jì)值來(lái)作為當(dāng)期分?jǐn)?shù)的參考。當(dāng)預(yù)測(cè)與真實(shí)價(jià)格同向變動(dòng)時(shí)胜臊,該趨勢(shì)追隨的技術(shù)分析方法獲得數(shù)量為市場(chǎng)收益的正的得分勺卢,表明其前一期的預(yù)測(cè)能夠?yàn)橥顿Y者帶來(lái)正的收益。而由于短期的趨勢(shì)追隨預(yù)測(cè) 易受到市場(chǎng)中隨機(jī)噪聲的影響象对,機(jī)構(gòu)投資者往往關(guān)注于該技術(shù)分析方法獲利的穩(wěn)定性黑忱。所以本文采取加權(quán)平均的 累積收益來(lái)計(jì)分。其權(quán)重 0 < \delta < 1為一貼現(xiàn)因子勒魔,反映越近期的預(yù)測(cè)的準(zhǔn)確程度對(duì)其得分函數(shù)的影響越大甫煞。當(dāng)該累積得分Score(i)t > |ft| 時(shí),表明此時(shí)的市場(chǎng)形勢(shì)下追隨趨勢(shì)可能比價(jià)值投資更有獲利性冠绢,故機(jī)構(gòu)投資者會(huì)采取趨勢(shì)追隨來(lái)形成價(jià)格預(yù)測(cè)抚吠,即 g(i)t = 1 ,式(4)退化到式(2)的形式。反之弟胀,式(4)退化到式(3)的形式楷力。

第2類是趨勢(shì)交易者。他們無(wú)法獲得較為準(zhǔn)確的上市公司的價(jià)值信息(或成本太大)孵户,只能通過(guò)技術(shù)分析的手段依據(jù)近期價(jià)格趨勢(shì)推測(cè)未來(lái)價(jià)格萧朝,其具體形式如式(6):
式(6)

類似的,d(i)3為趨勢(shì)交易者利用趨勢(shì)信息推測(cè)未來(lái)價(jià)格的強(qiáng)烈程度夏哭,其分布假設(shè)為均勻分布检柬。與機(jī)構(gòu)投資者的趨勢(shì)追隨策略不同的是,d(i)3 > 0 表示趨勢(shì)追隨竖配,d(i)3 < 0 表示趨勢(shì)反轉(zhuǎn)(實(shí)際上何址,機(jī)構(gòu)投資者的反轉(zhuǎn)策略是通過(guò)價(jià)值投資來(lái)實(shí)現(xiàn)的)。移動(dòng)平均價(jià)格械念、觀測(cè)尺度头朱、時(shí)變的方差等與第一類投資者采取的分布相同。

第3類是噪聲交易者龄减,他們?yōu)榱藵M足自身流動(dòng)性或風(fēng)險(xiǎn)對(duì)沖的需求進(jìn)入市場(chǎng)项钮,并不通過(guò)股票市場(chǎng)信息來(lái)盈利,其對(duì)價(jià)格的預(yù)測(cè)由當(dāng)日的開(kāi)盤價(jià)(前一日的收盤價(jià))加上一個(gè)正態(tài)分布的隨機(jī)噪聲構(gòu)成。
式(7)

2.2.2.2 投資者的申購(gòu)行為

各類投資者根據(jù)自身的資源稟賦及信息處理能力形成價(jià)格預(yù)測(cè)后烁巫,便進(jìn)入市場(chǎng)參與交易署隘。由于目前中國(guó)證券交易主要由限價(jià)訂單構(gòu)成,所以本文只考慮限價(jià)訂單亚隙,由報(bào)價(jià)和報(bào)量?jī)刹糠謽?gòu)成磁餐。 對(duì)于報(bào)價(jià),當(dāng)投資者預(yù)測(cè)價(jià)格將會(huì)上漲(下跌)時(shí)阿弃,結(jié)合自己的預(yù)測(cè)及下單前觀測(cè)到的市場(chǎng)即時(shí)信息來(lái)進(jìn)行買入(賣出)報(bào)價(jià)诊霹,買(bid(i)t) 賣 (ask(i)t) 報(bào)價(jià)的具體形式分別見(jiàn)式(8),式(9):
式(8)
式(9)

其中渣淳,

為當(dāng)日最近m次的平均成交價(jià)格(現(xiàn)價(jià)脾还,我在模型中將m取1),買賣報(bào)價(jià)的基本原則是當(dāng)投資者作為買(賣)方進(jìn)入市場(chǎng)時(shí)入愧,若觀測(cè)到的最近幾次成交的價(jià)格低(高)于自己對(duì)當(dāng)日收盤價(jià)的預(yù)測(cè)鄙漏,則提交一個(gè)介于近期成交平均價(jià)與自己預(yù)測(cè)價(jià)之間的價(jià)格以提高成交的概率;若觀測(cè)到的最近幾次成交的價(jià)格已經(jīng)高(低)于自己對(duì)當(dāng)日收盤價(jià)的預(yù)測(cè)棺蛛,投資者也不會(huì)提交一個(gè)高(低)于自己預(yù)測(cè)的價(jià)格去買(賣)怔蚌,則會(huì)提交一個(gè)介于昨日收盤價(jià)與自己預(yù)測(cè)價(jià)格之間的價(jià)格等待成交。對(duì)于報(bào)量旁赊,投資者會(huì)根據(jù)自身的風(fēng)險(xiǎn)偏好程度 α(i) 與當(dāng)前可利用的財(cái)富(不考慮買空賣空)來(lái)確定桦踊。這里采用最簡(jiǎn)潔直接的形式——買賣報(bào)量與投資者的風(fēng)險(xiǎn)偏好及財(cái)富成正比,即買入報(bào)量:

賣出報(bào)量:

其中 c(i)(t-1) 與 s(i)(t-1) 分別為投資者在上一期(當(dāng)前可用)的現(xiàn)金財(cái)富與股票財(cái)富彤恶。

2.3 價(jià)格形成——連續(xù)雙向拍賣機(jī)制

各類投資者按隨機(jī)順序進(jìn)入市場(chǎng)下單钞钙,基于連續(xù)雙向拍賣的基本機(jī)制,采用上海證券交易所的規(guī)則來(lái)確定雙方的成交價(jià)格声离。具體地芒炼,進(jìn)入市場(chǎng)的訂單按價(jià)格優(yōu)先和時(shí)間優(yōu)先的準(zhǔn)則在訂單簿中排序,最優(yōu)買入申報(bào)價(jià)格為最 高買入申報(bào)價(jià)格术徊,記為B本刽;最優(yōu)賣出申報(bào)價(jià)格為最低賣出申報(bào)價(jià)格,記為 A赠涮。若新進(jìn)入的買單報(bào)價(jià)>= A, 則以 A 的價(jià)格成交子寓,即 q(現(xiàn)價(jià))= A; 若新進(jìn)入的賣單報(bào)價(jià)<=B,則以B的價(jià)格成交,即q(現(xiàn)價(jià)) = B笋除。若最優(yōu)買入申報(bào)價(jià)格與最優(yōu)賣出申報(bào)價(jià)格相同斜友,則q(現(xiàn)價(jià))= A= B

當(dāng)所有投資者都陸續(xù)進(jìn)入市場(chǎng)完成一次申購(gòu)后垃它,一個(gè)交易日結(jié)束鲜屏。各類投資者根據(jù)各自的成交情況對(duì)自身的現(xiàn)金財(cái)富和股票財(cái)富進(jìn)行清算烹看,見(jiàn)式(10)。
式(10)

其中洛史,p(i)\tau 與 q(i)\tau 分別表示當(dāng)日第i個(gè)投資者在\tau 時(shí)刻成交的成交價(jià)格與成交量惯殊。投資者每日的總財(cái)富為更新后的現(xiàn)金財(cái)富與以當(dāng)日收盤價(jià)結(jié)算的股票財(cái)富之和,即

至此也殖,模型封閉土思。隨著時(shí)間的推進(jìn),模型進(jìn)入下一期的循環(huán)忆嗜。

3. 模擬結(jié)果

假設(shè)每類投資者都有100人己儒,初始財(cái)富都為100股的股票+10000元,股票的初始價(jià)值為100元每股霎褐,且價(jià)值符合隨機(jī)游走過(guò)程址愿,模擬100天的結(jié)果如圖所示:
運(yùn)行100期價(jià)格圖

當(dāng)然该镣,該模型中也包含了各類投資者的財(cái)富變化冻璃,對(duì)模型進(jìn)行適當(dāng)?shù)男薷模€可以考慮T+1制度损合、漲跌停等制度對(duì)財(cái)富分布省艳、價(jià)格分布的影響。這里給出源碼:

# -*- coding: utf-8 -*-
# @Time    : 2019/3/5 16:23
# @Author  : Arron Zhang
# @Email   : 549144697@qq.com
# @File    : Simulation of Stock Market.py
# @Software: PyCharm

import math
import matplotlib.pyplot as plt
from numpy import random
import numpy as np
import pandas as pd
from itertools import chain
from math import e, log
# 構(gòu)造投資者類別嫁审,返回其對(duì)當(dāng)日收盤價(jià)的預(yù)測(cè)


def inv_1(pclose,score, v):
    # 機(jī)構(gòu)投資者價(jià)值預(yù)測(cè)噪聲誤差
    e2 = 0.5
    e2 = random.normal(loc=0.0, scale=e2)
    # 機(jī)構(gòu)投資者預(yù)測(cè)價(jià)格回復(fù)價(jià)值速度
    d1 = random.uniform(0.2, 0.8)
    # 機(jī)構(gòu)投資者預(yù)測(cè)價(jià)格追隨趨勢(shì)程度
    d2 = random.uniform(0.2, 0.8)
    # 計(jì)算N日的均值
    N = random.randint(2, 30)
    ma = np.sum(pclose[-(N+1): -1])/N
    vmean = 0
    for i in range(N):
        a = i+1
        vmean = (pclose[-(a+1)] - ma)**2 + vmean
    mean = vmean/N
    mean = mean**0.5
    e1 = random.normal(loc=0,scale=mean)
    # 貼現(xiàn)因子跋炕,越靠近當(dāng)日對(duì)score的影響越大
    sigma = 0.9
    # 上期收益率
    r = math.log10(pclose[-1]) - math.log10(pclose[-2])
    sign = 0
    if r > 0:
        sign = 1
    elif r <= 0:
        sign = -1
    score = sign * (pclose[-2] - ma) * r + sigma * score
    f = math.log10(v[-2]) - math.log10(pclose[-2])
    g = 0
    if score > abs(f):
        g = 1
    elif score <= abs(f):
        g = 0
    pred = pclose[-2] + g*(d2*(pclose[-2] - ma)+e1) + (1 - g)*(d1*(v[-2] - pclose[-2]) + e2)
    if pred > 0:
        pass
    else:
        pred = 0

    return [pred, score]


def inv_2(pclose):
    # 趨勢(shì)交易者預(yù)測(cè)價(jià)格持續(xù)或反轉(zhuǎn)程度
    d3 = random.uniform(-1.5, 1.5)
    # 計(jì)算N日的均值
    if len(pclose) >= 30:
        N = random.randint(2, 31)
    else:
        N = random.randint(1, len(pclose) + 1)
    ma = np.sum(pclose[-N:]) / N
    vmean = 0
    for i in range(N):
        a = i + 1
        vmean = (pclose[-a] - ma) ** 2 + vmean
    mean = vmean / N
    mean = mean**0.5
    e4 = random.normal(loc=0, scale=mean)
    pred = pclose[-1] + d3*(pclose[-1] - ma) + e4
    if pred > 0:
        pass
    else:
        pred = 0
    return pred


def inv_3(pclose):
    # 噪聲交易者預(yù)測(cè)的噪聲標(biāo)準(zhǔn)差
    e5 = 0.5
    e5 = random.normal(loc=0, scale=e5)
    pred = pclose[-1] + e5
    if pred > 0:
        pass
    else:
        pred = 0
    return pred


# 三類交易者根據(jù)即時(shí)成交價(jià)格形成報(bào)價(jià)
def giveprice_1(pclose, pred, price):
    callprice = 0
    # 交易信號(hào)蒙幻,1 為買入報(bào)價(jià)舞蔽, -1 為賣出報(bào)價(jià)
    tradesign = 0
    if pred > pclose:
        tradesign = 1
        if pred > price:
            callprice = random.uniform(price,pred)
        else:
            callprice = random.uniform(pclose,pred)
    elif pred <= pclose:
        tradesign = -1
        if pred > price:
            callprice = random.uniform(pred, pclose)
        else:
            callprice = random.uniform(pred, price)

    if callprice > 0:
        pass
    else:
        giveprice_1(pclose, pred, price)

    return [callprice, tradesign]


# 生成初始的投資者信息
def generate_origin_info():
    #三類投資者數(shù)量 n_1,n_2,n_3
    n_1 = 100
    n_2 = 100
    n_3 = 100
    # 投資者分類列表
    list_1 = []
    # 投資者資金列表
    list_2 = []
    # 投資者持有的股票數(shù)量列表
    list_3 = []
    # 投資者擁有的財(cái)富列表
    list_4 = []
    # 投資者使用的方法得分
    list_5 = []
    for i in range(n_1):
        list_1.append(1)
    for i in range(n_2):
        list_1.append(2)
    for i in range(n_3):
        list_1.append(3)

    n = n_2 + n_1 + n_3
    for i in range(n):
        list_2.append(10000)
        list_3.append(100)
        list_4.append(20000)
        list_5.append(0)

    dictionary = {'type': list_1, 'money': list_2, 'volume': list_3, 'fortune': list_4, 'score': list_5}
    primeinfo = pd.DataFrame(dictionary)
    return primeinfo


# 執(zhí)行一天的交易過(guò)程
def buy_sold_a_day(investorinfo, pclose, v):
    buy = [[len(investorinfo) + 1, 0, 0]]
    sold = [[len(investorinfo) + 1, 10000, 0]]
    price = []
    price.append(pclose[-1])
    # 按照隨機(jī)順序使得投資者進(jìn)入市場(chǎng)
    rd = []
    for i in range(len(investorinfo)):
        rd.append(i)
    random.shuffle(rd)
    for i in range(len(rd)):
        candidate = investorinfo.iloc[rd[i]]
        # 第一類投資者
        if candidate[0] == 1:
            score = candidate[4]
            # 形成第一類投資者的報(bào)價(jià)典徊,分?jǐn)?shù)
            respond = inv_1(pclose, score, v)
            # 更新分?jǐn)?shù)
            investorinfo.iloc[rd[i], 4] = respond[1]
            preclose = pclose[-1]
            price_now = price[-1]
            pred_1 = respond[0]
            callprice_1 = giveprice_1(preclose, pred_1, price_now)

            # 判斷買入報(bào)價(jià)和賣出報(bào)價(jià)漆腌,1 為買入庙洼,-1為賣出
            if callprice_1[1] == 1:
                # 買入報(bào)量
                money_now = candidate[1]
                if money_now > 0:
                    afa = random.uniform(0.25, 0.85)
                    quant = int(afa * (money_now / callprice_1[0]))
                    q_origin = quant
                    # 最終成交量
                    q = 0
                    sold.sort(key=lambda x: x[1])
                    j = 0
                    # 將買入報(bào)價(jià)寫入買單蔫磨,在完成交易后更新成交量
                    buy.append([rd[i], callprice_1[0], quant])
                    # 尋找所有賣出報(bào)價(jià)低于買入報(bào)價(jià)的賣家報(bào)單祝拯,每完成一筆您没,更新持倉(cāng)量
                    while sold[j][1] <= callprice_1[0]:
                        if sold[j][2] >= quant and quant> 0:
                            sold[j][2] = sold[j][2] - quant
                            q = q + quant
                            if quant > 0:
                                price.append(sold[j][1])
                                investorinfo.iloc[sold[j][0], 1] = investorinfo.copy().iloc[sold[j][0], 1] + quant*sold[j][1]
                                investorinfo.iloc[rd[i], 1] = investorinfo.copy().iloc[rd[i], 1] - quant * sold[j][1]
                                investorinfo.iloc[sold[j][0], 2] = investorinfo.copy().iloc[sold[j][0], 2] - quant
                                quant = 0
                            else:
                                pass
                        elif sold[j][2] < quant and sold[j][2] > 0:
                            quant = quant - sold[j][2]
                            q = q + sold[j][2]
                            if sold[j][2] > 0:
                                price.append(sold[j][1])
                                investorinfo.iloc[rd[i], 1] = investorinfo.copy().iloc[rd[i], 1] - sold[j][2] * sold[j][1]
                                investorinfo.iloc[sold[j][0], 1] = investorinfo.copy().iloc[sold[j][0], 1] + sold[j][2] * sold[j][1]
                                investorinfo.iloc[sold[j][0], 2] = investorinfo.copy().iloc[sold[j][0], 2] - sold[j][2]
                            sold[j][2] = 0
                        if j + 1 >= len(sold):
                            break
                        else:
                            j = j + 1
                    buy[-1][2] = q_origin - q
                    investorinfo.iloc[rd[i], 2] = investorinfo.copy().iloc[rd[i], 2] + q
                else:
                    pass

            elif callprice_1[1] == -1:
                if candidate[2] > 0:
                    afa = random.uniform(0.25, 0.85)
                    quant = int(afa * candidate[2])
                    q_origin = quant
                    # 最終成交量
                    q = 0
                    buy.sort(key=lambda x: x[1], reverse=True)
                    j = 0
                    # 將賣出報(bào)價(jià)寫入賣價(jià)報(bào)單厂僧,在完成交易后更新成交量
                    sold.append([rd[i], callprice_1[0], quant])
                    # 尋找所有買入報(bào)價(jià)高于賣出報(bào)價(jià)的買家報(bào)單扣草,每完成一筆,更新持倉(cāng)量
                    while buy[j][1] >= callprice_1[0]:
                        if buy[j][2] >= quant and quant > 0:
                            buy[j][2] = buy[j][2] - quant
                            q = q + quant
                            if quant > 0:
                                price.append(buy[j][1])
                                investorinfo.iloc[buy[j][0], 1] = investorinfo.copy().iloc[buy[j][0], 1] - quant * buy[j][1]
                                investorinfo.iloc[rd[i], 1] = investorinfo.copy().iloc[rd[i], 1] + quant * buy[j][1]
                                investorinfo.iloc[buy[j][0], 2] = investorinfo.copy().iloc[buy[j][0], 2] + quant
                                quant = 0
                            else:
                                pass
                        elif buy[j][2] < quant and buy[j][2] > 0:
                            quant = quant - buy[j][2]
                            q = q + buy[j][2]
                            if buy[j][2] > 0:
                                price.append(buy[j][1])
                                investorinfo.iloc[buy[j][0], 1] = investorinfo.copy().iloc[buy[j][0], 1] - buy[j][2] * buy[j][1]
                                investorinfo.iloc[rd[i], 1] = investorinfo.copy().iloc[rd[i], 1] + buy[j][2] * buy[j][1]
                                investorinfo.iloc[buy[j][0], 2] = investorinfo.copy().iloc[buy[j][0], 2] + buy[j][2]
                            else:
                                pass
                            buy[j][2] = 0
                        if j + 1 >= len(buy):
                            break
                        else:
                            j = j + 1
                    sold[-1][2] = q_origin - q
                    investorinfo.iloc[rd[i], 2] = investorinfo.copy().iloc[rd[i], 2] - q
                else:
                    pass

        # 第二類投資者
        elif candidate[0] == 2:
            respond = inv_2(pclose)
            # 更新分?jǐn)?shù)
            preclose = pclose[-1]
            price_now = price[-1]
            pred_1 = respond
            callprice_1 = giveprice_1(preclose, pred_1, price_now)

            # 判斷買入報(bào)價(jià)和賣出報(bào)價(jià)颜屠,1 為買入辰妙,-1為賣出
            if callprice_1[1] == 1:
                # 買入報(bào)量
                money_now = candidate[1]
                if money_now > 0:
                    afa = random.uniform(0.25, 0.85)
                    quant = int(afa * money_now / callprice_1[0])
                    q_origin = quant
                    # 最終成交量
                    q = 0
                    sold.sort(key=lambda x: x[1])
                    j = 0
                    # 將買入報(bào)價(jià)寫入買單,在完成交易后更新成交量
                    buy.append([rd[i], callprice_1[0], quant])
                    # 尋找所有賣出報(bào)價(jià)低于買入報(bào)價(jià)的賣家報(bào)單甫窟,每完成一筆密浑,更新持倉(cāng)量
                    while sold[j][1] <= callprice_1[0]:
                        if sold[j][2] >= quant and quant > 0:
                            sold[j][2] = sold[j][2] - quant
                            q = q + quant
                            if quant > 0:
                                price.append(sold[j][1])
                                investorinfo.iloc[sold[j][0], 1] = investorinfo.copy().iloc[sold[j][0], 1] + quant*sold[j][1]
                                investorinfo.iloc[rd[i], 1] = investorinfo.copy().iloc[rd[i], 1] - quant * sold[j][1]
                                investorinfo.iloc[sold[j][0], 2] = investorinfo.copy().iloc[sold[j][0], 2] - quant
                                quant = 0
                            else:
                                pass
                        elif sold[j][2] < quant and sold[j][2] > 0:
                            quant = quant - sold[j][2]
                            q = q + sold[j][2]
                            if sold[j][2] > 0:
                                price.append(sold[j][1])
                                investorinfo.iloc[rd[i], 1] = investorinfo.copy().iloc[rd[i], 1] - sold[j][2] * sold[j][1]
                                investorinfo.iloc[sold[j][0], 1] = investorinfo.copy().iloc[sold[j][0], 1] + sold[j][2] * sold[j][1]
                                investorinfo.iloc[sold[j][0], 2] = investorinfo.copy().iloc[sold[j][0], 2] - sold[j][2]
                            sold[j][2] = 0
                        if j + 1 >= len(sold):
                            break
                        else:
                            j = j + 1
                    buy[-1][2] = q_origin - q
                    investorinfo.iloc[rd[i], 2] = investorinfo.copy().iloc[rd[i], 2] + q
                else:
                    pass

            elif callprice_1[1] == -1:
                if candidate[2] > 0:
                    afa = random.uniform(0.25, 0.85)
                    quant = int(afa * candidate[2])
                    q_origin = quant
                    # 最終成交量
                    q = 0
                    buy.sort(key=lambda x: x[1], reverse=True)
                    j = 0
                    # 將賣出報(bào)價(jià)寫入賣價(jià)報(bào)單,在完成交易后更新成交量
                    sold.append([rd[i], callprice_1[0], quant])
                    # 尋找所有買入報(bào)價(jià)高于賣出報(bào)價(jià)的買家報(bào)單粗井,每完成一筆尔破,更新持倉(cāng)量
                    while buy[j][1] >= callprice_1[0]:
                        if buy[j][2] >= quant and quant > 0:
                            buy[j][2] = buy[j][2] - quant
                            q = q + quant
                            if quant > 0:
                                price.append(buy[j][1])
                                investorinfo.iloc[buy[j][0], 1] = investorinfo.copy().iloc[buy[j][0], 1] - quant * buy[j][1]
                                investorinfo.iloc[rd[i], 1] = investorinfo.copy().iloc[rd[i], 1] + quant * buy[j][1]
                                investorinfo.iloc[buy[j][0], 2] = investorinfo.copy().iloc[buy[j][0], 2] + quant
                                quant = 0
                            else:
                                pass
                        elif buy[j][2] < quant and buy[j][2] > 0:
                            quant = quant - buy[j][2]
                            q = q + buy[j][2]
                            if buy[j][2] > 0:
                                price.append(buy[j][1])
                                investorinfo.iloc[buy[j][0], 1] = investorinfo.copy().iloc[buy[j][0], 1] - buy[j][2] * buy[j][1]
                                investorinfo.iloc[rd[i], 1] = investorinfo.copy().iloc[rd[i], 1] + buy[j][2] * buy[j][1]
                                investorinfo.iloc[buy[j][0], 2] = investorinfo.copy().iloc[buy[j][0], 2] + buy[j][2]
                            else:
                                pass
                            buy[j][2] = 0
                        if j + 1 >= len(buy):
                            break
                        else:
                            j = j + 1
                    sold[-1][2] = q_origin - q
                    investorinfo.iloc[rd[i], 2] = investorinfo.copy().iloc[rd[i], 2] - q
                else:
                    pass

        # 第三類投資者
        elif candidate[0] == 3:
            respond = inv_3(pclose)
            preclose = pclose[-1]
            price_now = price[-1]
            pred_1 = respond
            callprice_1 = giveprice_1(preclose, pred_1, price_now)

            # 判斷買入報(bào)價(jià)和賣出報(bào)價(jià)敬锐,1 為買入,-1為賣出
            if callprice_1[1] == 1:
                # 買入報(bào)量
                money_now = candidate[1]
                if money_now > 0:
                    afa = random.uniform(0.25, 0.85)
                    quant = int(afa * money_now / callprice_1[0])
                    q_origin = quant
                    # 最終成交量
                    q = 0
                    sold.sort(key=lambda x: x[1])
                    j = 0
                    # 將買入報(bào)價(jià)寫入買單呆瞻,在完成交易后更新成交量
                    buy.append([rd[i], callprice_1[0], quant])
                    # 尋找所有賣出報(bào)價(jià)低于買入報(bào)價(jià)的賣家報(bào)單台夺,每完成一筆,更新持倉(cāng)量
                    while sold[j][1] <= callprice_1[0]:
                        if sold[j][2] >= quant and quant > 0:
                            sold[j][2] = sold[j][2] - quant
                            q = q + quant
                            if quant > 0:
                                price.append(sold[j][1])
                                investorinfo.iloc[sold[j][0], 1] = investorinfo.copy().iloc[sold[j][0], 1] + quant*sold[j][1]
                                investorinfo.iloc[rd[i], 1] = investorinfo.copy().iloc[rd[i], 1] - quant * sold[j][1]
                                investorinfo.iloc[sold[j][0], 2] = investorinfo.copy().iloc[sold[j][0], 2] - quant
                                quant = 0
                            else:
                                pass
                        elif sold[j][2] < quant and sold[j][2] > 0:
                            quant = quant - sold[j][2]
                            q = q + sold[j][2]
                            if sold[j][2] > 0:
                                price.append(sold[j][1])
                                investorinfo.iloc[rd[i], 1] = investorinfo.copy().iloc[rd[i], 1] - sold[j][2] * sold[j][1]
                                investorinfo.iloc[sold[j][0], 1] = investorinfo.copy().iloc[sold[j][0], 1] + sold[j][2] * sold[j][1]
                                investorinfo.iloc[sold[j][0], 2] = investorinfo.copy().iloc[sold[j][0], 2] - sold[j][2]
                            sold[j][2] = 0
                        if j + 1 >= len(sold):
                            break
                        else:
                            j = j + 1
                    buy[-1][2] = q_origin - q
                    investorinfo.iloc[rd[i], 2] = investorinfo.copy().iloc[rd[i], 2] + q
                else:
                    pass

            elif callprice_1[1] == -1:
                if candidate[2] > 0:
                    afa = random.uniform(0.25, 0.85)
                    quant = int(afa * candidate[2])
                    q_origin = quant
                    # 最終成交量
                    q = 0
                    buy.sort(key=lambda x: x[1], reverse=True)
                    j = 0
                    # 將賣出報(bào)價(jià)寫入賣價(jià)報(bào)單痴脾,在完成交易后更新成交量
                    sold.append([rd[i], callprice_1[0], quant])
                    # 尋找所有買入報(bào)價(jià)高于賣出報(bào)價(jià)的買家報(bào)單颤介,每完成一筆,更新持倉(cāng)量
                    while buy[j][1] >= callprice_1[0]:
                        if buy[j][2] >= quant and quant > 0:
                            buy[j][2] = buy[j][2] - quant
                            q = q + quant
                            if quant > 0:
                                price.append(buy[j][1])
                                investorinfo.iloc[buy[j][0], 1] = investorinfo.copy().iloc[buy[j][0], 1] - quant * buy[j][1]
                                investorinfo.iloc[rd[i], 1] = investorinfo.copy().iloc[rd[i], 1] + quant * buy[j][1]
                                investorinfo.iloc[buy[j][0], 2] = investorinfo.copy().iloc[buy[j][0], 2] + quant
                                quant = 0
                            else:
                                pass
                        elif buy[j][2] < quant and buy[j][2] > 0:
                            quant = quant - buy[j][2]
                            q = q + buy[j][2]
                            if buy[j][2] > 0:
                                price.append(buy[j][1])
                                investorinfo.iloc[buy[j][0], 1] = investorinfo.copy().iloc[buy[j][0], 1] - buy[j][2] * buy[j][1]
                                investorinfo.iloc[rd[i], 1] = investorinfo.copy().iloc[rd[i], 1] + buy[j][2] * buy[j][1]
                                investorinfo.iloc[buy[j][0], 2] = investorinfo.copy().iloc[buy[j][0], 2] + buy[j][2]
                            else:
                                pass
                            buy[j][2] = 0
                        if j + 1 >= len(buy):
                            break
                        else:
                            j = j + 1
                    sold[-1][2] = q_origin - q
                    investorinfo.iloc[rd[i], 2] = investorinfo.copy().iloc[rd[i], 2] - q
                else:
                    pass




    # 更新最終財(cái)富赞赖,收盤價(jià)
    rate = price[-1]/pclose[-1]
    pclose = price[-1]
    investorinfo['fortune'] = investorinfo.apply(lambda x: x['money'] + price[-1] * x['volume'], axis=1)

    return investorinfo, pclose, price[1:], rate


# 前100期取收盤價(jià)為100
pclose = [100 for i in range(100)]
# 價(jià)值的隨機(jī)游走分布
v = [100]
# 價(jià)值隨機(jī)游走標(biāo)準(zhǔn)差
e3 = 0.05
price = []
# 收益率
rate = []
investorinfo = generate_origin_info()
# 設(shè)定要運(yùn)行的期數(shù):
num = 100
for i in range(num):
    value = v[-1]
    # v為股票的價(jià)值滚朵,符合隨機(jī)游走過(guò)程,是個(gè)隨機(jī)變量
    n = random.normal(loc=0.0, scale=e3)
    value = e**(log(value)+n)
    v.append(value)
    back = buy_sold_a_day(investorinfo, pclose, v)
    investorinfo = back[0]
    pclose.append(back[1])
    price.append(back[2])
    rate.append(back[3])
    print('\r當(dāng)前進(jìn)度:{:.2f}%'.format((i+1) * 100 / num), end='')
print(investorinfo)
print(pclose)
print(rate)


price = list(chain.from_iterable(price))
print(price)
draw_pclose = pclose[99:]
x = np.linspace(0, num, 1)
plt.title('All Price that happened')
plt.xlabel('Times')
plt.ylabel('price')
plt.plot(price)
plt.show()

4. 參考文獻(xiàn)

【1】高言, 李昭輝. 基于人工股票市場(chǎng)的財(cái)富分布及演化研究[J]. 復(fù)雜系統(tǒng)與復(fù)雜性科學(xué), 2015(1):17-27.

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末前域,一起剝皮案震驚了整個(gè)濱河市辕近,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌匿垄,老刑警劉巖移宅,帶你破解...
    沈念sama閱讀 212,383評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異椿疗,居然都是意外死亡漏峰,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門届榄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)浅乔,“玉大人,你說(shuō)我怎么就攤上這事铝条【肝” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,852評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵班缰,是天一觀的道長(zhǎng)贤壁。 經(jīng)常有香客問(wèn)我,道長(zhǎng)鲁捏,這世上最難降的妖魔是什么芯砸? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,621評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮给梅,結(jié)果婚禮上假丧,老公的妹妹穿的比我還像新娘。我一直安慰自己动羽,他們只是感情好包帚,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著运吓,像睡著了一般渴邦。 火紅的嫁衣襯著肌膚如雪疯趟。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,929評(píng)論 1 290
  • 那天谋梭,我揣著相機(jī)與錄音信峻,去河邊找鬼。 笑死瓮床,一個(gè)胖子當(dāng)著我的面吹牛盹舞,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播隘庄,決...
    沈念sama閱讀 39,076評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼踢步,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了丑掺?” 一聲冷哼從身側(cè)響起获印,我...
    開(kāi)封第一講書(shū)人閱讀 37,803評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎街州,沒(méi)想到半個(gè)月后兼丰,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,265評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡菇肃,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評(píng)論 2 327
  • 正文 我和宋清朗相戀三年地粪,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片琐谤。...
    茶點(diǎn)故事閱讀 38,716評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖玩敏,靈堂內(nèi)的尸體忽然破棺而出斗忌,到底是詐尸還是另有隱情,我是刑警寧澤旺聚,帶...
    沈念sama閱讀 34,395評(píng)論 4 333
  • 正文 年R本政府宣布织阳,位于F島的核電站,受9級(jí)特大地震影響砰粹,放射性物質(zhì)發(fā)生泄漏唧躲。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評(píng)論 3 316
  • 文/蒙蒙 一碱璃、第九天 我趴在偏房一處隱蔽的房頂上張望弄痹。 院中可真熱鬧,春花似錦嵌器、人聲如沸肛真。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,798評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)蚓让。三九已至乾忱,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間历极,已是汗流浹背窄瘟。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,027評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留趟卸,地道東北人寞肖。 一個(gè)月前我還...
    沈念sama閱讀 46,488評(píng)論 2 361
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像衰腌,于是被迫代替她去往敵國(guó)和親新蟆。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容