Quantopian zipline trading algorithm parameter optimization with Spearmint Bayesian Optimizer - Part 2
The zipline version of the algorithm is shown in figure 2 and can be downloaded here :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 44 44 45 46 47 48 |
from zipline.api import (add_history, history, order_target_percent, record, symbol) import talib def initialize(context): context.secs = [ symbol('SPY') ] context.history_depth = 30 context.iwarmup = 0 add_history(30, '1d', 'price') context.BBANDS_timeperiod = 16 context.BBANDS_nbdevup = 1.819 context.BBANDS_nbdevdn = 1.470 UseParams = False try: Nparams=len(context.algo_params) if Nparams == 3 : UseParams = True else: print 'len context.algo_params is',Nparams,' expecting 3' except Exception as e: print 'context.algo_params not passed' if UseParams: print 'Setting Algo parameters via passed algo_params' context.BBANDS_timeperiod = context.algo_params['timeperiod'] context.BBANDS_nbdevup = context.algo_params['nbdevup'] context.BBANDS_nbdevdn = context.algo_params['nbdevdn'] def handle_data(context, data): context.iwarmup = context.iwarmup + 1 if context.iwarmup <= ( context.history_depth + 1 ) : return dfHistD = history(30, '1d', 'price') S=context.secs[0] CurP=data[S].price BolU,BolM,BolL = talib.BBANDS( dfHistD[S].values, timeperiod=context.BBANDS_timeperiod, nbdevup=context.BBANDS_nbdevup, nbdevdn=context.BBANDS_nbdevdn, matype=0) record(CurP=CurP,BolU=BolU[-1],BolM=BolM[-1],BolL=BolL[-1]) if CurP < BolL[-1] : order_target_percent(S,+0.97) else: if CurP > BolU[-1] : order_target_percent(S,-0.97) return |
|
Figure 2 : Zipline version of the BBANDS Trading Algorithm ready for spearmint |
Lines 15 to 28 were added to allow the spearmint optimizer to pass experiment parameters to BBANDS. Stock zipline does not have “algo_parms” attached to context (eg, Trading Algorithm object) so the try statement will catch the error and use the hardcoded tuning constants from lines 12 to 14.
To pass parameters to the BBANDS algo, we must first patch zipline in a minor way (patch can be downloaded here and here ):
quant@QUANT2-> pwd /usr/local/lib/python2.7/dist-packages
quant@QUANT2-> diff -Naur zipline/algorithm.py_orig zipline/algorithm.py --- zipline/algorithm.py_orig 2015-01-06 07:09:22.000000000 -0500 +++ zipline/algorithm.py 2015-01-06 07:54:32.000000000 -0500 @@ -167,6 +167,9 @@ # set the capital base self.capital_base = kwargs.pop('capital_base', DEFAULT_CAPITAL_BASE)
+ # set the algo_params + self.algo_params = kwargs.pop('algo_params') + self.sim_params = kwargs.pop('sim_params', None) if self.sim_params is None: self.sim_params = create_simulation_parameters(
quant@QUANT2-> diff -Naur zipline/utils/cli.py_orig zipline/utils/cli.py --- zipline/utils/cli.py_orig 2015-01-06 07:26:56.000000000 -0500 +++ zipline/utils/cli.py 2015-01-06 07:53:51.000000000 -0500 @@ -101,6 +101,7 @@ parser.add_argument('--start', '-s') parser.add_argument('--end', '-e') parser.add_argument('--capital_base') + parser.add_argument('--algo_params') parser.add_argument('--source', choices=('yahoo',)) parser.add_argument('--symbols') parser.add_argument('--output', '-o') @@ -191,6 +192,7 @@ algo = zipline.TradingAlgorithm(script=algo_text, namespace=kwargs.get('namespace', {}), capital_base=float(kwargs['capital_base']), + algo_params=kwargs['algo_params'], algo_filename=kwargs.get('algofile'))
perf = algo.run(source) |
Figure 3 : Patching zipline to pass parameters to a trading algorithm |
We must also create a spearmint compatible version of zipline’s /usr/bin/run_algo.py. This can be downloaded here and is shown in Figure 4.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
#!/usr/bin/python import sys from zipline.utils import parse_args, run_pipeline def main(job_id,D): parsed={} parsed['symbols']='SPY' parsed['start']='2014-01-01' parsed['end']='2014-12-31' parsed['algofile']= '/home/quant/pta/py/algos/BBANDS-zipday.py' parsed['capital_base']='100000'
parsed['data_frequency']='daily' parsed['conf_file']= None parsed['source']='yahoo' parsed['output']= None parsed['risk']= None
# Below what we expect spearmint to pass us # parsed['algo_params']=[47,88.7,7.7] # D={} # D['timeperiod']=10 # D['nbdevup']=1.00 # D['nbdevdn']=1.00 parsed['algo_params']=D
perf = run_pipeline(print_algo=False, **parsed)
StartV=perf['portfolio_value'][ 0]
EndV=perf['portfolio_value'][-1]
# spearmint wants to minimize so return negative profit BBANDS=(StartV-EndV)
return BBANDS
if __name__ == "__main__": # Below will be overridden by spearmint when it runs main(47, {'timeperiod':10,'nbdevup':1.00,'nbdevdn':1.00}) |
|
Figure 4 : BBANDS.py – The python script that spearmint will call to perform experiments on |
The logic flow of passing parameters (spearmint experiments) through to zipline can now be described:
1 |
Figure 4 : Spearmint will read in BBANDS.py, and create experiments by replacing line 39 with the experimental data. I use a python dictionary construction for the parameters to pass.
|
2 |
Figure 4 : The dictionary is picked up as variable D in line 4, and added to the dictionary parsed in line 24.
|
3 |
Figure 3 : The patch attaches dictionary of algo parameters to the TradingAlgorithm object.
|
4 |
Figure 2 : The zipline version of the algorithm can now read the passed parameters (lines 15 to 28) and use them in the handle_data section of the algorithm .
|
5 |
Figure 4 : At the completion of the zipline run, the standard zipline perf dataframe is retuned. The opening and closing portfolio value is used to calculate the return. Spearmint is designed to minimize the objective function, so we return the a negative profit lines 24 to 33. |
Click Part 3 where we configure spearmint to optimize our algorithm. Or, click to go back to Part 1 .