利用粒子群優化演算法(PSO)來優化vnpy的量化策略引數

張國平發表於2019-05-14

上一篇文章中說要用PSO演算法來做量化策略優化,模仿DEAP的示例程式碼,做了一個單執行緒的類,實現優化。裡面程式碼結構是和之前做利用遺傳演算法優化的基本一樣。


粒子全演算法可以看看我之前文章,抄的分析結果:

粒子群演算法比遺傳演算法具有更高效的資訊共享機制,更新群體極值使得資訊實現全域性範圍共享,但遺傳演算法通過交叉和變異擁有比粒子群演算法更有效的逃離區域性最優解的概率。


下面簡單說說程式碼:


1、靜態函式object_func,本函式為優化目標函式,根據隨機生成的策略引數,執行回測後自動返回1個結果指標:夏普比率 這個是直接抄GenticOptimize2V的。這個獨立出來是為了之後多執行緒實現。這裡傳入的是一個[{key1:para},{key2,para}] 這樣結構的引數佇列。


2、類PSOOptimize,放置主要函式如下:

2.1、__init__ 物件初始化,需要傳入的引數是Strategy量化策略, Symbollist回測品種, Parameterlist引數範圍。

2.2、creator.create("FitnessMax", base.Fitness, weights=(1.0,)) 定義優化方向

        creator.create("Particle", list, fitness=creator.FitnessMax, speed=list, pmin = list, pmax = list,smin=list, smax=list, parameterPackage = dict, best=None)

         定義粒子類,其中包括引數list,回測返回結果,速度list,所在範圍上下限佇列,和速度 上下限 佇列;還有一個打包的引數字典,主要為了之後多執行緒呼叫。和示例程式碼對比最大區別就是多了所在範圍上線,因為如果沒有,會出現負引數的情況報錯。

2.3、particle_generate, 生成粒子,包含隨機位置,和速度;這裡面只支援(start,end,pace) 這樣一種引數範圍賦值。start和end就是位置範圍上下限。步長pace就是速度上限,負pace就是速度下限。

2.4、updateParticle,更新粒子資訊,根據粒子群最佳位置best,去更新粒子part的位置和速度。如果速度或位置在上下限,就去上下限值。如果位置值是整數,比是MAwindow這樣,就會更新值也是整數。

        速度公式:

        v[] = v[] + c1 * rand() * (pbest[] - present[]) + c2 * rand() * (gbest[] - present[])

        位置公式:

        present[] = persent[] + v[]

2.5、optimize,這個沒說明好說,主要就是繫結函式到toolbox,然後定義粒子群的粒子數量,和尋覓次數。優化出最有結果。    使用pool.map實現了多執行緒

2.6、mutated, 自變異函式

示例程式碼執行結果如下,result是夏普值。

best para: [{'slMultiplier': 2.0}, {'bollDev': 2.0}, {'bollWindow': 53.0}, {'atrWindow': 37.0}, {'cciWindow': 8.0}], result:(1.194,)


------------------------------------------------------------------2019.4.15--------------------------------------------------------------------------------

增加自適應變異函式mutated,使用pool.map實現多執行緒。


可以在我的github的 PSOOptimize資料夾下載。

https://github.com/BillyZhangGuoping/MarketDataAnaylzerbyDataFrame

程式碼:

# encoding: UTF-8
"""
展示如何實現PSO粒子群優化VNPY策略引數
1、靜態函式object_func,本函式為優化目標函式,根據隨機生成的策略引數,執行回測後自動返回1個結果指標:夏普比率 這個是直接抄GenticOptimize2V的。這個獨立出來是為了之後多執行緒實現。這裡傳入的是一個[{key1:para},{key2,para}] 這樣結構的引數佇列。
2、類PSOOptimize,放置主要函式如下:
2.1、__init__ 物件初始化,需要傳入的引數是Strategy量化策略, Symbollist回測品種, Parameterlist引數範圍。
2.2、creator.create("FitnessMax", base.Fitness, weights=(1.0,)) 定義優化方向
        creator.create("Particle", list, fitness=creator.FitnessMax, speed=list, pmin = list, pmax = list,smin=list, smax=list, parameterPackage = dict, best=None)
         定義粒子類,其中包括引數list,回測返回結果,速度list,所在範圍上下限佇列,和速度 上下限 佇列;還有一個打包的引數字典,主要為了之後多執行緒呼叫。和示例程式碼對比最大區別就是多了所在範圍上線,因為如果沒有,會出現負引數的情況報錯。
2.3、particle_generate, 生成粒子,包含隨機位置,和速度;這裡面只支援(start,end,pace) 這樣一種引數範圍賦值。start和end就是位置範圍上下限。步長pace就是速度上限,負pace就是速度下限。
2.4、updateParticle,更新粒子資訊,根據粒子群最佳位置best,去更新粒子part的位置和速度。如果速度或位置在上下限,就去上下限值。如果位置值是整數,比是MAwindow這樣,就會更新值也是整數。
        速度公式:
        v[] = v[] + c1 * rand() * (pbest[] - present[]) + c2 * rand() * (gbest[] - present[])
        位置公式:
        present[] = persent[] + v[]
2.5、optimize,這個沒說明好說,主要就是繫結函式到toolbox,然後定義粒子群的粒子數量,和尋覓次數。優化出最有結果。
    使用pool.map實現了多執行緒
2.6、mutated, 自變異函式
"""
from __future__ import division
from __future__ import print_function
import operator
import random
import numpy
from deap import base
from deap import creator
from deap import tools
from vnpy.trader.app.ctaStrategy.ctaBacktesting import BacktestingEngine, MINUTE_DB_NAME, OptimizationSetting
from vnpy.trader.app.ctaStrategy.strategy.strategyBollChannel import BollChannelStrategy
import multiprocessing
import pandas as pd
import datetime
def object_func(strategy_avgTuple):
    """
    本函式為優化目標函式,根據隨機生成的策略引數,執行回測後自動返回1個結果指標:夏普比率
    這個是直接賦值GenticOptimize2V的
    """
    strategy_avg = strategy_avgTuple
    paraSet = strategy_avgTuple.parameterPackage
    symbol = paraSet["symbol"]
    strategy = paraSet["strategy"]
    # 建立回測引擎物件
    engine = BacktestingEngine()
    # 設定回測使用的資料
    engine.setBacktestingMode(engine.BAR_MODE)  # 設定引擎的回測模式為K線
    engine.setDatabase("VnTrader_1Min_Db", symbol["vtSymbol"])  # 設定使用的歷史資料庫
    engine.setStartDate(symbol["StartDate"])  # 設定回測用的資料起始日期
    engine.setEndDate(symbol["EndDate"])  # 設定回測用的資料起始日期
    # 配置回測引擎引數
    engine.setSlippage(symbol["Slippage"])  # 1跳
    engine.setRate(symbol["Rate"])  # 佣金大小
    engine.setSize(symbol["Size"])  # 合約大小
    engine.setPriceTick(symbol["Slippage"])  # 最小价格變動
    engine.setCapital(symbol["Capital"])
    setting = {}
    for item in range(len(strategy_avg)):
        setting.update(strategy_avg[item])
    engine.clearBacktestingResult()
    # 載入策略
    engine.initStrategy(strategy, setting)
    # 執行回測,返回指定的結果指標
    engine.runBacktesting()  # 執行回測
    # 逐日回測
    # engine.calculateDailyResult()
    backresult = engine.calculateBacktestingResult()
    try:
        sharpeRatio = round(backresult['sharpeRatio'], 3)
        totalResultCount = round(backresult['totalResult'],3)
    except Exception, e:
        print("Error: %s, %s" %(str(Exception),str(e)))
        sharpeRatio = 0
    return sharpeRatio,
class PSOOptimize(object):
    strategy = None
    symbol = {}
    parameterlist = {}
    parameterPackage = {}
    # ------------------------------------------------------------------------
    def __init__(self, Strategy, Symbollist, Parameterlist):
        self.strategy = Strategy
        self.symbol = Symbollist
        self.parameterlist = Parameterlist
        self.parameterPackage = {
            "strategy":self.strategy,
            "symbol":self.symbol
        }
    creator.create("FitnessMax", base.Fitness, weights=(1.0,))
    creator.create("Particle", list, fitness=creator.FitnessMax, speed=list,
                   pmin = list, pmax = list,smin=list, smax=list, parameterPackage = dict, best=None)
    def particle_generate(self):
        """
        生產particle粒子,根據傳入設定的起始值,終止值隨機生成位置,和位置最大最小值,根據步進生成速度,和速度最大最小值
        支援兩種引數設定,一個是三個元素start,end 和pace
        還有一個單一不變點
        """
        position_list = []
        speed_list = []
        pmin_list = []
        pmax_list = []
        smin_list = []
        smax_list = []
        for key, value in self.parameterlist.items():
            if isinstance(value, tuple):
                if len(value) == 3:
                    if isinstance(value[0],int):
                        position_list.append({key:random.randrange(value[0], value[1])})
                    else:
                        position_list.append({key: random.uniform(value[0], value[1])})
                    pmin_list.append(value[0])
                    pmax_list.append(value[1])
                    speed_list.append(random.uniform(-value[2], value[2]))
                    smin_list.append(-value[2])
                    smax_list.append(value[2])
                else:
                    print("Paramerte list incorrect")
            elif isinstance(value, int) or isinstance(value, float):
                position_list.append({key: value})
                pmin_list.append(value)
                pmax_list.append(value)
                speed_list.append(0)
                smin_list.append(0)
                smax_list.append(0)
            else:
                print("Paramerte list incorrect")
        particle = creator.Particle(position_list)
        particle.speed = speed_list
        particle.pmin = pmin_list
        particle.pmax = pmax_list
        particle.smin = smin_list
        particle.smax = smax_list
        particle.parameterPackage = self.parameterPackage
        return particle
    def updateParticle(self,part, best, phi1, phi2):
        """
        根據粒子群最佳位置best,去更新粒子part的位置和速度,
        速度公式:
        v[] = v[] + c1 * rand() * (pbest[] - present[]) + c2 * rand() * (gbest[] - present[])
        位置公式:
        present[] = persent[] + v[]
        """
        u1 = (random.uniform(0, phi1) for _ in range(len(part)))
        u2 = (random.uniform(0, phi2) for _ in range(len(part)))
        v_u1 = map(operator.mul, u1, map(self.sub, part.best, part))# c1 * rand() * (pbest[] - present[])
        v_u2 = map(operator.mul, u2, map(self.sub, best, part)) # c2 * rand() * (gbest[] - present[])
        part.speed = list(map(operator.add, part.speed, map(operator.add, v_u1, v_u2)))
        for i, speed in enumerate(part.speed):
            if speed < part.smin:
                part.speed[i] = part.smin[i]
            elif speed > part.smax:
                part.speed[i] = part.smax[i]
        #返回現在位置,如果原來是整數,返回也是整數,否則這是浮點數; 如果超過上下限,用上下限數值
        for i,item in enumerate(part):
            if isinstance(item.values()[0],int):
                positionV = round(item.values()[0] + part.speed[i])
            else:
                positionV = item.values()[0] + part.speed[i]
            if positionV <= part.pmin[i] :
                part[i][item.keys()[0]] = part.pmin[i]
            elif positionV >= part.pmax[i]:
                part[i][item.keys()[0]] = part.pmax[i]
            else:
                part[i][item.keys()[0]] = positionV
    def sub(self,a,b):
       return a.values()[0] - b.values()[0]
    def mutated(self,particle,MUTPB):
        """
        自適應變異
        :param particle: 傳入
        :param MUTPB:
        :return:
        """
        size = len(particle)
        newPart = self.particle_generate()
        for i in xrange(size):
            if random.random() < MUTPB:
                particle[i] = newPart[i]
        return particle
    def optimize(self):
        toolbox = base.Toolbox()
        pool = multiprocessing.Pool(processes=(multiprocessing.cpu_count()))
        toolbox.register("map", pool.map)
        toolbox.register("particle", self.particle_generate)
        toolbox.register("population", tools.initRepeat, list, toolbox.particle)
        toolbox.register("update", self.updateParticle, phi1=2.0, phi2=2.0)
        toolbox.register("evaluate", object_func)
        pop = toolbox.population(n=20) #粒子群有5個粒子
        GEN = 20 #更新一千次
        MUTPB = 0.1 #自適應變異概率
        best = None
        for g in range(GEN):
            # for part in pop: #每次更新,計算粒子群中最優引數,並把最優值寫入best
            #     part.fitness.values = toolbox.evaluate(part)
            #     if not part.best or part.best.fitness < part.fitness:
            #         part.best = creator.Particle(part)
            #         part.best.fitness.values = part.fitness.values
            #     if not best or best.fitness < part.fitness:
            #         best = creator.Particle(part)
            #         best.fitness.values = part.fitness.values
            fitnesses = toolbox.map(toolbox.evaluate, pop) #利用pool.map,實現多執行緒
            for part, fit in zip(pop, fitnesses):
                part.fitness.values = fit
                if not part.best or part.best.fitness < part.fitness:
                    part.best = creator.Particle(part)
                    part.best.fitness.values = part.fitness.values
                if not best or best.fitness < part.fitness:
                    best = creator.Particle(part)
                    best.fitness.values = part.fitness.values
            if g < GEN - 1: #在最後一輪之前,每輪進行位置更新和變異
                for i,part in enumerate(pop):
                    toolbox.update(part, best)#更新粒子位置
                    if random.random() < MUTPB: #自適應變異
                        self.mutated(part,MUTPB)
            # Gather all the fitnesses in one list and print the stats
        return pop, best
if __name__ == "__main__":
    Strategy = BollChannelStrategy
    Symbol = {
            "vtSymbol": 'rb1905',
            "StartDate": "20181001",
            "EndDate": "20190301",
            "Slippage": 1,
            "Size": 10,
            "Rate": 2 / 10000,
            "Capital": 10000
        }
    Parameterlist = {
        'bollWindow':(10,50,5),
        'bollDev':(3.0,6.0,0.6),
        'cciWindow':(10,50,5),
        'atrWindow':(10,50,5),
        'slMultiplier':(3.5,5.5,0.5),
        'fixedSize':1
    }
    parameterPackage = {
        "symbol": Symbol,
        "parameterlist": Parameterlist,
        "strategy": Strategy
    }
    PSO = PSOOptimize(Strategy, Symbol, Parameterlist)
    pop,best = PSO.optimize()
    print ("best para: %s, result:%s" %(best,best.fitness.values))
    print(pop[:20])
    print("-- End of (successful) %s evolution --", Symbol["vtSymbol"])

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/22259926/viewspace-2644377/,如需轉載,請註明出處,否則將追究法律責任。

相關文章