寫了一個類GeneticOptimizeStrategy,針對VNPY策略遺傳演算法優化

張國平發表於2019-04-09

寫了一個類,GeneticOptimizeStrategy,

  1. Parameterlist 字典; 簡化了呼叫引數和定義對應隨機範圍問題。只要在Parameterlist 中定義策略引數名稱和對應的隨機範圍就可以,其中兩個引數的元祖是兩個之間隨機數,呼叫random.uniform(),三個引數元祖是開始結束和中間步進,呼叫的是random.randrange(), 如果是陣列就是在陣列中間隨機選擇。
  2. Symbollist 字典,維護回測品種和資料
  3. poptoExcel方法,輸出一個Excel,包括引數和value;引數可以直接呼叫。同時把相同項合併。效果如下圖

    原始碼也可以去我的GitHub中

-------------------------------------------------------------------------------------------------------------

發現多執行緒時候有報錯

cPickle.PicklingError: Can't pickle <type 'instancemethod'>: attribute lookup  builtin .instanceme
搜尋一下,發現是python2.7多程式問題,pool.map沒法繫結包在類裡面方法。


把evaluate的方法放在類外面做成靜態方法繫結,雖然解決了pickle,但是在多執行緒情況下,策略引數名字和值的對應經出出錯。
就把引數賦值的方式也改了,從
[value1,value2,value3...]變成[{key1:value1},{key2,value2},{key3,value3}...],這樣。可以滿足交叉,突變要求。生成隨機DNA演算法要改下。

    def parameter_generate(self):
        '''
        根據設定的起始值,終止值和步進,隨機生成待優化的策略引數
        '''
        parameter_list = []
        for key, value in self.parameterlist.items():
            if isinstance(value, tuple):
                if len(value) == 3:
                    parameter_list.append({key:random.randrange(value[0], value[1], value[2])})
                elif len(value) == 2:
                    parameter_list.append({key:random.uniform(value[0], value[1])})
            elif isinstance(value, list):
                parameter_list.append({key:random.choice(value)})
            else:
                parameter_list.append({key:value})
        return parameter_list

parameter_list是類似vnpy optimize的格式,不過有所增強。 strategy_avg返回變成了[{key1:value1},{key2,value2},{key3,value3}...],包含字典的list格式也可以滿足交叉和突變方法。

在進化篩選方法object_func,使用下面遍歷把[{key1:value1},{key2,value2},{key3,value3}...]變回{key1,value1,key2,value2...},這樣就可以進入回測

    setting = {}
    for item in range(len(strategy_avg)):
        setting.update(strategy_avg[item])


最後,因為進化篩選方法object_func放在類外面,但必須要把一些回測引數,比如品種,日期等的傳入,這裡有兩種方式可以實現,一個是把傳入individual list改為Tuple list,變成[(individual, parameterPackage)..]這樣list,但是就要修改algorithms.eaMuPlusLambda,比較麻煩。
還有一個是增強individual, 加入這個回測引數集做為屬性。但是多執行緒也有些要注意,不得不把parameterPackage做為靜態屬性放在類裡面,不然回提示parameterPackage為空。還沒有找到比較合適處理方法

creator.create("Individual", list, fitness=creator.FitnessMulti, parameterPackage=parameterPackage)

完整新程式碼如下

# encoding: UTF-8
"""
展示如何執行引數優化。
"""
from __future__ import division
from __future__ import print_function
from vnpy.trader.app.ctaStrategy.ctaBacktesting import BacktestingEngine, MINUTE_DB_NAME, OptimizationSetting
from vnpy.trader.app.ctaStrategy.strategy.strategyBollChannel import BollChannelStrategy
import random
import numpy as np
from deap import creator, base, tools, algorithms
import multiprocessing
import time, datetime
import pandas as pd
def object_func(strategy_avgTuple):
    """
    本函式為優化目標函式,根據隨機生成的策略引數,執行回測後自動返回2個結果指標:收益回撤比和夏普比率
    """
    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:
        capital = round(backresult['capital'], 3)  # 收益回撤比
        profitLossRatio = round(backresult['profitLossRatio'], 3)  # 夏普比率                 #夏普比率
        sharpeRatio = round(backresult['sharpeRatio'], 3)
    except Exception, e:
        print("Error: %s, %s" %(str(Exception),str(e)))
        sharpeRatio = 0
        profitLossRatio = 0  # 收益回撤比
        averageWinning = 0  # 夏普比率                 #夏普比率
        capital = 0
    return capital, sharpeRatio, profitLossRatio
class GeneticOptimizeStrategy(object):
    Strategy = BollChannelStrategy
    Symbollist ={
                    "vtSymbol": 'rb0000',
                    "StartDate": "20140601",
                    "EndDate": "20141101",
                    "Slippage": 1,
                    "Size": 10,
                    "Rate": 2 / 10000.0,
                    "Capital": 10000
                    }
    Parameterlist = {
                    'bollWindow': (10,50,1),       #布林帶視窗
                    'bollDev': (2,10,1),        #布林帶通道閾值
                    'slMultiplier':(3,6),
                    'barMins':[2,3,5,10,15,20],
    }
    parameterPackage = {
    "symbol":Symbollist,
    "strategy":Strategy
    }
    # ------------------------------------------------------------------------
    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("FitnessMulti", base.Fitness, weights=(1.0, 1.0, 1.0))  # 1.0 求最大值;-1.0 求最小值
    creator.create("Individual", list, fitness=creator.FitnessMulti, parameterPackage=parameterPackage)
    # ------------------------------------------------------------------------
    def parameter_generate(self):
        '''
        根據設定的起始值,終止值和步進,隨機生成待優化的策略引數
        '''
        parameter_list = []
        for key, value in self.parameterlist.items():
            if isinstance(value, tuple):
                if len(value) == 3:
                    parameter_list.append({key:random.randrange(value[0], value[1], value[2])})
                elif len(value) == 2:
                    parameter_list.append({key:random.uniform(value[0], value[1])})
            elif isinstance(value, list):
                parameter_list.append({key:random.choice(value)})
            else:
                parameter_list.append({key:value})
        return parameter_list
    def mutArrayGroup(self, individual, parameterlist, indpb):
        size = len(individual)
        paralist = parameterlist()
        for i in xrange(size):
            if random.random() < indpb:
                individual[i] = paralist[i]
        return individual,
    def optimize(self):
        # 設定優化方向:最大化收益回撤比,最大化夏普比率
        toolbox = base.Toolbox()  # Toolbox是deap庫內建的工具箱,裡面包含遺傳演算法中所用到的各種函式
        pool = multiprocessing.Pool(processes=(multiprocessing.cpu_count()-1))
        toolbox.register("map", pool.map)
        # 初始化
        toolbox.register("individual", tools.initIterate, creator.Individual,
                         self.parameter_generate)  # 註冊個體:隨機生成的策略引數parameter_generate()
        toolbox.register("population", tools.initRepeat, list,
                         toolbox.individual)  # 註冊種群:個體形成種群
        toolbox.register("mate", tools.cxTwoPoint)  # 註冊交叉:兩點交叉
        toolbox.register("mutate", self.mutArrayGroup, parameterlist=self.parameter_generate,
                         indpb=0.6)  # 註冊變異:隨機生成一定區間內的整數
        toolbox.register("evaluate", object_func)  # 註冊評估:優化目標函式object_func()
        toolbox.register("select", tools.selNSGA2)  # 註冊選擇:NSGA-II(帶精英策略的非支配排序的遺傳演算法)
        # 遺傳演算法引數設定
        MU = 8  # 設定每一代選擇的個體數
        LAMBDA = 5  # 設定每一代產生的子女數
        pop = toolbox.population(20)  # 設定族群裡面的個體數量
        CXPB, MUTPB, NGEN = 0.5, 0.3, 10 # 分別為種群內部個體的交叉概率、變異概率、產生種群代數
        hof = tools.ParetoFront()  # 解的集合:帕累託前沿(非佔優最優集)
        # 解的集合的描述統計資訊
        # 集合內平均值,標準差,最小值,最大值可以體現集合的收斂程度
        # 收斂程度低可以增加演算法的迭代次數
        stats = tools.Statistics(lambda ind: ind.fitness.values)
        np.set_printoptions(suppress=True)  # 對numpy預設輸出的科學計數法轉換
        stats.register("mean", np.mean, axis=0)  # 統計目標優化函式結果的平均值
        stats.register("std", np.std, axis=0)  # 統計目標優化函式結果的標準差
        stats.register("min", np.min, axis=0)  # 統計目標優化函式結果的最小值
        stats.register("max", np.max, axis=0)  # 統計目標優化函式結果的最大值
        # 執行演算法
        algorithms.eaMuPlusLambda(pop, toolbox, MU, LAMBDA, CXPB, MUTPB, NGEN, stats,
                                  halloffame=hof, verbose=True)  # esMuPlusLambda是一種基於(μ+λ)選擇策略的多目標優化分段遺傳演算法
        return pop
    def poptoExcel(self, pop, number = 1000, path = "C:/data/"):
        #按照輸入統計資料佇列和路徑,輸出excel,這裡不提供新增模式,如果想,可以改
        #dft.to_csv(path,index=False,header=True, mode = 'a')
        path = path +  self.strategy.className + "_" + self.symbol[ "vtSymbol"] + str(datetime.date.today())+ ".xls"
        summayKey = ["StrategyParameter","TestValues"]
        best_ind = tools.selBest(pop, number)
        dft = pd.DataFrame(columns=summayKey)
        for i in range(0,len(best_ind)-1):
            if i == 0:
                # new = pd.DataFrame([{"StrategyParameter":self.complieString(best_ind[i])},{"TestValues":best_ind[i].fitness.values}], index=["0"])
                dft = dft.append([{"StrategyParameter":self.complieString(best_ind[i]),"TestValues":best_ind[i].fitness.values}], ignore_index=True)
            elif str(best_ind[i-1]) == (str(best_ind[i])):
                pass
            else:
                #new = pd.DataFrame({"StrategyParameter":self.complieString(best_ind[i]),"TestValues":best_ind[i].fitness.values}, index=["0"])
                dft = dft.append([{"StrategyParameter":self.complieString(best_ind[i]),"TestValues":best_ind[i].fitness.values}], ignore_index=True)
        dft.to_excel(path,index=False,header=True)
        print("回測統計結果輸出到" + path)
    def complieString(self,individual):
        setting = {}
        for item in range(len(individual)):
            setting.update(individual[item])
        return str(setting)
if __name__ == "__main__":
    Strategy = BollChannelStrategy
    Symbollist ={
                    "vtSymbol": 'rb0000',
                    "StartDate": "20140601",
                    "EndDate": "20141101",
                    "Slippage": 1,
                    "Size": 10,
                    "Rate": 2 / 10000.0,
                    "Capital": 10000
                    }
    Parameterlist = {
                    'bollWindow': (10,50,1),       #布林帶視窗
                    'bollDev': (2,10,1),        #布林帶通道閾值
                    'slMultiplier':(3,6),
                    'barMins':[2,3,5,10,15,20],
    }
    parameterPackage = {
    "symbol":Symbollist,
    "parameterlist":Parameterlist,
    "strategy":Strategy
    }
    GE = GeneticOptimizeStrategy(Strategy,Symbollist,Parameterlist)
    GE.poptoExcel(GE.optimize())
    print("-- End of (successful) evolution --")

-------------------------------------------------------------------------------------------------------------

https://github.com/BillyZhangGuoping/MarketDataAnaylzerbyDataFrame/tree/master/GeneticOptimizeforVNPYStrategy


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

相關文章