# 破解股市泡沫之谜——对数周期幂率（LPPL）模型

## 引言

Sornette教授开始尝试着解答这个问题——不是通过传统的金融学方法，而是将物理学思想引入其中。作为2004年出版的《股市为什么会崩盘》(Why Stock Markets Crash)一书的作者，Sornette教授实质上是希望更深刻地理解泡沫的形成和发展。在对复杂体系的分析中，他独自——或者是和极少数几个人一起——引领着三个并行领域：纯物理学、应用经济学和计量经济学，以及市场从业人员。

## 什么是对数周期幂率模型？

Sornette教授的眼睛仿佛一下子充满了光芒，他仿佛看到了美元纸币上的林肯在向他招手。受易辛模型启发，Sornette教授认为在金融市场中，投资者也只具有两种状态，即买或者卖。同时，投资者的交易行为取决于其他投资者的决策及外部因素的影响，这与易辛模型是多么的相似！

LPPL模型收到的批评与收到的赞扬一样多，有不少人认为该模型没有操作价值。如果你想了解更多关于它的信息，可以仔细详读Everything You Always Wanted to Know about Log Periodic Power Laws for Bubble Modelling but Were Afraid to Ask

P.S 这次股灾虽然我逃顶了，也许我马后炮了~ :)

``````import lib.lppltool as lppltool
from matplotlib import pyplot as plt
import datetime
import numpy as np
import pandas as pd
import seaborn as sns
sns.set_style('white')

limits = ([8.4, 8.8], [-1, -0.1], [350, 400], [.1,.9], [-1,1], [12,18], [0, 2*np.pi])
x = lppltool.Population(limits, 20, 0.3, 1.5, .05, 4)
for i in range (2):
x.Fitness()
x.Eliminate()
x.Mate()
x.Mutate()

x.Fitness()
values = x.BestSolutions(3)
for x in values:
print x.PrintIndividual()

Fitness Evaluating: 0 of 20
Fitness Evaluating: 1 of 20
Fitness Evaluating: 2 of 20
Fitness Evaluating: 3 of 20
Fitness Evaluating: 4 of 20
Fitness Evaluating: 5 of 20
Fitness Evaluating: 6 of 20
Fitness Evaluating: 7 of 20
Fitness Evaluating: 8 of 20
Fitness Evaluating: 9 of 20
Fitness Evaluating: 10 of 20
Fitness Evaluating: 11 of 20
Fitness Evaluating: 12 of 20
Fitness Evaluating: 13 of 20
Fitness Evaluating: 14 of 20
Fitness Evaluating: 15 of 20
Fitness Evaluating: 16 of 20
Fitness Evaluating: 17 of 20
Fitness Evaluating: 18 of 20
Fitness Evaluating: 19 of 20
fitness out size: 20 0
Eliminate: 14
Mate Loop complete: 25
Mutate: 2
Fitness Evaluating: 0 of 31
Fitness Evaluating: 1 of 31
Fitness Evaluating: 2 of 31
Fitness Evaluating: 3 of 31
Fitness Evaluating: 4 of 31
Fitness Evaluating: 5 of 31
Fitness Evaluating: 6 of 31
Fitness Evaluating: 7 of 31
Fitness Evaluating: 8 of 31
Fitness Evaluating: 9 of 31
Fitness Evaluating: 10 of 31
Fitness Evaluating: 11 of 31
Fitness Evaluating: 12 of 31
Fitness Evaluating: 13 of 31
Fitness Evaluating: 14 of 31
Fitness Evaluating: 15 of 31
Fitness Evaluating: 16 of 31
Fitness Evaluating: 17 of 31
Fitness Evaluating: 18 of 31
Fitness Evaluating: 19 of 31
Fitness Evaluating: 20 of 31
Fitness Evaluating: 21 of 31
Fitness Evaluating: 22 of 31
Fitness Evaluating: 23 of 31
Fitness Evaluating: 24 of 31
Fitness Evaluating: 25 of 31
Fitness Evaluating: 26 of 31
Fitness Evaluating: 27 of 31
Fitness Evaluating: 28 of 31
Fitness Evaluating: 29 of 31
Fitness Evaluating: 30 of 31
fitness out size: 31 0
Eliminate: 25
Mate Loop complete: 25
Mutate: 0
Fitness Evaluating: 0 of 31
Fitness Evaluating: 1 of 31
Fitness Evaluating: 2 of 31
Fitness Evaluating: 3 of 31
Fitness Evaluating: 4 of 31
Fitness Evaluating: 5 of 31
Fitness Evaluating: 6 of 31
Fitness Evaluating: 7 of 31
Fitness Evaluating: 8 of 31
Fitness Evaluating: 9 of 31
Fitness Evaluating: 10 of 31
Fitness Evaluating: 11 of 31
Fitness Evaluating: 12 of 31
Fitness Evaluating: 13 of 31
Fitness Evaluating: 14 of 31
Fitness Evaluating: 15 of 31
Fitness Evaluating: 16 of 31
Fitness Evaluating: 17 of 31
Fitness Evaluating: 18 of 31
Fitness Evaluating: 19 of 31
Fitness Evaluating: 20 of 31
Fitness Evaluating: 21 of 31
Fitness Evaluating: 22 of 31
Fitness Evaluating: 23 of 31
Fitness Evaluating: 24 of 31
Fitness Evaluating: 25 of 31
Fitness Evaluating: 26 of 31
Fitness Evaluating: 27 of 31
Fitness Evaluating: 28 of 31
Fitness Evaluating: 29 of 31
Fitness Evaluating: 30 of 31
fitness out size: 31 0
fitness: 0.99612688166
A: 9.817B: -0.681Critical Time: 365.323m: 0.207c: -0.023omega: 12.241phi: 4.25
fitness: 0.99612688166
A: 9.817B: -0.681Critical Time: 365.323m: 0.207c: -0.023omega: 12.241phi: 4.25
fitness: 0.99653502204
A: 9.8B: -0.667Critical Time: 365.405m: 0.209c: -0.023omega: 12.267phi: 4.105
``````
``````data = pd.DataFrame({'Date':values[0].getDataSeries()[0],'Index':values[0].getDataSeries()[1],'Fit1':values[0].getExpData(),'Fit2':values[1].getExpData(),'Fit3':values[2].getExpData()})
data = data.set_index('Date')
data.plot(figsize=(14,8))

<matplotlib.axes.AxesSubplot at 0x663c250>
``````

`lib`库代码，请保存并命名为`lppltool`

``````#code created by jd8001
#reference: https://github.com/jd8001/LPPL
#kindly thank jd8001!

import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import fmin_tnc
import random
import pandas as pd
from pandas import Series, DataFrame
import datetime
import itertools

time = np.linspace(0, len(SP)-1, len(SP))
close = [np.log(SP.closeIndex[i]) for i in range(len(SP))]
global DataSeries
DataSeries = [time, close]

def lppl (t,x): #return fitting result using LPPL parameters
a = x[0]
b = x[1]
tc = x[2]
m = x[3]
c = x[4]
w = x[5]
phi = x[6]
return a + (b*np.power(tc - t, m))*(1 + (c*np.cos((w *np.log(tc-t))+phi)))

def func(x):
delta = [lppl(t,x) for t in DataSeries[0]] #生成lppl时间序列
delta = np.subtract(delta, DataSeries[1]) #将生成的lppl时间序列减去对数指数序列
delta = np.power(delta, 2)
return np.sum(delta) #返回拟合均方差

class Individual:
'base class for individuals'

def __init__ (self, InitValues):
self.fit = 0
self.cof = InitValues

def fitness(self): #
try:
cofs, nfeval, rc = fmin_tnc(func, self.cof, fprime=None,approx_grad=True, messages=0) #基于牛顿梯度下山的寻找函数最小值
self.fit = func(cofs)
self.cof = cofs

except:

#does not converge
return False

def mate(self, partner): #交配
for i in range(0, len(self.cof)): # 遍历所以的输入参数
if (random.randint(0,1) == 1): # 交配，0.5的概率自身的参数保留，0.5的概率留下partner的参数，即基因交换
else:

def mutate(self): #突变
for i in range(0, len(self.cof)-1):
if (random.randint(0,len(self.cof)) <= 2):
#print "Mutate" + str(i)
self.cof[i] += random.choice([-1,1]) * .05 * i #突变

def PrintIndividual(self): #打印结果
#t, a, b, tc, m, c, w, phi
cofs = "A: " + str(round(self.cof[0], 3))
cofs += "B: " + str(round(self.cof[1],3))
cofs += "Critical Time: " + str(round(self.cof[2], 3))
cofs += "m: " + str(round(self.cof[3], 3))
cofs += "c: " + str(round(self.cof[4], 3))
cofs += "omega: " + str(round(self.cof[5], 3))
cofs += "phi: " + str(round(self.cof[6], 3))

return "fitness: " + str(self.fit) +"\n" + cofs
#return str(self.cof) + " fitness: " + str(self.fit)
def getDataSeries(self):
return DataSeries
def getExpData(self):
return [lppl(t,self.cof) for t in DataSeries[0]]
return date

def fitFunc(t, a, b, tc, m, c, w, phi):
return a - (b*np.power(tc - t, m))*(1 + (c*np.cos((w *np.log(tc-t))+phi)))

class Population:
'base class for a population'
LOOP_MAX = 1000

def __init__ (self, limits, size, eliminate, mate, probmutate, vsize):
'seeds the population'
'limits is a tuple holding the lower and upper limits of the cofs'
'size is the size of the seed population'
self.populous = []
self.eliminate = eliminate
self.size = size
self.mate = mate
self.probmutate = probmutate
self.fitness = []

for i in range(size):
SeedCofs = [random.uniform(a[0], a[1]) for a in limits]
self.populous.append(Individual(SeedCofs))

def PopulationPrint(self):
for x in self.populous:
print x.cof
def SetFitness(self):
self.fitness = [x.fit for x in self.populous]
def FitnessStats(self):
#returns an array with high, low, mean
return [np.amax(self.fitness), np.amin(self.fitness), np.mean(self.fitness)]
def Fitness(self):
counter = 0
false = 0
for individual in list(self.populous):
print('Fitness Evaluating: ' + str(counter) +  " of " + str(len(self.populous)) + "        \r"),
state = individual.fitness()
counter += 1

if ((state == False)):
false += 1
self.populous.remove(individual)
self.SetFitness()
print "\n fitness out size: " + str(len(self.populous)) + " " + str(false)
def Eliminate(self):
a = len(self.populous)
self.populous.sort(key=lambda ind: ind.fit)
while (len(self.populous) > self.size * self.eliminate):
self.populous.pop()
print "Eliminate: " + str(a- len(self.populous))
def Mate(self):
counter = 0
while (len(self.populous) <= self.mate * self.size):
counter += 1
i = self.populous[random.randint(0, len(self.populous)-1)]
j = self.populous[random.randint(0, len(self.populous)-1)]
diff = abs(i.fit-j.fit)
if (diff < random.uniform(np.amin(self.fitness), np.amax(self.fitness) - np.amin(self.fitness))):
self.populous.append(i.mate(j))

if (counter > Population.LOOP_MAX):
print "loop broken: mate"
while (len(self.populous) <= self.mate * self.size):
i = self.populous[random.randint(0, len(self.populous)-1)]
j = self.populous[random.randint(0, len(self.populous)-1)]
self.populous.append(i.mate(j))

print "Mate Loop complete: " + str(counter)

def Mutate(self):
counter = 0
for ind in self.populous:
if (random.uniform(0, 1) < self.probmutate):
ind.mutate()
ind.fitness()
counter +=1
print "Mutate: " + str(counter)
self.SetFitness()

def BestSolutions(self, num):